summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.ci/scripts/common/post-upload.sh4
-rw-r--r--.ci/scripts/common/pre-upload.sh3
-rwxr-xr-x.ci/scripts/linux/docker.sh39
-rw-r--r--.ci/scripts/linux/upload.sh21
-rwxr-xr-x.ci/scripts/windows/docker.sh7
-rw-r--r--.ci/templates/build-msvc.yml4
-rw-r--r--.ci/yuzu-patreon-step2.yml1
-rw-r--r--.gitmodules14
-rw-r--r--.lgtm.yml10
-rwxr-xr-x.travis/linux-mingw/docker.sh10
-rwxr-xr-x.travis/linux/build.sh2
-rwxr-xr-x.travis/linux/docker.sh2
-rwxr-xr-x.travis/macos/build.sh3
-rw-r--r--CMakeLists.txt244
-rw-r--r--CMakeModules/CopyYuzuFFmpegDeps.cmake10
-rw-r--r--CMakeModules/CopyYuzuUnicornDeps.cmake9
-rw-r--r--README.md3
-rw-r--r--dist/icons/controller/applet_dual_joycon.pngbin0 -> 3554 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_dark.pngbin0 -> 3554 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_dark_disabled.pngbin0 -> 3527 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_disabled.pngbin0 -> 3314 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_midnight.pngbin0 -> 3549 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_midnight_disabled.pngbin0 -> 3584 bytes
-rw-r--r--dist/icons/controller/applet_handheld.pngbin0 -> 1671 bytes
-rw-r--r--dist/icons/controller/applet_handheld_dark.pngbin0 -> 1637 bytes
-rw-r--r--dist/icons/controller/applet_handheld_dark_disabled.pngbin0 -> 2642 bytes
-rw-r--r--dist/icons/controller/applet_handheld_disabled.pngbin0 -> 2221 bytes
-rw-r--r--dist/icons/controller/applet_handheld_midnight.pngbin0 -> 1644 bytes
-rw-r--r--dist/icons/controller/applet_handheld_midnight_disabled.pngbin0 -> 2634 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller.pngbin0 -> 4382 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_dark.pngbin0 -> 4236 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_dark_disabled.pngbin0 -> 4477 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_disabled.pngbin0 -> 4173 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_midnight.pngbin0 -> 4376 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_midnight_disabled.pngbin0 -> 4459 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left.pngbin0 -> 2083 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_dark.pngbin0 -> 2067 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_dark_disabled.pngbin0 -> 2520 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_disabled.pngbin0 -> 2179 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_midnight.pngbin0 -> 2065 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_midnight_disabled.pngbin0 -> 2529 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right.pngbin0 -> 2150 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_dark.pngbin0 -> 2146 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_dark_disabled.pngbin0 -> 2556 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_disabled.pngbin0 -> 2212 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_midnight.pngbin0 -> 2150 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_midnight_disabled.pngbin0 -> 2611 bytes
-rw-r--r--dist/icons/controller/controller.qrc51
-rw-r--r--dist/icons/controller/dual_joycon.pngbin36466 -> 0 bytes
-rw-r--r--dist/icons/controller/dual_joycon_dark.pngbin36261 -> 0 bytes
-rw-r--r--dist/icons/controller/dual_joycon_midnight.pngbin34667 -> 0 bytes
-rw-r--r--dist/icons/controller/handheld.pngbin14108 -> 0 bytes
-rw-r--r--dist/icons/controller/handheld_dark.pngbin13731 -> 0 bytes
-rw-r--r--dist/icons/controller/handheld_midnight.pngbin13366 -> 0 bytes
-rw-r--r--dist/icons/controller/pro_controller.pngbin36710 -> 0 bytes
-rw-r--r--dist/icons/controller/pro_controller_dark.pngbin34897 -> 0 bytes
-rw-r--r--dist/icons/controller/pro_controller_midnight.pngbin35893 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_left.pngbin25565 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_dark.pngbin25682 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_midnight.pngbin24405 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_vertical.pngbin24764 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_vertical_dark.pngbin24938 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_left_vertical_midnight.pngbin23681 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_right.pngbin28320 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_dark.pngbin28157 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_midnight.pngbin27006 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_vertical.pngbin27655 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_vertical_dark.pngbin27729 -> 0 bytes
-rw-r--r--dist/icons/controller/single_joycon_right_vertical_midnight.pngbin26354 -> 0 bytes
-rw-r--r--dist/languages/de.ts4749
-rw-r--r--dist/languages/es.ts4757
-rw-r--r--dist/languages/fr.ts4732
-rw-r--r--dist/languages/it.ts4724
-rw-r--r--dist/languages/ja_JP.ts4752
-rw-r--r--dist/languages/nl.ts4719
-rw-r--r--dist/languages/pl.ts4713
-rw-r--r--dist/languages/pt_BR.ts4757
-rw-r--r--dist/languages/pt_PT.ts4725
-rw-r--r--dist/languages/ru_RU.ts4720
-rw-r--r--dist/languages/zh_CN.ts4747
-rw-r--r--dist/qt_themes/colorful_dark/icons/index.theme2
-rw-r--r--dist/qt_themes/colorful_midnight_blue/icons/index.theme2
-rw-r--r--dist/qt_themes/default/style.qss200
-rw-r--r--dist/qt_themes/qdarkstyle/style.qrc2
-rw-r--r--dist/qt_themes/qdarkstyle/style.qss287
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qrc2
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qss333
-rw-r--r--dist/yuzu.bmpbin0 -> 262282 bytes
-rw-r--r--externals/CMakeLists.txt32
m---------externals/cubeb0
m---------externals/dynarmic0
m---------externals/ffmpeg0
-rw-r--r--externals/find-modules/FindFFmpeg.cmake187
-rw-r--r--externals/httplib/README.md2
-rw-r--r--externals/httplib/httplib.h4762
m---------externals/inih/inih0
m---------externals/libressl0
m---------externals/libusb0
-rw-r--r--externals/libusb/CMakeLists.txt150
-rw-r--r--externals/libusb/config.h.in90
m---------externals/libusb/libusb0
-rw-r--r--externals/lurlparser/CMakeLists.txt8
-rw-r--r--externals/lurlparser/LUrlParser.cpp265
-rw-r--r--externals/lurlparser/LUrlParser.h78
-rw-r--r--externals/lurlparser/README.md19
-rw-r--r--externals/microprofile/microprofile.h20
m---------externals/unicorn0
m---------externals/xbyak0
-rw-r--r--src/CMakeLists.txt22
-rw-r--r--src/audio_core/CMakeLists.txt36
-rw-r--r--src/audio_core/algorithm/filter.cpp9
-rw-r--r--src/audio_core/algorithm/filter.h4
-rw-r--r--src/audio_core/algorithm/interpolate.cpp37
-rw-r--r--src/audio_core/algorithm/interpolate.h3
-rw-r--r--src/audio_core/audio_out.cpp4
-rw-r--r--src/audio_core/audio_out.h3
-rw-r--r--src/audio_core/audio_renderer.cpp592
-rw-r--r--src/audio_core/audio_renderer.h245
-rw-r--r--src/audio_core/behavior_info.cpp85
-rw-r--r--src/audio_core/behavior_info.h60
-rw-r--r--src/audio_core/buffer.h2
-rw-r--r--src/audio_core/codec.cpp5
-rw-r--r--src/audio_core/codec.h2
-rw-r--r--src/audio_core/command_generator.cpp1308
-rw-r--r--src/audio_core/command_generator.h107
-rw-r--r--src/audio_core/common.h89
-rw-r--r--src/audio_core/cubeb_sink.cpp42
-rw-r--r--src/audio_core/delay_line.cpp104
-rw-r--r--src/audio_core/delay_line.h46
-rw-r--r--src/audio_core/effect_context.cpp321
-rw-r--r--src/audio_core/effect_context.h350
-rw-r--r--src/audio_core/info_updater.cpp516
-rw-r--r--src/audio_core/info_updater.h58
-rw-r--r--src/audio_core/memory_pool.cpp61
-rw-r--r--src/audio_core/memory_pool.h52
-rw-r--r--src/audio_core/mix_context.cpp296
-rw-r--r--src/audio_core/mix_context.h114
-rw-r--r--src/audio_core/sink_context.cpp45
-rw-r--r--src/audio_core/sink_context.h96
-rw-r--r--src/audio_core/splitter_context.cpp617
-rw-r--r--src/audio_core/splitter_context.h219
-rw-r--r--src/audio_core/stream.cpp52
-rw-r--r--src/audio_core/stream.h28
-rw-r--r--src/audio_core/voice_context.cpp530
-rw-r--r--src/audio_core/voice_context.h296
-rw-r--r--src/common/CMakeLists.txt45
-rw-r--r--src/common/alignment.h29
-rw-r--r--src/common/atomic_ops.cpp75
-rw-r--r--src/common/atomic_ops.h71
-rw-r--r--src/common/bit_cast.h22
-rw-r--r--src/common/bit_set.h99
-rw-r--r--src/common/bit_util.h125
-rw-r--r--src/common/color.h271
-rw-r--r--src/common/common_funcs.h47
-rw-r--r--src/common/concepts.h4
-rw-r--r--src/common/div_ceil.h26
-rw-r--r--src/common/fiber.cpp194
-rw-r--r--src/common/fiber.h29
-rw-r--r--src/common/file_util.cpp31
-rw-r--r--src/common/file_util.h2
-rw-r--r--src/common/hex_util.h10
-rw-r--r--src/common/intrusive_red_black_tree.h602
-rw-r--r--src/common/logging/backend.cpp33
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/common/math_util.h10
-rw-r--r--src/common/memory_hook.cpp11
-rw-r--r--src/common/memory_hook.h47
-rw-r--r--src/common/misc.cpp15
-rw-r--r--src/common/multi_level_queue.h345
-rw-r--r--src/common/nvidia_flags.cpp27
-rw-r--r--src/common/nvidia_flags.h10
-rw-r--r--src/common/page_table.cpp12
-rw-r--r--src/common/page_table.h100
-rw-r--r--src/common/parent_of_member.h191
-rw-r--r--src/common/quaternion.h30
-rw-r--r--src/common/ring_buffer.h21
-rw-r--r--src/common/scope_exit.h8
-rw-r--r--src/common/spin_lock.h8
-rw-r--r--src/common/stream.cpp47
-rw-r--r--src/common/stream.h56
-rw-r--r--src/common/string_util.cpp19
-rw-r--r--src/common/swap.h4
-rw-r--r--src/common/telemetry.h4
-rw-r--r--src/common/thread.cpp12
-rw-r--r--src/common/thread.h9
-rw-r--r--src/common/thread_worker.cpp58
-rw-r--r--src/common/thread_worker.h30
-rw-r--r--src/common/timer.cpp161
-rw-r--r--src/common/timer.h41
-rw-r--r--src/common/tree.h674
-rw-r--r--src/common/uuid.h4
-rw-r--r--src/common/vector_math.h75
-rw-r--r--src/common/virtual_buffer.cpp4
-rw-r--r--src/common/virtual_buffer.h38
-rw-r--r--src/common/wall_clock.cpp8
-rw-r--r--src/common/wall_clock.h8
-rw-r--r--src/common/x64/native_clock.cpp118
-rw-r--r--src/common/x64/native_clock.h28
-rw-r--r--src/common/x64/xbyak_abi.h44
-rw-r--r--src/core/CMakeLists.txt109
-rw-r--r--src/core/arm/arm_interface.cpp12
-rw-r--r--src/core/arm/arm_interface.h29
-rw-r--r--src/core/arm/cpu_interrupt_handler.h4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp37
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.h6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp66
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.h4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h4
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp295
-rw-r--r--src/core/arm/unicorn/arm_unicorn.h63
-rw-r--r--src/core/core.cpp182
-rw-r--r--src/core/core.h194
-rw-r--r--src/core/core_timing.cpp1
-rw-r--r--src/core/core_timing.h8
-rw-r--r--src/core/cpu_manager.cpp144
-rw-r--r--src/core/crypto/key_manager.cpp25
-rw-r--r--src/core/crypto/key_manager.h6
-rw-r--r--src/core/crypto/partition_data_manager.cpp1
-rw-r--r--src/core/file_sys/bis_factory.cpp11
-rw-r--r--src/core/file_sys/bis_factory.h5
-rw-r--r--src/core/file_sys/card_image.cpp10
-rw-r--r--src/core/file_sys/card_image.h9
-rw-r--r--src/core/file_sys/common_funcs.h56
-rw-r--r--src/core/file_sys/content_archive.cpp70
-rw-r--r--src/core/file_sys/content_archive.h10
-rw-r--r--src/core/file_sys/control_metadata.cpp1
-rw-r--r--src/core/file_sys/control_metadata.h4
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp10
-rw-r--r--src/core/file_sys/ips_layer.cpp8
-rw-r--r--src/core/file_sys/kernel_executable.cpp3
-rw-r--r--src/core/file_sys/kernel_executable.h9
-rw-r--r--src/core/file_sys/nca_metadata.cpp3
-rw-r--r--src/core/file_sys/nca_metadata.h2
-rw-r--r--src/core/file_sys/nca_patch.cpp85
-rw-r--r--src/core/file_sys/nca_patch.h6
-rw-r--r--src/core/file_sys/partition_filesystem.cpp8
-rw-r--r--src/core/file_sys/partition_filesystem.h8
-rw-r--r--src/core/file_sys/patch_manager.cpp226
-rw-r--r--src/core/file_sys/patch_manager.h63
-rw-r--r--src/core/file_sys/program_metadata.cpp1
-rw-r--r--src/core/file_sys/program_metadata.h2
-rw-r--r--src/core/file_sys/registered_cache.cpp3
-rw-r--r--src/core/file_sys/registered_cache.h8
-rw-r--r--src/core/file_sys/romfs.h1
-rw-r--r--src/core/file_sys/romfs_factory.cpp76
-rw-r--r--src/core/file_sys/romfs_factory.h25
-rw-r--r--src/core/file_sys/savedata_factory.cpp18
-rw-r--r--src/core/file_sys/savedata_factory.h15
-rw-r--r--src/core/file_sys/sdmc_factory.cpp2
-rw-r--r--src/core/file_sys/sdmc_factory.h2
-rw-r--r--src/core/file_sys/submission_package.cpp102
-rw-r--r--src/core/file_sys/submission_package.h11
-rw-r--r--src/core/file_sys/system_archive/data/font_nintendo_extended.cpp555
-rw-r--r--src/core/file_sys/system_archive/data/font_nintendo_extended.h2
-rw-r--r--src/core/file_sys/system_archive/system_version.cpp12
-rw-r--r--src/core/file_sys/vfs.cpp39
-rw-r--r--src/core/file_sys/vfs.h44
-rw-r--r--src/core/file_sys/vfs_concat.cpp18
-rw-r--r--src/core/file_sys/vfs_concat.h2
-rw-r--r--src/core/file_sys/vfs_layered.cpp24
-rw-r--r--src/core/file_sys/vfs_layered.h18
-rw-r--r--src/core/file_sys/vfs_offset.cpp11
-rw-r--r--src/core/file_sys/vfs_offset.h6
-rw-r--r--src/core/file_sys/vfs_real.cpp38
-rw-r--r--src/core/file_sys/vfs_real.h24
-rw-r--r--src/core/file_sys/vfs_static.h10
-rw-r--r--src/core/file_sys/vfs_vector.cpp12
-rw-r--r--src/core/file_sys/vfs_vector.h26
-rw-r--r--src/core/file_sys/xts_archive.cpp16
-rw-r--r--src/core/file_sys/xts_archive.h16
-rw-r--r--src/core/frontend/applets/controller.cpp81
-rw-r--r--src/core/frontend/applets/controller.h57
-rw-r--r--src/core/frontend/applets/error.cpp7
-rw-r--r--src/core/frontend/applets/general_frontend.cpp68
-rw-r--r--src/core/frontend/applets/general_frontend.h51
-rw-r--r--src/core/frontend/applets/web_browser.cpp24
-rw-r--r--src/core/frontend/applets/web_browser.h20
-rw-r--r--src/core/frontend/emu_window.cpp51
-rw-r--r--src/core/frontend/emu_window.h17
-rw-r--r--src/core/frontend/framebuffer_layout.cpp8
-rw-r--r--src/core/frontend/framebuffer_layout.h1
-rw-r--r--src/core/frontend/input.h59
-rw-r--r--src/core/frontend/input_interpreter.cpp49
-rw-r--r--src/core/frontend/input_interpreter.h141
-rw-r--r--src/core/gdbstub/gdbstub.cpp1397
-rw-r--r--src/core/gdbstub/gdbstub.h114
-rw-r--r--src/core/hardware_properties.h36
-rw-r--r--src/core/hle/ipc.h8
-rw-r--r--src/core/hle/ipc_helpers.h95
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp322
-rw-r--r--src/core/hle/kernel/address_arbiter.h94
-rw-r--r--src/core/hle/kernel/client_port.cpp7
-rw-r--r--src/core/hle/kernel/client_port.h2
-rw-r--r--src/core/hle/kernel/client_session.cpp24
-rw-r--r--src/core/hle/kernel/client_session.h19
-rw-r--r--src/core/hle/kernel/errors.h40
-rw-r--r--src/core/hle/kernel/global_scheduler_context.cpp52
-rw-r--r--src/core/hle/kernel/global_scheduler_context.h86
-rw-r--r--src/core/hle/kernel/handle_table.cpp22
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp75
-rw-r--r--src/core/hle/kernel/hle_ipc.h48
-rw-r--r--src/core/hle/kernel/k_address_arbiter.cpp341
-rw-r--r--src/core/hle/kernel/k_address_arbiter.h70
-rw-r--r--src/core/hle/kernel/k_affinity_mask.h58
-rw-r--r--src/core/hle/kernel/k_condition_variable.cpp345
-rw-r--r--src/core/hle/kernel/k_condition_variable.h59
-rw-r--r--src/core/hle/kernel/k_event.cpp32
-rw-r--r--src/core/hle/kernel/k_event.h57
-rw-r--r--src/core/hle/kernel/k_light_condition_variable.h57
-rw-r--r--src/core/hle/kernel/k_light_lock.cpp130
-rw-r--r--src/core/hle/kernel/k_light_lock.h41
-rw-r--r--src/core/hle/kernel/k_priority_queue.h451
-rw-r--r--src/core/hle/kernel/k_readable_event.cpp56
-rw-r--r--src/core/hle/kernel/k_readable_event.h51
-rw-r--r--src/core/hle/kernel/k_resource_limit.cpp152
-rw-r--r--src/core/hle/kernel/k_resource_limit.h81
-rw-r--r--src/core/hle/kernel/k_scheduler.cpp814
-rw-r--r--src/core/hle/kernel/k_scheduler.h207
-rw-r--r--src/core/hle/kernel/k_scheduler_lock.h75
-rw-r--r--src/core/hle/kernel/k_scoped_lock.h41
-rw-r--r--src/core/hle/kernel/k_scoped_resource_reservation.h67
-rw-r--r--src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h46
-rw-r--r--src/core/hle/kernel/k_synchronization_object.cpp171
-rw-r--r--src/core/hle/kernel/k_synchronization_object.h59
-rw-r--r--src/core/hle/kernel/k_thread.cpp1048
-rw-r--r--src/core/hle/kernel/k_thread.h768
-rw-r--r--src/core/hle/kernel/k_thread_queue.h81
-rw-r--r--src/core/hle/kernel/k_writable_event.cpp27
-rw-r--r--src/core/hle/kernel/k_writable_event.h44
-rw-r--r--src/core/hle/kernel/kernel.cpp324
-rw-r--r--src/core/hle/kernel/kernel.h68
-rw-r--r--src/core/hle/kernel/memory/address_space_info.cpp2
-rw-r--r--src/core/hle/kernel/memory/memory_block.h20
-rw-r--r--src/core/hle/kernel/memory/memory_block_manager.h4
-rw-r--r--src/core/hle/kernel/memory/memory_layout.h19
-rw-r--r--src/core/hle/kernel/memory/memory_manager.cpp6
-rw-r--r--src/core/hle/kernel/memory/page_heap.h4
-rw-r--r--src/core/hle/kernel/memory/page_table.cpp106
-rw-r--r--src/core/hle/kernel/mutex.cpp170
-rw-r--r--src/core/hle/kernel/mutex.h42
-rw-r--r--src/core/hle/kernel/object.cpp6
-rw-r--r--src/core/hle/kernel/object.h14
-rw-r--r--src/core/hle/kernel/physical_core.cpp52
-rw-r--r--src/core/hle/kernel/physical_core.h44
-rw-r--r--src/core/hle/kernel/process.cpp225
-rw-r--r--src/core/hle/kernel/process.h169
-rw-r--r--src/core/hle/kernel/process_capability.cpp40
-rw-r--r--src/core/hle/kernel/readable_event.cpp54
-rw-r--r--src/core/hle/kernel/readable_event.h57
-rw-r--r--src/core/hle/kernel/resource_limit.cpp73
-rw-r--r--src/core/hle/kernel/resource_limit.h104
-rw-r--r--src/core/hle/kernel/scheduler.cpp845
-rw-r--r--src/core/hle/kernel/scheduler.h318
-rw-r--r--src/core/hle/kernel/server_port.cpp20
-rw-r--r--src/core/hle/kernel/server_port.h9
-rw-r--r--src/core/hle/kernel/server_session.cpp71
-rw-r--r--src/core/hle/kernel/server_session.h45
-rw-r--r--src/core/hle/kernel/service_thread.cpp110
-rw-r--r--src/core/hle/kernel/service_thread.h28
-rw-r--r--src/core/hle/kernel/session.cpp22
-rw-r--r--src/core/hle/kernel/session.h8
-rw-r--r--src/core/hle/kernel/shared_memory.cpp11
-rw-r--r--src/core/hle/kernel/shared_memory.h2
-rw-r--r--src/core/hle/kernel/svc.cpp1357
-rw-r--r--src/core/hle/kernel/svc_common.h14
-rw-r--r--src/core/hle/kernel/svc_results.h41
-rw-r--r--src/core/hle/kernel/svc_types.h34
-rw-r--r--src/core/hle/kernel/svc_wrap.h103
-rw-r--r--src/core/hle/kernel/synchronization.cpp115
-rw-r--r--src/core/hle/kernel/synchronization.h44
-rw-r--r--src/core/hle/kernel/synchronization_object.cpp49
-rw-r--r--src/core/hle/kernel/synchronization_object.h76
-rw-r--r--src/core/hle/kernel/thread.cpp540
-rw-r--r--src/core/hle/kernel/thread.h681
-rw-r--r--src/core/hle/kernel/time_manager.cpp45
-rw-r--r--src/core/hle/kernel/time_manager.h12
-rw-r--r--src/core/hle/kernel/transfer_memory.cpp2
-rw-r--r--src/core/hle/kernel/transfer_memory.h2
-rw-r--r--src/core/hle/kernel/writable_event.cpp45
-rw-r--r--src/core/hle/kernel/writable_event.h59
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/acc/acc.cpp175
-rw-r--r--src/core/hle/service/acc/acc.h11
-rw-r--r--src/core/hle/service/acc/acc_su.cpp2
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp4
-rw-r--r--src/core/hle/service/acc/acc_u1.cpp2
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp20
-rw-r--r--src/core/hle/service/acc/profile_manager.h18
-rw-r--r--src/core/hle/service/am/am.cpp296
-rw-r--r--src/core/hle/service/am/am.h70
-rw-r--r--src/core/hle/service/am/applet_ae.cpp56
-rw-r--r--src/core/hle/service/am/applet_ae.h7
-rw-r--r--src/core/hle/service/am/applet_oe.cpp26
-rw-r--r--src/core/hle/service/am/applet_oe.h7
-rw-r--r--src/core/hle/service/am/applets/applets.cpp117
-rw-r--r--src/core/hle/service/am/applets/applets.h45
-rw-r--r--src/core/hle/service/am/applets/controller.cpp252
-rw-r--r--src/core/hle/service/am/applets/controller.h136
-rw-r--r--src/core/hle/service/am/applets/error.cpp18
-rw-r--r--src/core/hle/service/am/applets/general_backend.cpp28
-rw-r--r--src/core/hle/service/am/applets/general_backend.h3
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp6
-rw-r--r--src/core/hle/service/am/applets/profile_select.h1
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp18
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.h1
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp792
-rw-r--r--src/core/hle/service/am/applets/web_browser.h80
-rw-r--r--src/core/hle/service/am/applets/web_types.h178
-rw-r--r--src/core/hle/service/am/idle.cpp2
-rw-r--r--src/core/hle/service/am/idle.h6
-rw-r--r--src/core/hle/service/am/omm.cpp2
-rw-r--r--src/core/hle/service/am/omm.h6
-rw-r--r--src/core/hle/service/am/spsm.cpp2
-rw-r--r--src/core/hle/service/am/spsm.h6
-rw-r--r--src/core/hle/service/am/tcap.cpp2
-rw-r--r--src/core/hle/service/am/tcap.h6
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp108
-rw-r--r--src/core/hle/service/aoc/aoc_u.h11
-rw-r--r--src/core/hle/service/apm/apm.cpp10
-rw-r--r--src/core/hle/service/apm/apm.h4
-rw-r--r--src/core/hle/service/apm/controller.cpp6
-rw-r--r--src/core/hle/service/apm/interface.cpp32
-rw-r--r--src/core/hle/service/apm/interface.h6
-rw-r--r--src/core/hle/service/audio/audctl.cpp2
-rw-r--r--src/core/hle/service/audio/audctl.h6
-rw-r--r--src/core/hle/service/audio/auddbg.cpp2
-rw-r--r--src/core/hle/service/audio/auddbg.h6
-rw-r--r--src/core/hle/service/audio/audin_a.cpp2
-rw-r--r--src/core/hle/service/audio/audin_a.h6
-rw-r--r--src/core/hle/service/audio/audin_u.cpp6
-rw-r--r--src/core/hle/service/audio/audin_u.h6
-rw-r--r--src/core/hle/service/audio/audio.cpp26
-rw-r--r--src/core/hle/service/audio/audout_a.cpp2
-rw-r--r--src/core/hle/service/audio/audout_a.h6
-rw-r--r--src/core/hle/service/audio/audout_u.cpp43
-rw-r--r--src/core/hle/service/audio/audout_u.h2
-rw-r--r--src/core/hle/service/audio/audrec_a.cpp2
-rw-r--r--src/core/hle/service/audio/audrec_a.h6
-rw-r--r--src/core/hle/service/audio/audrec_u.cpp5
-rw-r--r--src/core/hle/service/audio/audrec_u.h6
-rw-r--r--src/core/hle/service/audio/audren_a.cpp2
-rw-r--r--src/core/hle/service/audio/audren_a.h6
-rw-r--r--src/core/hle/service/audio/audren_u.cpp212
-rw-r--r--src/core/hle/service/audio/audren_u.h1
-rw-r--r--src/core/hle/service/audio/codecctl.cpp5
-rw-r--r--src/core/hle/service/audio/codecctl.h6
-rw-r--r--src/core/hle/service/audio/hwopus.cpp9
-rw-r--r--src/core/hle/service/audio/hwopus.h6
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp18
-rw-r--r--src/core/hle/service/bcat/backend/backend.h10
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp22
-rw-r--r--src/core/hle/service/bcat/module.cpp47
-rw-r--r--src/core/hle/service/bcat/module.h3
-rw-r--r--src/core/hle/service/bpc/bpc.cpp10
-rw-r--r--src/core/hle/service/bpc/bpc.h6
-rw-r--r--src/core/hle/service/btdrv/btdrv.cpp18
-rw-r--r--src/core/hle/service/btm/btm.cpp57
-rw-r--r--src/core/hle/service/caps/caps.cpp14
-rw-r--r--src/core/hle/service/caps/caps.h6
-rw-r--r--src/core/hle/service/caps/caps_a.cpp5
-rw-r--r--src/core/hle/service/caps/caps_a.h6
-rw-r--r--src/core/hle/service/caps/caps_c.cpp21
-rw-r--r--src/core/hle/service/caps/caps_c.h9
-rw-r--r--src/core/hle/service/caps/caps_sc.cpp2
-rw-r--r--src/core/hle/service/caps/caps_sc.h6
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp2
-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_u.cpp46
-rw-r--r--src/core/hle/service/caps/caps_u.h8
-rw-r--r--src/core/hle/service/erpt/erpt.cpp10
-rw-r--r--src/core/hle/service/erpt/erpt.h6
-rw-r--r--src/core/hle/service/es/es.cpp6
-rw-r--r--src/core/hle/service/es/es.h6
-rw-r--r--src/core/hle/service/eupld/eupld.cpp10
-rw-r--r--src/core/hle/service/eupld/eupld.h6
-rw-r--r--src/core/hle/service/fatal/fatal.cpp10
-rw-r--r--src/core/hle/service/fatal/fatal.h4
-rw-r--r--src/core/hle/service/fatal/fatal_p.cpp4
-rw-r--r--src/core/hle/service/fatal/fatal_p.h2
-rw-r--r--src/core/hle/service/fatal/fatal_u.cpp4
-rw-r--r--src/core/hle/service/fatal/fatal_u.h2
-rw-r--r--src/core/hle/service/fgm/fgm.cpp18
-rw-r--r--src/core/hle/service/fgm/fgm.h6
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp62
-rw-r--r--src/core/hle/service/filesystem/filesystem.h4
-rw-r--r--src/core/hle/service/filesystem/fsp_ldr.cpp2
-rw-r--r--src/core/hle/service/filesystem/fsp_ldr.h6
-rw-r--r--src/core/hle/service/filesystem/fsp_pr.cpp2
-rw-r--r--src/core/hle/service/filesystem/fsp_pr.h6
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp124
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h7
-rw-r--r--src/core/hle/service/friend/friend.cpp31
-rw-r--r--src/core/hle/service/friend/friend.h4
-rw-r--r--src/core/hle/service/friend/interface.cpp4
-rw-r--r--src/core/hle/service/friend/interface.h2
-rw-r--r--src/core/hle/service/glue/arp.cpp14
-rw-r--r--src/core/hle/service/glue/arp.h6
-rw-r--r--src/core/hle/service/glue/bgtc.cpp4
-rw-r--r--src/core/hle/service/glue/bgtc.h8
-rw-r--r--src/core/hle/service/glue/glue.cpp4
-rw-r--r--src/core/hle/service/grc/grc.cpp6
-rw-r--r--src/core/hle/service/grc/grc.h6
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.h4
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp21
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.h21
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp11
-rw-r--r--src/core/hle/service/hid/controllers/mouse.h26
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp974
-rw-r--r--src/core/hle/service/hid/controllers/npad.h428
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp121
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h33
-rw-r--r--src/core/hle/service/hid/controllers/xpad.h70
-rw-r--r--src/core/hle/service/hid/hid.cpp919
-rw-r--r--src/core/hle/service/hid/hid.h61
-rw-r--r--src/core/hle/service/hid/irs.cpp4
-rw-r--r--src/core/hle/service/hid/irs.h10
-rw-r--r--src/core/hle/service/hid/xcd.cpp2
-rw-r--r--src/core/hle/service/hid/xcd.h6
-rw-r--r--src/core/hle/service/lbl/lbl.cpp289
-rw-r--r--src/core/hle/service/lbl/lbl.h6
-rw-r--r--src/core/hle/service/ldn/errors.h13
-rw-r--r--src/core/hle/service/ldn/ldn.cpp65
-rw-r--r--src/core/hle/service/ldn/ldn.h6
-rw-r--r--src/core/hle/service/ldr/ldr.cpp47
-rw-r--r--src/core/hle/service/ldr/ldr.h4
-rw-r--r--src/core/hle/service/lm/lm.cpp332
-rw-r--r--src/core/hle/service/lm/manager.cpp134
-rw-r--r--src/core/hle/service/lm/manager.h106
-rw-r--r--src/core/hle/service/mig/mig.cpp6
-rw-r--r--src/core/hle/service/mig/mig.h6
-rw-r--r--src/core/hle/service/mii/manager.cpp57
-rw-r--r--src/core/hle/service/mii/manager.h112
-rw-r--r--src/core/hle/service/mii/mii.cpp19
-rw-r--r--src/core/hle/service/mii/mii.h6
-rw-r--r--src/core/hle/service/mii/raw_data.cpp3848
-rw-r--r--src/core/hle/service/mii/raw_data.h27
-rw-r--r--src/core/hle/service/mm/mm_u.cpp7
-rw-r--r--src/core/hle/service/mm/mm_u.h10
-rw-r--r--src/core/hle/service/ncm/ncm.cpp22
-rw-r--r--src/core/hle/service/ncm/ncm.h6
-rw-r--r--src/core/hle/service/nfc/nfc.cpp34
-rw-r--r--src/core/hle/service/nfc/nfc.h6
-rw-r--r--src/core/hle/service/nfp/nfp.cpp71
-rw-r--r--src/core/hle/service/nfp/nfp.h15
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp4
-rw-r--r--src/core/hle/service/nfp/nfp_user.h2
-rw-r--r--src/core/hle/service/nifm/nifm.cpp228
-rw-r--r--src/core/hle/service/nifm/nifm.h8
-rw-r--r--src/core/hle/service/nim/nim.cpp66
-rw-r--r--src/core/hle/service/nim/nim.h8
-rw-r--r--src/core/hle/service/npns/npns.cpp10
-rw-r--r--src/core/hle/service/npns/npns.h6
-rw-r--r--src/core/hle/service/ns/ns.cpp77
-rw-r--r--src/core/hle/service/ns/ns.h40
-rw-r--r--src/core/hle/service/ns/pl_u.cpp60
-rw-r--r--src/core/hle/service/ns/pl_u.h22
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h36
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp21
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp169
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h109
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp133
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h113
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp192
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h55
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp294
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h169
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp73
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.h33
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp244
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h170
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp42
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h18
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.cpp65
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.h37
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp119
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h69
-rw-r--r--src/core/hle/service/nvdrv/interface.cpp206
-rw-r--r--src/core/hle/service/nvdrv/interface.h12
-rw-r--r--src/core/hle/service/nvdrv/nvdata.h86
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp141
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h51
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.cpp2
-rw-r--r--src/core/hle/service/nvdrv/nvmemp.h6
-rw-r--r--src/core/hle/service/nvdrv/syncpoint_manager.cpp39
-rw-r--r--src/core/hle/service/nvdrv/syncpoint_manager.h85
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp189
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.h35
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp52
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h15
-rw-r--r--src/core/hle/service/olsc/olsc.cpp80
-rw-r--r--src/core/hle/service/olsc/olsc.h20
-rw-r--r--src/core/hle/service/pcie/pcie.cpp8
-rw-r--r--src/core/hle/service/pcie/pcie.h6
-rw-r--r--src/core/hle/service/pctl/module.cpp76
-rw-r--r--src/core/hle/service/pctl/module.h9
-rw-r--r--src/core/hle/service/pctl/pctl.cpp4
-rw-r--r--src/core/hle/service/pctl/pctl.h6
-rw-r--r--src/core/hle/service/pcv/pcv.cpp14
-rw-r--r--src/core/hle/service/pcv/pcv.h6
-rw-r--r--src/core/hle/service/pm/pm.cpp24
-rw-r--r--src/core/hle/service/prepo/prepo.cpp104
-rw-r--r--src/core/hle/service/prepo/prepo.h8
-rw-r--r--src/core/hle/service/psc/psc.cpp14
-rw-r--r--src/core/hle/service/psc/psc.h6
-rw-r--r--src/core/hle/service/ptm/psm.cpp121
-rw-r--r--src/core/hle/service/ptm/psm.h6
-rw-r--r--src/core/hle/service/service.cpp128
-rw-r--r--src/core/hle/service/service.h57
-rw-r--r--src/core/hle/service/set/set.cpp3
-rw-r--r--src/core/hle/service/set/set.h6
-rw-r--r--src/core/hle/service/set/set_cal.cpp2
-rw-r--r--src/core/hle/service/set/set_cal.h6
-rw-r--r--src/core/hle/service/set/set_fd.cpp2
-rw-r--r--src/core/hle/service/set/set_fd.h6
-rw-r--r--src/core/hle/service/set/set_sys.cpp8
-rw-r--r--src/core/hle/service/set/set_sys.h6
-rw-r--r--src/core/hle/service/set/settings.cpp11
-rw-r--r--src/core/hle/service/set/settings.h10
-rw-r--r--src/core/hle/service/sm/controller.cpp2
-rw-r--r--src/core/hle/service/sm/controller.h6
-rw-r--r--src/core/hle/service/sm/sm.cpp28
-rw-r--r--src/core/hle/service/sm/sm.h15
-rw-r--r--src/core/hle/service/sockets/bsd.cpp811
-rw-r--r--src/core/hle/service/sockets/bsd.h147
-rw-r--r--src/core/hle/service/sockets/ethc.cpp4
-rw-r--r--src/core/hle/service/sockets/ethc.h8
-rw-r--r--src/core/hle/service/sockets/nsd.cpp2
-rw-r--r--src/core/hle/service/sockets/nsd.h7
-rw-r--r--src/core/hle/service/sockets/sfdnsres.cpp38
-rw-r--r--src/core/hle/service/sockets/sfdnsres.h6
-rw-r--r--src/core/hle/service/sockets/sockets.cpp18
-rw-r--r--src/core/hle/service/sockets/sockets.h90
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp166
-rw-r--r--src/core/hle/service/sockets/sockets_translate.h48
-rw-r--r--src/core/hle/service/spl/csrng.cpp3
-rw-r--r--src/core/hle/service/spl/csrng.h6
-rw-r--r--src/core/hle/service/spl/module.cpp11
-rw-r--r--src/core/hle/service/spl/module.h9
-rw-r--r--src/core/hle/service/spl/spl.cpp3
-rw-r--r--src/core/hle/service/spl/spl.h6
-rw-r--r--src/core/hle/service/ssl/ssl.cpp14
-rw-r--r--src/core/hle/service/ssl/ssl.h6
-rw-r--r--src/core/hle/service/time/clock_types.h26
-rw-r--r--src/core/hle/service/time/interface.cpp2
-rw-r--r--src/core/hle/service/time/standard_user_system_clock_core.cpp8
-rw-r--r--src/core/hle/service/time/standard_user_system_clock_core.h7
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.cpp4
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.h6
-rw-r--r--src/core/hle/service/time/time.cpp48
-rw-r--r--src/core/hle/service/time/time.h15
-rw-r--r--src/core/hle/service/time/time_manager.cpp359
-rw-r--r--src/core/hle/service/time/time_manager.h85
-rw-r--r--src/core/hle/service/time/time_sharedmemory.h2
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp5
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.h4
-rw-r--r--src/core/hle/service/time/time_zone_manager.cpp21
-rw-r--r--src/core/hle/service/time/time_zone_service.cpp5
-rw-r--r--src/core/hle/service/time/time_zone_service.h7
-rw-r--r--src/core/hle/service/time/time_zone_types.h22
-rw-r--r--src/core/hle/service/usb/usb.cpp39
-rw-r--r--src/core/hle/service/usb/usb.h6
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp14
-rw-r--r--src/core/hle/service/vi/display/vi_display.h9
-rw-r--r--src/core/hle/service/vi/vi.cpp205
-rw-r--r--src/core/hle/service/vi/vi.h12
-rw-r--r--src/core/hle/service/vi/vi_m.cpp6
-rw-r--r--src/core/hle/service/vi/vi_m.h8
-rw-r--r--src/core/hle/service/vi/vi_s.cpp6
-rw-r--r--src/core/hle/service/vi/vi_s.h8
-rw-r--r--src/core/hle/service/vi/vi_u.cpp6
-rw-r--r--src/core/hle/service/vi/vi_u.h8
-rw-r--r--src/core/hle/service/wlan/wlan.cpp22
-rw-r--r--src/core/hle/service/wlan/wlan.h6
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp27
-rw-r--r--src/core/loader/deconstructed_rom_directory.h8
-rw-r--r--src/core/loader/elf.cpp3
-rw-r--r--src/core/loader/elf.h8
-rw-r--r--src/core/loader/kip.cpp8
-rw-r--r--src/core/loader/kip.h8
-rw-r--r--src/core/loader/loader.cpp33
-rw-r--r--src/core/loader/loader.h19
-rw-r--r--src/core/loader/nax.cpp5
-rw-r--r--src/core/loader/nax.h10
-rw-r--r--src/core/loader/nca.cpp8
-rw-r--r--src/core/loader/nca.h8
-rw-r--r--src/core/loader/nro.cpp18
-rw-r--r--src/core/loader/nro.h8
-rw-r--r--src/core/loader/nso.cpp26
-rw-r--r--src/core/loader/nso.h16
-rw-r--r--src/core/loader/nsp.cpp29
-rw-r--r--src/core/loader/nsp.h14
-rw-r--r--src/core/loader/xci.cpp26
-rw-r--r--src/core/loader/xci.h14
-rw-r--r--src/core/memory.cpp213
-rw-r--r--src/core/memory.h34
-rw-r--r--src/core/memory/cheat_engine.cpp44
-rw-r--r--src/core/memory/cheat_engine.h5
-rw-r--r--src/core/network/network.cpp84
-rw-r--r--src/core/network/network.h24
-rw-r--r--src/core/network/sockets.h4
-rw-r--r--src/core/reporter.cpp50
-rw-r--r--src/core/reporter.h3
-rw-r--r--src/core/settings.cpp56
-rw-r--r--src/core/settings.h91
-rw-r--r--src/core/telemetry_session.cpp13
-rw-r--r--src/core/telemetry_session.h18
-rw-r--r--src/input_common/CMakeLists.txt45
-rwxr-xr-xsrc/input_common/analog_from_button.cpp140
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp520
-rw-r--r--src/input_common/gcadapter/gc_adapter.h149
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp238
-rw-r--r--src/input_common/gcadapter/gc_poller.h11
-rw-r--r--src/input_common/keyboard.cpp5
-rw-r--r--src/input_common/main.cpp160
-rw-r--r--src/input_common/main.h68
-rw-r--r--src/input_common/motion_emu.cpp169
-rw-r--r--src/input_common/motion_emu.h46
-rw-r--r--src/input_common/motion_from_button.cpp34
-rw-r--r--src/input_common/motion_from_button.h25
-rw-r--r--src/input_common/motion_input.cpp301
-rw-r--r--src/input_common/motion_input.h74
-rw-r--r--src/input_common/mouse/mouse_input.cpp159
-rw-r--r--src/input_common/mouse/mouse_input.h103
-rw-r--r--src/input_common/mouse/mouse_poller.cpp285
-rw-r--r--src/input_common/mouse/mouse_poller.h109
-rw-r--r--src/input_common/sdl/sdl.h2
-rw-r--r--src/input_common/sdl/sdl_impl.cpp684
-rw-r--r--src/input_common/sdl/sdl_impl.h4
-rw-r--r--src/input_common/settings.cpp14
-rw-r--r--src/input_common/settings.h43
-rw-r--r--src/input_common/touch_from_button.cpp52
-rw-r--r--src/input_common/touch_from_button.h23
-rw-r--r--src/input_common/udp/client.cpp358
-rw-r--r--src/input_common/udp/client.h120
-rw-r--r--src/input_common/udp/protocol.h27
-rw-r--r--src/input_common/udp/udp.cpp159
-rw-r--r--src/input_common/udp/udp.h61
-rw-r--r--src/tests/CMakeLists.txt5
-rw-r--r--src/tests/common/bit_field.cpp4
-rw-r--r--src/tests/common/bit_utils.cpp23
-rw-r--r--src/tests/common/fibers.cpp75
-rw-r--r--src/tests/common/multi_level_queue.cpp55
-rw-r--r--src/tests/common/ring_buffer.cpp38
-rw-r--r--src/tests/core/arm/arm_test_common.cpp145
-rw-r--r--src/tests/core/arm/arm_test_common.h93
-rw-r--r--src/tests/video_core/buffer_base.cpp549
-rw-r--r--src/video_core/CMakeLists.txt242
-rw-r--r--src/video_core/buffer_cache/buffer_base.h590
-rw-r--r--src/video_core/buffer_cache/buffer_block.h67
-rw-r--r--src/video_core/buffer_cache/buffer_cache.cpp13
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h1662
-rw-r--r--src/video_core/buffer_cache/map_interval.cpp33
-rw-r--r--src/video_core/buffer_cache/map_interval.h92
-rw-r--r--src/video_core/cdma_pusher.cpp170
-rw-r--r--src/video_core/cdma_pusher.h136
-rw-r--r--src/video_core/command_classes/codecs/codec.cpp129
-rw-r--r--src/video_core/command_classes/codecs/codec.h70
-rw-r--r--src/video_core/command_classes/codecs/h264.cpp293
-rw-r--r--src/video_core/command_classes/codecs/h264.h118
-rw-r--r--src/video_core/command_classes/codecs/vp9.cpp989
-rw-r--r--src/video_core/command_classes/codecs/vp9.h197
-rw-r--r--src/video_core/command_classes/codecs/vp9_types.h302
-rw-r--r--src/video_core/command_classes/host1x.cpp30
-rw-r--r--src/video_core/command_classes/host1x.h37
-rw-r--r--src/video_core/command_classes/nvdec.cpp48
-rw-r--r--src/video_core/command_classes/nvdec.h38
-rw-r--r--src/video_core/command_classes/nvdec_common.h48
-rw-r--r--src/video_core/command_classes/sync_manager.cpp60
-rw-r--r--src/video_core/command_classes/sync_manager.h64
-rw-r--r--src/video_core/command_classes/vic.cpp172
-rw-r--r--src/video_core/command_classes/vic.h110
-rw-r--r--src/video_core/compatible_formats.cpp145
-rw-r--r--src/video_core/compatible_formats.h23
-rw-r--r--src/video_core/delayed_destruction_ring.h32
-rw-r--r--src/video_core/dirty_flags.cpp38
-rw-r--r--src/video_core/dirty_flags.h11
-rw-r--r--src/video_core/dma_pusher.cpp67
-rw-r--r--src/video_core/dma_pusher.h53
-rw-r--r--src/video_core/engines/engine_upload.cpp8
-rw-r--r--src/video_core/engines/engine_upload.h4
-rw-r--r--src/video_core/engines/fermi_2d.cpp98
-rw-r--r--src/video_core/engines/fermi_2d.h331
-rw-r--r--src/video_core/engines/kepler_compute.cpp31
-rw-r--r--src/video_core/engines/kepler_compute.h23
-rw-r--r--src/video_core/engines/kepler_memory.cpp5
-rw-r--r--src/video_core/engines/kepler_memory.h6
-rw-r--r--src/video_core/engines/maxwell_3d.cpp376
-rw-r--r--src/video_core/engines/maxwell_3d.h345
-rw-r--r--src/video_core/engines/maxwell_dma.cpp14
-rw-r--r--src/video_core/engines/maxwell_dma.h16
-rw-r--r--src/video_core/engines/shader_bytecode.h192
-rw-r--r--src/video_core/engines/shader_header.h51
-rw-r--r--src/video_core/fence_manager.h56
-rw-r--r--src/video_core/framebuffer_config.h31
-rw-r--r--src/video_core/gpu.cpp191
-rw-r--r--src/video_core/gpu.h175
-rw-r--r--src/video_core/gpu_asynch.cpp64
-rw-r--r--src/video_core/gpu_asynch.h46
-rw-r--r--src/video_core/gpu_synch.cpp45
-rw-r--r--src/video_core/gpu_synch.h40
-rw-r--r--src/video_core/gpu_thread.cpp72
-rw-r--r--src/video_core/gpu_thread.h54
-rw-r--r--src/video_core/guest_driver.h4
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt85
-rw-r--r--src/video_core/host_shaders/StringShaderHeader.cmake2
-rw-r--r--src/video_core/host_shaders/block_linear_unswizzle_2d.comp122
-rw-r--r--src/video_core/host_shaders/block_linear_unswizzle_3d.comp125
-rw-r--r--src/video_core/host_shaders/convert_depth_to_float.frag13
-rw-r--r--src/video_core/host_shaders/convert_float_to_depth.frag13
-rw-r--r--src/video_core/host_shaders/full_screen_triangle.vert29
-rw-r--r--src/video_core/host_shaders/opengl_copy_bc4.comp70
-rw-r--r--src/video_core/host_shaders/opengl_present.frag4
-rw-r--r--src/video_core/host_shaders/opengl_present.vert4
-rw-r--r--src/video_core/host_shaders/pitch_unswizzle.comp86
-rw-r--r--src/video_core/host_shaders/vulkan_blit_color_float.frag14
-rw-r--r--src/video_core/host_shaders/vulkan_blit_depth_stencil.frag16
-rw-r--r--src/video_core/host_shaders/vulkan_present.frag15
-rw-r--r--src/video_core/host_shaders/vulkan_present.vert19
-rw-r--r--src/video_core/host_shaders/vulkan_quad_indexed.comp41
-rw-r--r--src/video_core/host_shaders/vulkan_uint8.comp31
-rw-r--r--src/video_core/macro/macro.cpp2
-rw-r--r--src/video_core/macro/macro_hle.cpp6
-rw-r--r--src/video_core/macro/macro_hle.h2
-rw-r--r--src/video_core/macro/macro_interpreter.cpp27
-rw-r--r--src/video_core/macro/macro_interpreter.h10
-rw-r--r--src/video_core/macro/macro_jit_x64.cpp31
-rw-r--r--src/video_core/macro/macro_jit_x64.h4
-rw-r--r--src/video_core/memory_manager.cpp76
-rw-r--r--src/video_core/memory_manager.h22
-rw-r--r--src/video_core/morton.cpp250
-rw-r--r--src/video_core/morton.h18
-rw-r--r--src/video_core/query_cache.h45
-rw-r--r--src/video_core/rasterizer_interface.h37
-rw-r--r--src/video_core/renderer_base.h36
-rw-r--r--src/video_core/renderer_opengl/gl_arb_decompiler.cpp132
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp232
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h157
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp79
-rw-r--r--src/video_core/renderer_opengl/gl_device.h29
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.h13
-rw-r--r--src/video_core/renderer_opengl/gl_framebuffer_cache.cpp85
-rw-r--r--src/video_core/renderer_opengl/gl_framebuffer_cache.h68
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp27
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h22
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp1187
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h146
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.cpp8
-rw-r--r--src/video_core/renderer_opengl/gl_resource_manager.h3
-rw-r--r--src/video_core/renderer_opengl/gl_sampler_cache.cpp52
-rw-r--r--src/video_core/renderer_opengl/gl_sampler_cache.h25
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp84
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h26
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp149
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h18
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.h16
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h6
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.cpp26
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.h71
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.cpp104
-rw-r--r--src/video_core/renderer_opengl/gl_stream_buffer.h61
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp1357
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h279
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h85
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp399
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h56
-rw-r--r--src/video_core/renderer_opengl/util_shaders.cpp225
-rw-r--r--src/video_core/renderer_opengl/util_shaders.h52
-rw-r--r--src/video_core/renderer_opengl/utils.cpp42
-rw-r--r--src/video_core/renderer_opengl/utils.h16
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp624
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h96
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp109
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h45
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp128
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.h24
-rw-r--r--src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp220
-rw-r--r--src/video_core/renderer_vulkan/nsight_aftermath_tracker.h87
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp425
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h76
-rw-r--r--src/video_core/renderer_vulkan/shaders/blit.frag24
-rw-r--r--src/video_core/renderer_vulkan/shaders/blit.vert28
-rw-r--r--src/video_core/renderer_vulkan/shaders/quad_array.comp37
-rw-r--r--src/video_core/renderer_vulkan/shaders/quad_indexed.comp50
-rw-r--r--src/video_core/renderer_vulkan/shaders/uint8.comp33
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp348
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h54
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp380
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h136
-rw-r--r--src/video_core/renderer_vulkan/vk_command_pool.cpp46
-rw-r--r--src/video_core/renderer_vulkan/vk_command_pool.h34
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp500
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h61
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.cpp23
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_pool.h19
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp790
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h274
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.cpp65
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.h30
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp110
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h30
-rw-r--r--src/video_core/renderer_vulkan/vk_image.cpp135
-rw-r--r--src/video_core/renderer_vulkan/vk_image.h84
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.cpp56
-rw-r--r--src/video_core/renderer_vulkan/vk_master_semaphore.h75
-rw-r--r--src/video_core/renderer_vulkan/vk_memory_manager.cpp230
-rw-r--r--src/video_core/renderer_vulkan/vk_memory_manager.h126
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp135
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h43
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp83
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h43
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp1329
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h204
-rw-r--r--src/video_core/renderer_vulkan/vk_renderpass_cache.cpp158
-rw-r--r--src/video_core/renderer_vulkan/vk_renderpass_cache.h70
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.cpp311
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_manager.h196
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.cpp63
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.h43
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.cpp83
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.h29
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp148
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h106
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp117
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.h41
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_util.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_util.h8
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp285
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h100
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp78
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.h23
-rw-r--r--src/video_core/renderer_vulkan/vk_stream_buffer.cpp34
-rw-r--r--src/video_core/renderer_vulkan/vk_stream_buffer.h23
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp27
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h16
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp1537
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h340
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.cpp8
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.h38
-rw-r--r--src/video_core/renderer_vulkan/wrapper.cpp805
-rw-r--r--src/video_core/renderer_vulkan/wrapper.h1057
-rw-r--r--src/video_core/sampler_cache.cpp21
-rw-r--r--src/video_core/sampler_cache.h60
-rw-r--r--src/video_core/shader/ast.cpp13
-rw-r--r--src/video_core/shader/ast.h56
-rw-r--r--src/video_core/shader/async_shaders.cpp87
-rw-r--r--src/video_core/shader/async_shaders.h42
-rw-r--r--src/video_core/shader/control_flow.cpp47
-rw-r--r--src/video_core/shader/control_flow.h14
-rw-r--r--src/video_core/shader/decode.cpp12
-rw-r--r--src/video_core/shader/decode/arithmetic.cpp6
-rw-r--r--src/video_core/shader/decode/arithmetic_half.cpp3
-rw-r--r--src/video_core/shader/decode/arithmetic_integer.cpp9
-rw-r--r--src/video_core/shader/decode/arithmetic_integer_immediate.cpp40
-rw-r--r--src/video_core/shader/decode/conversion.cpp4
-rw-r--r--src/video_core/shader/decode/half_set.cpp14
-rw-r--r--src/video_core/shader/decode/image.cpp40
-rw-r--r--src/video_core/shader/decode/memory.cpp25
-rw-r--r--src/video_core/shader/decode/other.cpp45
-rw-r--r--src/video_core/shader/decode/shift.cpp2
-rw-r--r--src/video_core/shader/decode/texture.cpp107
-rw-r--r--src/video_core/shader/decode/warp.cpp2
-rw-r--r--src/video_core/shader/expr.h6
-rw-r--r--src/video_core/shader/memory_util.cpp7
-rw-r--r--src/video_core/shader/memory_util.h6
-rw-r--r--src/video_core/shader/node.h159
-rw-r--r--src/video_core/shader/node_helper.cpp2
-rw-r--r--src/video_core/shader/registry.cpp50
-rw-r--r--src/video_core/shader/registry.h2
-rw-r--r--src/video_core/shader/shader_ir.cpp21
-rw-r--r--src/video_core/shader/shader_ir.h31
-rw-r--r--src/video_core/shader/track.cpp4
-rw-r--r--src/video_core/surface.cpp14
-rw-r--r--src/video_core/surface.h152
-rw-r--r--src/video_core/texture_cache/accelerated_swizzle.cpp70
-rw-r--r--src/video_core/texture_cache/accelerated_swizzle.h45
-rw-r--r--src/video_core/texture_cache/copy_params.h36
-rw-r--r--src/video_core/texture_cache/decode_bc4.cpp97
-rw-r--r--src/video_core/texture_cache/decode_bc4.h16
-rw-r--r--src/video_core/texture_cache/descriptor_table.h82
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp380
-rw-r--r--src/video_core/texture_cache/format_lookup_table.h42
-rw-r--r--src/video_core/texture_cache/formatter.cpp95
-rw-r--r--src/video_core/texture_cache/formatter.h263
-rw-r--r--src/video_core/texture_cache/image_base.cpp218
-rw-r--r--src/video_core/texture_cache/image_base.h83
-rw-r--r--src/video_core/texture_cache/image_info.cpp189
-rw-r--r--src/video_core/texture_cache/image_info.h38
-rw-r--r--src/video_core/texture_cache/image_view_base.cpp41
-rw-r--r--src/video_core/texture_cache/image_view_base.h47
-rw-r--r--src/video_core/texture_cache/image_view_info.cpp88
-rw-r--r--src/video_core/texture_cache/image_view_info.h50
-rw-r--r--src/video_core/texture_cache/render_targets.h51
-rw-r--r--src/video_core/texture_cache/samples_helper.h55
-rw-r--r--src/video_core/texture_cache/slot_vector.h156
-rw-r--r--src/video_core/texture_cache/surface_base.cpp294
-rw-r--r--src/video_core/texture_cache/surface_base.h333
-rw-r--r--src/video_core/texture_cache/surface_params.cpp445
-rw-r--r--src/video_core/texture_cache/surface_params.h293
-rw-r--r--src/video_core/texture_cache/surface_view.cpp27
-rw-r--r--src/video_core/texture_cache/surface_view.h68
-rw-r--r--src/video_core/texture_cache/texture_cache.h2402
-rw-r--r--src/video_core/texture_cache/types.h140
-rw-r--r--src/video_core/texture_cache/util.cpp1210
-rw-r--r--src/video_core/texture_cache/util.h109
-rw-r--r--src/video_core/textures/astc.cpp87
-rw-r--r--src/video_core/textures/astc.h5
-rw-r--r--src/video_core/textures/convert.cpp93
-rw-r--r--src/video_core/textures/convert.h22
-rw-r--r--src/video_core/textures/decoders.cpp255
-rw-r--r--src/video_core/textures/decoders.h44
-rw-r--r--src/video_core/textures/texture.cpp16
-rw-r--r--src/video_core/textures/texture.h239
-rw-r--r--src/video_core/video_core.cpp41
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.cpp203
-rw-r--r--src/video_core/vulkan_common/nsight_aftermath_tracker.h80
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.cpp46
-rw-r--r--src/video_core/vulkan_common/vulkan_debug_callback.h13
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp860
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h307
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.cpp155
-rw-r--r--src/video_core/vulkan_common/vulkan_instance.h32
-rw-r--r--src/video_core/vulkan_common/vulkan_library.cpp36
-rw-r--r--src/video_core/vulkan_common/vulkan_library.h13
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp326
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h129
-rw-r--r--src/video_core/vulkan_common/vulkan_surface.cpp81
-rw-r--r--src/video_core/vulkan_common/vulkan_surface.h18
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp940
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h1253
-rw-r--r--src/web_service/CMakeLists.txt2
-rw-r--r--src/web_service/web_backend.cpp33
-rw-r--r--src/yuzu/CMakeLists.txt43
-rw-r--r--src/yuzu/aboutdialog.ui20
-rw-r--r--src/yuzu/applets/controller.cpp679
-rw-r--r--src/yuzu/applets/controller.h161
-rw-r--r--src/yuzu/applets/controller.ui2653
-rw-r--r--src/yuzu/applets/error.cpp12
-rw-r--r--src/yuzu/applets/profile_select.cpp15
-rw-r--r--src/yuzu/applets/profile_select.h3
-rw-r--r--src/yuzu/applets/software_keyboard.cpp4
-rw-r--r--src/yuzu/applets/software_keyboard.h2
-rw-r--r--src/yuzu/applets/web_browser.cpp443
-rw-r--r--src/yuzu/applets/web_browser.h191
-rw-r--r--src/yuzu/applets/web_browser_scripts.h193
-rw-r--r--src/yuzu/bootmanager.cpp235
-rw-r--r--src/yuzu/bootmanager.h21
-rw-r--r--src/yuzu/compatdb.cpp2
-rw-r--r--src/yuzu/configuration/config.cpp481
-rw-r--r--src/yuzu/configuration/config.h27
-rw-r--r--src/yuzu/configuration/configure.ui20
-rw-r--r--src/yuzu/configuration/configure_audio.cpp10
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp3
-rw-r--r--src/yuzu/configuration/configure_cpu.ui12
-rw-r--r--src/yuzu/configuration/configure_debug.cpp7
-rw-r--r--src/yuzu/configuration/configure_debug.ui120
-rw-r--r--src/yuzu/configuration/configure_debug_controller.cpp7
-rw-r--r--src/yuzu/configuration/configure_debug_controller.h9
-rw-r--r--src/yuzu/configuration/configure_debug_controller.ui20
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp27
-rw-r--r--src/yuzu/configuration/configure_general.cpp8
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp57
-rw-r--r--src/yuzu/configuration/configure_graphics.h1
-rw-r--r--src/yuzu/configuration/configure_graphics.ui7
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp8
-rw-r--r--src/yuzu/configuration/configure_input.cpp121
-rw-r--r--src/yuzu/configuration/configure_input.h16
-rw-r--r--src/yuzu/configuration/configure_input.ui46
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp23
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h3
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui261
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp1119
-rw-r--r--src/yuzu/configuration/configure_input_player.h93
-rw-r--r--src/yuzu/configuration/configure_input_player.ui412
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp2732
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.h192
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.cpp37
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.h40
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.ui71
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp337
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h95
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui370
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.ui46
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp14
-rw-r--r--src/yuzu/configuration/configure_per_game.ui20
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp6
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp10
-rw-r--r--src/yuzu/configuration/configure_service.cpp2
-rw-r--r--src/yuzu/configuration/configure_system.cpp40
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.cpp623
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.h92
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.ui221
-rw-r--r--src/yuzu/configuration/configure_touch_widget.h62
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.cpp3
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.ui51
-rw-r--r--src/yuzu/configuration/configure_ui.cpp3
-rw-r--r--src/yuzu/configuration/configure_vibration.cpp146
-rw-r--r--src/yuzu/configuration/configure_vibration.h43
-rw-r--r--src/yuzu/configuration/configure_vibration.ui546
-rw-r--r--src/yuzu/configuration/input_profiles.cpp131
-rw-r--r--src/yuzu/configuration/input_profiles.h32
-rw-r--r--src/yuzu/debugger/controller.cpp66
-rw-r--r--src/yuzu/debugger/controller.h31
-rw-r--r--src/yuzu/debugger/profiler.cpp2
-rw-r--r--src/yuzu/debugger/wait_tree.cpp207
-rw-r--r--src/yuzu/debugger/wait_tree.h31
-rw-r--r--src/yuzu/game_list.cpp141
-rw-r--r--src/yuzu/game_list.h24
-rw-r--r--src/yuzu/game_list_p.h39
-rw-r--r--src/yuzu/game_list_worker.cpp36
-rw-r--r--src/yuzu/install_dialog.h5
-rw-r--r--src/yuzu/main.cpp697
-rw-r--r--src/yuzu/main.h38
-rw-r--r--src/yuzu/main.ui86
-rw-r--r--src/yuzu/util/url_request_interceptor.cpp34
-rw-r--r--src/yuzu/util/url_request_interceptor.h30
-rw-r--r--src/yuzu/yuzu.qrc5
-rw-r--r--src/yuzu_cmd/CMakeLists.txt30
-rw-r--r--src/yuzu_cmd/config.cpp47
-rw-r--r--src/yuzu_cmd/default_ini.h17
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp143
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h30
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp44
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp11
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h7
-rw-r--r--src/yuzu_cmd/yuzu.cpp65
-rw-r--r--src/yuzu_tester/CMakeLists.txt34
-rw-r--r--src/yuzu_tester/config.cpp193
-rw-r--r--src/yuzu_tester/config.h24
-rw-r--r--src/yuzu_tester/default_ini.h182
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp148
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.h40
-rw-r--r--src/yuzu_tester/resource.h16
-rw-r--r--src/yuzu_tester/service/yuzutest.cpp112
-rw-r--r--src/yuzu_tester/service/yuzutest.h25
-rw-r--r--src/yuzu_tester/yuzu.cpp268
-rw-r--r--src/yuzu_tester/yuzu.rc17
1146 files changed, 128624 insertions, 41758 deletions
diff --git a/.ci/scripts/common/post-upload.sh b/.ci/scripts/common/post-upload.sh
index e46ee0abb..99e79fcb6 100644
--- a/.ci/scripts/common/post-upload.sh
+++ b/.ci/scripts/common/post-upload.sh
@@ -15,5 +15,5 @@ mv "${REV_NAME}-source.tar.xz" $RELEASE_NAME
7z a "$REV_NAME.7z" $RELEASE_NAME
# move the compiled archive into the artifacts directory to be uploaded by travis releases
-mv "$ARCHIVE_NAME" artifacts/
-mv "$REV_NAME.7z" artifacts/
+mv "$ARCHIVE_NAME" "${ARTIFACTS_DIR}/"
+mv "$REV_NAME.7z" "${ARTIFACTS_DIR}/"
diff --git a/.ci/scripts/common/pre-upload.sh b/.ci/scripts/common/pre-upload.sh
index 3c2fc79a2..a49e3fff3 100644
--- a/.ci/scripts/common/pre-upload.sh
+++ b/.ci/scripts/common/pre-upload.sh
@@ -2,5 +2,6 @@
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`"
+ARTIFACTS_DIR="artifacts"
-mkdir -p artifacts
+mkdir -p "${ARTIFACTS_DIR}/"
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index 277775ef6..39b1f77d7 100755
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -1,14 +1,49 @@
#!/bin/bash -ex
+# Exit on error, rather than continuing with the rest of the script.
+set -e
+
cd /yuzu
ccache -s
mkdir build || true && cd build
-cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON
+cmake .. -DDISPLAY_VERSION=$1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_INSTALL_PREFIX="/usr"
-ninja
+make -j$(nproc)
ccache -s
ctest -VV -C Release
+
+make install DESTDIR=AppDir
+rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester
+
+# Download tools needed to build an AppImage
+wget -nc https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
+wget -nc https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
+wget -nc https://github.com/darealshinji/AppImageKit-checkrt/releases/download/continuous/AppRun-patched-x86_64
+wget -nc https://github.com/darealshinji/AppImageKit-checkrt/releases/download/continuous/exec-x86_64.so
+# Set executable bit
+chmod 755 \
+ AppRun-patched-x86_64 \
+ exec-x86_64.so \
+ linuxdeploy-x86_64.AppImage \
+ linuxdeploy-plugin-qt-x86_64.AppImage
+
+# Workaround for https://github.com/AppImage/AppImageKit/issues/828
+export APPIMAGE_EXTRACT_AND_RUN=1
+
+mkdir -p AppDir/usr/optional
+mkdir -p AppDir/usr/optional/libstdc++
+mkdir -p AppDir/usr/optional/libgcc_s
+
+# Deploy yuzu's needed dependencies
+./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt
+
+# Workaround for building yuzu with GCC 10 but also trying to distribute it to Ubuntu 18.04 et al.
+# See https://github.com/darealshinji/AppImageKit-checkrt
+cp exec-x86_64.so AppDir/usr/optional/exec.so
+cp AppRun-patched-x86_64 AppDir/AppRun
+cp --dereference /usr/lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/optional/libstdc++/libstdc++.so.6
+cp --dereference /lib/x86_64-linux-gnu/libgcc_s.so.1 AppDir/usr/optional/libgcc_s/libgcc_s.so.1
diff --git a/.ci/scripts/linux/upload.sh b/.ci/scripts/linux/upload.sh
index fe4e6b2ac..b2ea07388 100644
--- a/.ci/scripts/linux/upload.sh
+++ b/.ci/scripts/linux/upload.sh
@@ -2,6 +2,7 @@
. .ci/scripts/common/pre-upload.sh
+APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}.AppImage"
REV_NAME="yuzu-linux-${GITDATE}-${GITREV}"
ARCHIVE_NAME="${REV_NAME}.tar.xz"
COMPRESSION_FLAGS="-cJvf"
@@ -17,4 +18,24 @@ mkdir "$DIR_NAME"
cp build/bin/yuzu-cmd "$DIR_NAME"
cp build/bin/yuzu "$DIR_NAME"
+# Build an AppImage
+cd build
+
+wget -nc https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
+chmod 755 appimagetool-x86_64.AppImage
+
+if [ "${RELEASE_NAME}" = "mainline" ]; then
+ # Generate update information if releasing to mainline
+ ./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}"
+else
+ ./appimagetool-x86_64.AppImage AppDir "${APPIMAGE_NAME}"
+fi
+cd ..
+
+# Copy the AppImage and update info to the artifacts directory and avoid compressing it
+cp "build/${APPIMAGE_NAME}" "${ARTIFACTS_DIR}/"
+if [ -f "build/${APPIMAGE_NAME}.zsync" ]; then
+ cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/"
+fi
+
. .ci/scripts/common/post-upload.sh
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index adfd636fa..192a01fd8 100755
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -5,7 +5,7 @@ cd /yuzu
ccache -s
mkdir build || true && cd build
-cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON
+cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON
ninja
ccache -s
@@ -42,3 +42,8 @@ done
pip3 install pefile
python3 .ci/scripts/windows/scan_dll.py package/*.exe "package/"
python3 .ci/scripts/windows/scan_dll.py package/imageformats/*.dll "package/"
+
+# copy FFmpeg libraries
+EXTERNALS_PATH="$(pwd)/build/externals"
+FFMPEG_DLL_PATH="$(find ${EXTERNALS_PATH} -maxdepth 1 -type d | grep ffmpeg)/bin"
+find ${FFMPEG_DLL_PATH} -type f -regex ".*\.dll" -exec cp -v {} package/ ';'
diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml
index d85a949aa..721179550 100644
--- a/.ci/templates/build-msvc.yml
+++ b/.ci/templates/build-msvc.yml
@@ -4,9 +4,11 @@ parameters:
version: ''
steps:
+- script: choco install vulkan-sdk
+ displayName: 'Install vulkan-sdk'
- script: python -m pip install --upgrade pip conan
displayName: 'Install conan'
-- script: mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
+- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
displayName: 'Configure CMake'
- task: MSBuild@1
displayName: 'Build'
diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml
index 41eccd973..3f338e2a0 100644
--- a/.ci/yuzu-patreon-step2.yml
+++ b/.ci/yuzu-patreon-step2.yml
@@ -9,6 +9,7 @@ stages:
displayName: 'build'
jobs:
- job: build
+ timeoutInMinutes: 120
displayName: 'windows-msvc'
pool:
vmImage: windows-2019
diff --git a/.gitmodules b/.gitmodules
index 79028bbb5..4962f7bfd 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,21 +1,21 @@
[submodule "inih"]
path = externals/inih/inih
- url = https://github.com/svn2github/inih
+ url = https://github.com/benhoyt/inih.git
[submodule "cubeb"]
path = externals/cubeb
url = https://github.com/kinetiknz/cubeb.git
[submodule "dynarmic"]
path = externals/dynarmic
url = https://github.com/MerryMage/dynarmic.git
-[submodule "unicorn"]
- path = externals/unicorn
- url = https://github.com/yuzu-emu/unicorn
[submodule "soundtouch"]
path = externals/soundtouch
url = https://github.com/citra-emu/ext-soundtouch.git
[submodule "libressl"]
path = externals/libressl
url = https://github.com/citra-emu/ext-libressl-portable.git
+[submodule "libusb"]
+ path = externals/libusb/libusb
+ url = https://github.com/libusb/libusb.git
[submodule "discord-rpc"]
path = externals/discord-rpc
url = https://github.com/discordapp/discord-rpc.git
@@ -34,9 +34,9 @@
[submodule "xbyak"]
path = externals/xbyak
url = https://github.com/herumi/xbyak.git
-[submodule "externals/libusb"]
- path = externals/libusb
- url = https://github.com/ameerj/libusb
[submodule "opus"]
path = externals/opus/opus
url = https://github.com/xiph/opus.git
+[submodule "externals/ffmpeg"]
+ path = externals/ffmpeg
+ url = https://git.ffmpeg.org/ffmpeg.git
diff --git a/.lgtm.yml b/.lgtm.yml
new file mode 100644
index 000000000..5751d0e4c
--- /dev/null
+++ b/.lgtm.yml
@@ -0,0 +1,10 @@
+path_classifiers:
+ library: "externals"
+extraction:
+ cpp:
+ prepare:
+ packages:
+ - "libsdl2-dev"
+ - "qtmultimedia5-dev"
+ - "libtbb-dev"
+ - "libjack-jackd2-dev"
diff --git a/.travis/linux-mingw/docker.sh b/.travis/linux-mingw/docker.sh
index 28033acfb..80d7dfe9b 100755
--- a/.travis/linux-mingw/docker.sh
+++ b/.travis/linux-mingw/docker.sh
@@ -4,16 +4,8 @@ cd /yuzu
# override Travis CI unreasonable ccache size
echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf"
-# Dirty hack to trick unicorn makefile into believing we are in a MINGW system
-mv /bin/uname /bin/uname1 && echo -e '#!/bin/sh\necho MINGW64' >> /bin/uname
-chmod +x /bin/uname
-
-# Dirty hack to trick unicorn makefile into believing we have cmd
-echo '' >> /bin/cmd
-chmod +x /bin/cmd
-
mkdir build && cd build
-cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
+cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
ninja
# Clean up the dirty hacks
diff --git a/.travis/linux/build.sh b/.travis/linux/build.sh
index 3929f97fc..0c7fb8c9d 100755
--- a/.travis/linux/build.sh
+++ b/.travis/linux/build.sh
@@ -1,4 +1,4 @@
#!/bin/bash -ex
mkdir -p "$HOME/.ccache"
-docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.travis/linux/docker.sh
+docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/home/yuzu/.ccache yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.travis/linux/docker.sh
diff --git a/.travis/linux/docker.sh b/.travis/linux/docker.sh
index 3a9970384..166fb6d4c 100755
--- a/.travis/linux/docker.sh
+++ b/.travis/linux/docker.sh
@@ -3,7 +3,7 @@
cd /yuzu
mkdir build && cd build
-cmake .. -G Ninja -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
+cmake .. -G Ninja -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
ninja
ccache -s
diff --git a/.travis/macos/build.sh b/.travis/macos/build.sh
index 0abd1a93a..db1c7cae7 100755
--- a/.travis/macos/build.sh
+++ b/.travis/macos/build.sh
@@ -4,13 +4,12 @@ set -o pipefail
export MACOSX_DEPLOYMENT_TARGET=10.14
export Qt5_DIR=$(brew --prefix)/opt/qt5
-export UNICORNDIR=$(pwd)/externals/unicorn
export PATH="/usr/local/opt/ccache/libexec:$PATH"
# TODO: Build using ninja instead of make
mkdir build && cd build
cmake --version
-cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
+cmake .. -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
make -j4
ccache -s
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2f3c59c5d..c45123139 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,7 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "EN
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
-option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
+CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled yuzu" ON "WIN32" OFF)
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
@@ -26,10 +26,12 @@ option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implemen
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
-option(ENABLE_VULKAN "Enables Vulkan backend" ON)
-
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
+if (NOT ENABLE_WEB_SERVICE)
+ set(YUZU_ENABLE_BOXCAT OFF)
+endif()
+
# Default to a Release build
get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
@@ -115,6 +117,9 @@ if (NOT DEFINED ARCHITECTURE)
endif()
message(STATUS "Target architecture: ${ARCHITECTURE}")
+if (UNIX)
+ add_definitions(-DYUZU_UNIX=1)
+endif()
# Configure C++ standard
# ===========================
@@ -159,15 +164,14 @@ macro(yuzu_find_packages)
# Capitalization matters here. We need the naming to match the generated paths from Conan
set(REQUIRED_LIBS
# Cmake Pkg Prefix Version Conan Pkg
- "Boost 1.73 boost/1.73.0"
"Catch2 2.13 catch2/2.13.0"
- "fmt 7.0 fmt/7.0.3"
+ "fmt 7.1 fmt/7.1.2"
# can't use until https://github.com/bincrafters/community/issues/1173
#"libzip 1.5 libzip/1.5.2@bincrafters/stable"
"lz4 1.8 lz4/1.9.2"
"nlohmann_json 3.8 nlohmann_json/3.8.0"
"ZLIB 1.2 zlib/1.2.11"
- "zstd 1.4 zstd/1.4.5"
+ "zstd 1.4 zstd/1.4.8"
)
foreach(PACKAGE ${REQUIRED_LIBS})
@@ -194,6 +198,22 @@ macro(yuzu_find_packages)
unset(FN_FORCE_REQUIRED)
endmacro()
+find_package(Boost 1.73.0 COMPONENTS context headers QUIET)
+if (Boost_FOUND)
+ set(Boost_LIBRARIES Boost::boost)
+ # Conditionally add Boost::context only if the active version of the Conan or system Boost package provides it
+ # The old version is missing Boost::context, so we want to avoid adding in that case
+ # The new version requires adding Boost::context to prevent linking issues
+ #
+ # This one is used by Conan on subsequent CMake configures, not the first configure.
+ if (TARGET Boost::context)
+ list(APPEND Boost_LIBRARIES Boost::context)
+ endif()
+else()
+ message(STATUS "Boost 1.73.0 or newer not found, falling back to Conan")
+ list(APPEND CONAN_REQUIRED_LIBS "boost/1.73.0")
+endif()
+
# Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
yuzu_find_packages()
@@ -210,7 +230,7 @@ if(ENABLE_QT)
set(QT_PREFIX_HINT)
if(YUZU_USE_BUNDLED_QT)
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
- set(QT_VER qt-5.12.0-msvc2017_64)
+ set(QT_VER qt-5.12.8-msvc2017_64)
else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
endif()
@@ -225,7 +245,7 @@ if(ENABLE_QT)
if (YUZU_USE_QT_WEB_ENGINE)
find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets)
endif()
-
+
if (ENABLE_QT_TRANSLATION)
find_package(Qt5 REQUIRED COMPONENTS LinguistTools ${QT_PREFIX_HINT})
endif()
@@ -243,7 +263,7 @@ if(ENABLE_SDL2)
find_package(SDL2)
if (NOT SDL2_FOUND)
# otherwise add this to the list of libraries to install
- list(APPEND CONAN_REQUIRED_LIBS "sdl2/2.0.12@bincrafters/stable")
+ list(APPEND CONAN_REQUIRED_LIBS "sdl2/2.0.14@bincrafters/stable")
endif()
endif()
@@ -263,6 +283,7 @@ if (CONAN_REQUIRED_LIBS)
libzip:with_openssl=False
libzip:enable_windows_crypto=False
)
+
conan_check(VERSION 1.24.0 REQUIRED)
# Add the bincrafters remote
conan_add_remote(NAME bincrafters
@@ -297,6 +318,17 @@ if (CONAN_REQUIRED_LIBS)
# this time with required, so we bail if its not found.
yuzu_find_packages(FORCE_REQUIRED)
+ if (NOT Boost_FOUND)
+ find_package(Boost 1.73.0 REQUIRED COMPONENTS context headers)
+ set(Boost_LIBRARIES Boost::boost)
+ # Conditionally add Boost::context only if the active version of the Conan Boost package provides it
+ # The old version is missing Boost::context, so we want to avoid adding in that case
+ # The new version requires adding Boost::context to prevent linking issues
+ if (TARGET Boost::context)
+ list(APPEND Boost_LIBRARIES Boost::context)
+ endif()
+ endif()
+
# Due to issues with variable scopes in functions, we need to also find_package(qt5) outside of the function
if(ENABLE_QT)
list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")
@@ -354,84 +386,144 @@ if (NOT LIBUSB_FOUND)
set(LIBUSB_LIBRARIES usb)
endif()
-# Prefer the -pthread flag on Linux.
-set(THREADS_PREFER_PTHREAD_FLAG ON)
-find_package(Threads REQUIRED)
+# List of all FFmpeg components required
+set(FFmpeg_COMPONENTS
+ avcodec
+ avutil
+ swscale)
+
+if (NOT YUZU_USE_BUNDLED_FFMPEG)
+ # Use system installed FFmpeg
+ find_package(FFmpeg REQUIRED COMPONENTS ${FFmpeg_COMPONENTS})
+
+ if (FFmpeg_FOUND)
+ # Overwrite aggregate defines from FFmpeg module to avoid over-linking libraries.
+ # Prevents shipping too many libraries with the AppImage.
+ set(FFmpeg_LIBRARIES "")
+ set(FFmpeg_INCLUDE_DIR "")
+
+ foreach(COMPONENT ${FFmpeg_COMPONENTS})
+ set(FFmpeg_LIBRARIES ${FFmpeg_LIBRARIES} ${FFmpeg_LIBRARY_${COMPONENT}} CACHE PATH "Paths to FFmpeg libraries" FORCE)
+ set(FFmpeg_INCLUDE_DIR ${FFmpeg_INCLUDE_DIR} ${FFmpeg_INCLUDE_${COMPONENT}} CACHE PATH "Path to FFmpeg headers" FORCE)
+ endforeach()
+ else()
+ message(WARNING "FFmpeg not found, falling back to externals")
+ set(YUZU_USE_BUNDLED_FFMPEG ON)
+ endif()
+endif()
-# If unicorn isn't found, msvc -> download bundled unicorn; everyone else -> build external
-if (YUZU_USE_BUNDLED_UNICORN)
- if (MSVC)
- message(STATUS "unicorn not found, falling back to bundled")
- # Detect toolchain and platform
- if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
- set(UNICORN_VER "unicorn-yuzu")
- else()
- message(FATAL_ERROR "No bundled Unicorn binaries for your toolchain. Disable YUZU_USE_BUNDLED_UNICORN and provide your own.")
+if (YUZU_USE_BUNDLED_FFMPEG)
+ if (NOT WIN32)
+ # Build FFmpeg from externals
+ message(STATUS "Using FFmpeg from externals")
+
+ # FFmpeg has source that requires one of nasm or yasm to assemble it.
+ # REQUIRED throws an error if not found here during configuration rather than during compilation.
+ find_program(ASSEMBLER NAMES nasm yasm REQUIRED)
+
+ set(FFmpeg_PREFIX ${PROJECT_SOURCE_DIR}/externals/ffmpeg)
+ set(FFmpeg_BUILD_DIR ${PROJECT_BINARY_DIR}/externals/ffmpeg)
+ set(FFmpeg_MAKEFILE ${FFmpeg_BUILD_DIR}/Makefile)
+ make_directory(${FFmpeg_BUILD_DIR})
+
+ # Read version string from external
+ file(READ ${FFmpeg_PREFIX}/RELEASE FFmpeg_VERSION)
+ set(FFmpeg_FOUND NO)
+ if (NOT FFmpeg_VERSION STREQUAL "")
+ set(FFmpeg_FOUND YES)
endif()
- if (DEFINED UNICORN_VER)
- download_bundled_external("unicorn/" ${UNICORN_VER} UNICORN_PREFIX)
- endif()
+ foreach(COMPONENT ${FFmpeg_COMPONENTS})
+ set(FFmpeg_${COMPONENT}_PREFIX "${FFmpeg_BUILD_DIR}/lib${COMPONENT}")
+ set(FFmpeg_${COMPONENT}_LIB_NAME "lib${COMPONENT}.a")
+ set(FFmpeg_${COMPONENT}_LIBRARY "${FFmpeg_${COMPONENT}_PREFIX}/${FFmpeg_${COMPONENT}_LIB_NAME}")
+
+ set(FFmpeg_LIBRARIES
+ ${FFmpeg_LIBRARIES}
+ ${FFmpeg_${COMPONENT}_LIBRARY}
+ CACHE PATH "Paths to FFmpeg libraries" FORCE)
+ endforeach()
+
+ set(FFmpeg_INCLUDE_DIR
+ ${FFmpeg_PREFIX}
+ CACHE PATH "Path to FFmpeg headers" FORCE)
+
+ # `configure` parameters builds only exactly what yuzu needs from FFmpeg
+ # `--disable-{vaapi,vdpau}` is needed to avoid linking issues
+ add_custom_command(
+ OUTPUT
+ ${FFmpeg_MAKEFILE}
+ COMMAND
+ /bin/bash ${FFmpeg_PREFIX}/configure
+ --disable-avdevice
+ --disable-avfilter
+ --disable-avformat
+ --disable-doc
+ --disable-everything
+ --disable-ffmpeg
+ --disable-ffprobe
+ --disable-network
+ --disable-postproc
+ --disable-swresample
+ --disable-vaapi
+ --disable-vdpau
+ --enable-decoder=h264
+ --enable-decoder=vp9
+ WORKING_DIRECTORY
+ ${FFmpeg_BUILD_DIR}
+ )
- if (DEFINED UNICORN_VER)
- download_bundled_external("unicorn/" ${UNICORN_VER} UNICORN_PREFIX)
- endif()
+ # Workaround for Ubuntu 18.04's older version of make not being able to call make as a child
+ # with context of the jobserver. Also helps ninja users.
+ execute_process(
+ COMMAND
+ nproc
+ OUTPUT_VARIABLE
+ SYSTEM_THREADS)
+
+ add_custom_command(
+ OUTPUT
+ ${FFmpeg_LIBRARIES}
+ COMMAND
+ make -j${SYSTEM_THREADS}
+ WORKING_DIRECTORY
+ ${FFmpeg_BUILD_DIR}
+ )
- set(UNICORN_FOUND YES)
- set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE)
- set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/lib/x64/unicorn_dynload.lib" CACHE PATH "Path to Unicorn library" FORCE)
- set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/lib/x64/" CACHE PATH "Path to unicorn.dll" FORCE)
- else()
- message(STATUS "unicorn not found, falling back to externals")
- if (MINGW)
- set(UNICORN_LIB_NAME "unicorn.a")
- else()
- set(UNICORN_LIB_NAME "libunicorn.a")
- endif()
+ # ALL makes this custom target build every time
+ # but it won't actually build if the DEPENDS parameter is up to date
+ add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_LIBRARIES})
+ add_custom_target(ffmpeg-configure ALL DEPENDS ${FFmpeg_MAKEFILE})
+
+ if (FFmpeg_FOUND)
+ message(STATUS "Found FFmpeg version ${FFmpeg_VERSION}")
- set(UNICORN_FOUND YES)
- set(UNICORN_PREFIX ${PROJECT_SOURCE_DIR}/externals/unicorn)
- set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/${UNICORN_LIB_NAME}" CACHE PATH "Path to Unicorn library" FORCE)
- set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE)
- set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/" CACHE PATH "Path to unicorn dynamic library" FORCE)
-
- find_package(PythonInterp 2.7 REQUIRED)
-
- if (MINGW)
- # Intentionally call the unicorn makefile directly instead of using make.sh so that we can override the
- # UNAME_S makefile variable to MINGW. This way we don't have to hack at the uname binary to build
- # Additionally, overriding DO_WINDOWS_EXPORT prevents unicorn from patching the static unicorn.a by using msvc and cmd,
- # which are both things we don't have in a mingw cross compiling environment.
- add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY}
- COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-gcc-ar RANLIB=x86_64-w64-mingw32-gcc-ranlib make UNAME_S=MINGW DO_WINDOWS_EXPORT=0
- WORKING_DIRECTORY ${UNICORN_PREFIX}
- )
+ add_dependencies(ffmpeg-build ffmpeg-configure)
else()
- add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY}
- COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" /bin/sh make.sh macos-universal-no
- WORKING_DIRECTORY ${UNICORN_PREFIX}
- )
+ message(FATAL_ERROR "FFmpeg not found")
endif()
-
- # ALL makes this custom target build every time
- # but it won't actually build if LIBUNICORN_LIBRARY is up to date
- add_custom_target(unicorn-build ALL
- DEPENDS ${LIBUNICORN_LIBRARY}
- )
- unset(UNICORN_LIB_NAME)
+ else() # WIN32
+ # Use yuzu FFmpeg binaries
+ set(FFmpeg_EXT_NAME "ffmpeg-4.2.1")
+ set(FFmpeg_PATH "${CMAKE_BINARY_DIR}/externals/${FFmpeg_EXT_NAME}")
+ download_bundled_external("ffmpeg/" ${FFmpeg_EXT_NAME} "")
+ set(FFmpeg_FOUND YES)
+ set(FFmpeg_INCLUDE_DIR "${FFmpeg_PATH}/include" CACHE PATH "Path to FFmpeg headers" FORCE)
+ set(FFmpeg_LIBRARY_DIR "${FFmpeg_PATH}/bin" CACHE PATH "Path to FFmpeg library directory" FORCE)
+ set(FFmpeg_DLL_DIR "${FFmpeg_PATH}/bin" CACHE PATH "Path to FFmpeg dll's" FORCE)
+ set(FFmpeg_LIBRARIES
+ ${FFmpeg_LIBRARY_DIR}/swscale.lib
+ ${FFmpeg_LIBRARY_DIR}/avcodec.lib
+ ${FFmpeg_LIBRARY_DIR}/avutil.lib
+ CACHE PATH "Paths to FFmpeg libraries" FORCE)
endif()
-else()
- find_package(Unicorn REQUIRED)
endif()
-if (UNICORN_FOUND)
- add_library(unicorn INTERFACE)
- add_dependencies(unicorn unicorn-build)
- target_link_libraries(unicorn INTERFACE "${LIBUNICORN_LIBRARY}")
- target_include_directories(unicorn INTERFACE "${LIBUNICORN_INCLUDE_DIR}")
-else()
- message(FATAL_ERROR "Could not find or build unicorn which is required.")
-endif()
+unset(FFmpeg_COMPONENTS)
+
+# Prefer the -pthread flag on Linux.
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+find_package(Threads REQUIRED)
# Platform-specific library requirements
# ======================================
diff --git a/CMakeModules/CopyYuzuFFmpegDeps.cmake b/CMakeModules/CopyYuzuFFmpegDeps.cmake
new file mode 100644
index 000000000..b7162cf17
--- /dev/null
+++ b/CMakeModules/CopyYuzuFFmpegDeps.cmake
@@ -0,0 +1,10 @@
+function(copy_yuzu_FFmpeg_deps target_dir)
+ include(WindowsCopyFiles)
+ set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
+ windows_copy_files(${target_dir} ${FFmpeg_DLL_DIR} ${DLL_DEST}
+ avcodec-58.dll
+ avutil-56.dll
+ swresample-3.dll
+ swscale-5.dll
+ )
+endfunction(copy_yuzu_FFmpeg_deps)
diff --git a/CMakeModules/CopyYuzuUnicornDeps.cmake b/CMakeModules/CopyYuzuUnicornDeps.cmake
deleted file mode 100644
index 7af0ef023..000000000
--- a/CMakeModules/CopyYuzuUnicornDeps.cmake
+++ /dev/null
@@ -1,9 +0,0 @@
-function(copy_yuzu_unicorn_deps target_dir)
- include(WindowsCopyFiles)
- set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
- windows_copy_files(${target_dir} ${UNICORN_DLL_DIR} ${DLL_DEST}
- libgcc_s_seh-1.dll
- libwinpthread-1.dll
- unicorn.dll
- )
-endfunction(copy_yuzu_unicorn_deps)
diff --git a/README.md b/README.md
index 981c8ef24..cb1a64d8c 100644
--- a/README.md
+++ b/README.md
@@ -30,11 +30,10 @@ If you want to contribute to the user interface translation, please check out th
* __Windows__: [Windows Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Windows)
* __Linux__: [Linux Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Linux)
-* __macOS__: [macOS Build](https://github.com/yuzu-emu/yuzu/wiki/Building-for-macOS)
### Support
-We happily accept monetary donations or donated games and hardware. Please see our [donations page](https://yuzu-emu.org/donate/) for more information on how you can contribute to yuzu. Any donations received will go towards things like:
+We happily accept monetary donations, or donated games and hardware. Please see our [donations page](https://yuzu-emu.org/donate/) for more information on how you can contribute to yuzu. Any donations received will go towards things like:
* Switch consoles to explore and reverse-engineer the hardware
* Switch games for testing, reverse-engineering, and implementing new features
* Web hosting and infrastructure setup
diff --git a/dist/icons/controller/applet_dual_joycon.png b/dist/icons/controller/applet_dual_joycon.png
new file mode 100644
index 000000000..32e0a04ae
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_dark.png b/dist/icons/controller/applet_dual_joycon_dark.png
new file mode 100644
index 000000000..6adc66356
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_dark_disabled.png b/dist/icons/controller/applet_dual_joycon_dark_disabled.png
new file mode 100644
index 000000000..208603ee7
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_disabled.png b/dist/icons/controller/applet_dual_joycon_disabled.png
new file mode 100644
index 000000000..43950618d
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_midnight.png b/dist/icons/controller/applet_dual_joycon_midnight.png
new file mode 100644
index 000000000..c7edea8a3
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_midnight_disabled.png b/dist/icons/controller/applet_dual_joycon_midnight_disabled.png
new file mode 100644
index 000000000..ee1aafc85
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld.png b/dist/icons/controller/applet_handheld.png
new file mode 100644
index 000000000..7f8dd2227
--- /dev/null
+++ b/dist/icons/controller/applet_handheld.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_dark.png b/dist/icons/controller/applet_handheld_dark.png
new file mode 100644
index 000000000..41f6d7aea
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_dark_disabled.png b/dist/icons/controller/applet_handheld_dark_disabled.png
new file mode 100644
index 000000000..5a136ddd1
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_disabled.png b/dist/icons/controller/applet_handheld_disabled.png
new file mode 100644
index 000000000..53f8ce7ad
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_midnight.png b/dist/icons/controller/applet_handheld_midnight.png
new file mode 100644
index 000000000..7188b9154
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_midnight_disabled.png b/dist/icons/controller/applet_handheld_midnight_disabled.png
new file mode 100644
index 000000000..6ccfc3253
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller.png b/dist/icons/controller/applet_pro_controller.png
new file mode 100644
index 000000000..e32258855
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_dark.png b/dist/icons/controller/applet_pro_controller_dark.png
new file mode 100644
index 000000000..c122b7b11
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_dark_disabled.png b/dist/icons/controller/applet_pro_controller_dark_disabled.png
new file mode 100644
index 000000000..416e1e2fb
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_disabled.png b/dist/icons/controller/applet_pro_controller_disabled.png
new file mode 100644
index 000000000..72a549ea9
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_midnight.png b/dist/icons/controller/applet_pro_controller_midnight.png
new file mode 100644
index 000000000..622e57fa1
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_midnight_disabled.png b/dist/icons/controller/applet_pro_controller_midnight_disabled.png
new file mode 100644
index 000000000..2907f3be4
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left.png b/dist/icons/controller/applet_single_joycon_left.png
new file mode 100644
index 000000000..47c44d43a
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_dark.png b/dist/icons/controller/applet_single_joycon_left_dark.png
new file mode 100644
index 000000000..69530b69c
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_dark_disabled.png b/dist/icons/controller/applet_single_joycon_left_dark_disabled.png
new file mode 100644
index 000000000..cfe4b1475
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_disabled.png b/dist/icons/controller/applet_single_joycon_left_disabled.png
new file mode 100644
index 000000000..c0102dc20
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_midnight.png b/dist/icons/controller/applet_single_joycon_left_midnight.png
new file mode 100644
index 000000000..56522bccb
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png b/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png
new file mode 100644
index 000000000..62434c188
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right.png b/dist/icons/controller/applet_single_joycon_right.png
new file mode 100644
index 000000000..b0d4fba90
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_dark.png b/dist/icons/controller/applet_single_joycon_right_dark.png
new file mode 100644
index 000000000..af26cce4b
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_dark_disabled.png b/dist/icons/controller/applet_single_joycon_right_dark_disabled.png
new file mode 100644
index 000000000..b33efab42
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_disabled.png b/dist/icons/controller/applet_single_joycon_right_disabled.png
new file mode 100644
index 000000000..01ca38322
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_midnight.png b/dist/icons/controller/applet_single_joycon_right_midnight.png
new file mode 100644
index 000000000..5bf70e21e
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png b/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png
new file mode 100644
index 000000000..e6693b027
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/controller.qrc b/dist/icons/controller/controller.qrc
index f44725d8f..78eae461c 100644
--- a/dist/icons/controller/controller.qrc
+++ b/dist/icons/controller/controller.qrc
@@ -1,25 +1,34 @@
<RCC>
<qresource prefix="controller">
- <file alias="dual_joycon">dual_joycon.png</file>
- <file alias="dual_joycon_dark">dual_joycon_dark.png</file>
- <file alias="dual_joycon_midnight">dual_joycon_midnight.png</file>
- <file alias="handheld">handheld.png</file>
- <file alias="handheld_dark">handheld_dark.png</file>
- <file alias="handheld_midnight">handheld_midnight.png</file>
- <file alias="pro_controller">pro_controller.png</file>
- <file alias="pro_controller_dark">pro_controller_dark.png</file>
- <file alias="pro_controller_midnight">pro_controller_midnight.png</file>
- <file alias="single_joycon_left">single_joycon_left.png</file>
- <file alias="single_joycon_left_dark">single_joycon_left_dark.png</file>
- <file alias="single_joycon_left_midnight">single_joycon_left_midnight.png</file>
- <file alias="single_joycon_right">single_joycon_right.png</file>
- <file alias="single_joycon_right_dark">single_joycon_right_dark.png</file>
- <file alias="single_joycon_right_midnight">single_joycon_right_midnight.png</file>
- <file alias="single_joycon_left_vertical">single_joycon_left_vertical.png</file>
- <file alias="single_joycon_left_vertical_dark">single_joycon_left_vertical_dark.png</file>
- <file alias="single_joycon_left_vertical_midnight">single_joycon_left_vertical_midnight.png</file>
- <file alias="single_joycon_right_vertical">single_joycon_right_vertical.png</file>
- <file alias="single_joycon_right_vertical_dark">single_joycon_right_vertical_dark.png</file>
- <file alias="single_joycon_right_vertical_midnight">single_joycon_right_vertical_midnight.png</file>
+ <file alias="applet_dual_joycon">applet_dual_joycon.png</file>
+ <file alias="applet_dual_joycon_dark">applet_dual_joycon_dark.png</file>
+ <file alias="applet_dual_joycon_midnight">applet_dual_joycon_midnight.png</file>
+ <file alias="applet_handheld">applet_handheld.png</file>
+ <file alias="applet_handheld_dark">applet_handheld_dark.png</file>
+ <file alias="applet_handheld_midnight">applet_handheld_midnight.png</file>
+ <file alias="applet_pro_controller">applet_pro_controller.png</file>
+ <file alias="applet_pro_controller_dark">applet_pro_controller_dark.png</file>
+ <file alias="applet_pro_controller_midnight">applet_pro_controller_midnight.png</file>
+ <file alias="applet_joycon_left">applet_single_joycon_left.png</file>
+ <file alias="applet_joycon_left_dark">applet_single_joycon_left_dark.png</file>
+ <file alias="applet_joycon_left_midnight">applet_single_joycon_left_midnight.png</file>
+ <file alias="applet_joycon_right">applet_single_joycon_right.png</file>
+ <file alias="applet_joycon_right_dark">applet_single_joycon_right_dark.png</file>
+ <file alias="applet_joycon_right_midnight">applet_single_joycon_right_midnight.png</file>
+ <file alias="applet_dual_joycon_disabled">applet_dual_joycon_disabled.png</file>
+ <file alias="applet_dual_joycon_dark_disabled">applet_dual_joycon_dark_disabled.png</file>
+ <file alias="applet_dual_joycon_midnight_disabled">applet_dual_joycon_midnight_disabled.png</file>
+ <file alias="applet_handheld_disabled">applet_handheld_disabled.png</file>
+ <file alias="applet_handheld_dark_disabled">applet_handheld_dark_disabled.png</file>
+ <file alias="applet_handheld_midnight_disabled">applet_handheld_midnight_disabled.png</file>
+ <file alias="applet_pro_controller_disabled">applet_pro_controller_disabled.png</file>
+ <file alias="applet_pro_controller_dark_disabled">applet_pro_controller_dark_disabled.png</file>
+ <file alias="applet_pro_controller_midnight_disabled">applet_pro_controller_midnight_disabled.png</file>
+ <file alias="applet_joycon_left_disabled">applet_single_joycon_left_disabled.png</file>
+ <file alias="applet_joycon_left_dark_disabled">applet_single_joycon_left_dark_disabled.png</file>
+ <file alias="applet_joycon_left_midnight_disabled">applet_single_joycon_left_midnight_disabled.png</file>
+ <file alias="applet_joycon_right_disabled">applet_single_joycon_right_disabled.png</file>
+ <file alias="applet_joycon_right_dark_disabled">applet_single_joycon_right_dark_disabled.png</file>
+ <file alias="applet_joycon_right_midnight_disabled">applet_single_joycon_right_midnight_disabled.png</file>
</qresource>
</RCC>
diff --git a/dist/icons/controller/dual_joycon.png b/dist/icons/controller/dual_joycon.png
deleted file mode 100644
index 4230f5f7b..000000000
--- a/dist/icons/controller/dual_joycon.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/dual_joycon_dark.png b/dist/icons/controller/dual_joycon_dark.png
deleted file mode 100644
index 4445db489..000000000
--- a/dist/icons/controller/dual_joycon_dark.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/dual_joycon_midnight.png b/dist/icons/controller/dual_joycon_midnight.png
deleted file mode 100644
index aac8e5321..000000000
--- a/dist/icons/controller/dual_joycon_midnight.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/handheld.png b/dist/icons/controller/handheld.png
deleted file mode 100644
index d009b4a47..000000000
--- a/dist/icons/controller/handheld.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/handheld_dark.png b/dist/icons/controller/handheld_dark.png
deleted file mode 100644
index c80ca9259..000000000
--- a/dist/icons/controller/handheld_dark.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/handheld_midnight.png b/dist/icons/controller/handheld_midnight.png
deleted file mode 100644
index 19de4629b..000000000
--- a/dist/icons/controller/handheld_midnight.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/pro_controller.png b/dist/icons/controller/pro_controller.png
deleted file mode 100644
index 07d65e94a..000000000
--- a/dist/icons/controller/pro_controller.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/pro_controller_dark.png b/dist/icons/controller/pro_controller_dark.png
deleted file mode 100644
index 73efe18f4..000000000
--- a/dist/icons/controller/pro_controller_dark.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/pro_controller_midnight.png b/dist/icons/controller/pro_controller_midnight.png
deleted file mode 100644
index 8d7e63f0d..000000000
--- a/dist/icons/controller/pro_controller_midnight.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left.png b/dist/icons/controller/single_joycon_left.png
deleted file mode 100644
index 547153034..000000000
--- a/dist/icons/controller/single_joycon_left.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_dark.png b/dist/icons/controller/single_joycon_left_dark.png
deleted file mode 100644
index b6ee073cb..000000000
--- a/dist/icons/controller/single_joycon_left_dark.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_midnight.png b/dist/icons/controller/single_joycon_left_midnight.png
deleted file mode 100644
index 34a485c81..000000000
--- a/dist/icons/controller/single_joycon_left_midnight.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_vertical.png b/dist/icons/controller/single_joycon_left_vertical.png
deleted file mode 100644
index 1e6282ad8..000000000
--- a/dist/icons/controller/single_joycon_left_vertical.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_vertical_dark.png b/dist/icons/controller/single_joycon_left_vertical_dark.png
deleted file mode 100644
index a615d995d..000000000
--- a/dist/icons/controller/single_joycon_left_vertical_dark.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_left_vertical_midnight.png b/dist/icons/controller/single_joycon_left_vertical_midnight.png
deleted file mode 100644
index 4cc578216..000000000
--- a/dist/icons/controller/single_joycon_left_vertical_midnight.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right.png b/dist/icons/controller/single_joycon_right.png
deleted file mode 100644
index 8d29173f6..000000000
--- a/dist/icons/controller/single_joycon_right.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_dark.png b/dist/icons/controller/single_joycon_right_dark.png
deleted file mode 100644
index ead2c44e0..000000000
--- a/dist/icons/controller/single_joycon_right_dark.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_midnight.png b/dist/icons/controller/single_joycon_right_midnight.png
deleted file mode 100644
index 89afe022d..000000000
--- a/dist/icons/controller/single_joycon_right_midnight.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_vertical.png b/dist/icons/controller/single_joycon_right_vertical.png
deleted file mode 100644
index 4d7d06547..000000000
--- a/dist/icons/controller/single_joycon_right_vertical.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_vertical_dark.png b/dist/icons/controller/single_joycon_right_vertical_dark.png
deleted file mode 100644
index 9a6eb3013..000000000
--- a/dist/icons/controller/single_joycon_right_vertical_dark.png
+++ /dev/null
Binary files differ
diff --git a/dist/icons/controller/single_joycon_right_vertical_midnight.png b/dist/icons/controller/single_joycon_right_vertical_midnight.png
deleted file mode 100644
index 685249b68..000000000
--- a/dist/icons/controller/single_joycon_right_vertical_midnight.png
+++ /dev/null
Binary files differ
diff --git a/dist/languages/de.ts b/dist/languages/de.ts
new file mode 100644
index 000000000..b93c9f51c
--- /dev/null
+++ b/dist/languages/de.ts
@@ -0,0 +1,4749 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="de" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Über yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu ist ein experimenteller, quelloffener Emulator für Nintendo Switch, lizensiert unter GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Diese Software sollte nicht dazu verwendet werden, Spiele zu spielen, die du nicht legal erhalten hast.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Webseite&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Quellcode&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Mitwirkende&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Lizenz&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; ist ein Warenzeichen von Nintendo. yuzu ist in keiner Weise mit Nintendo verbunden.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Verbindung mit dem Server wird hergestellt...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Abbrechen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Tippe auf die obere linke Ecke &lt;br&gt;deines Touchpads.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Tippe auf die untere rechte Ecke &lt;br&gt;deines Touchpads.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Konfiguration abgeschlossen!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Kompatibilität melden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Kompatibilität des Spiels melden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Solltest du einen Bericht zur &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu-Kompatibilitätsliste&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt; beitragen wollen, werden die folgenden Informationen gesammelt und auf der yuzu-Webseite dargestellt:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware-Informationen (CPU / GPU / Betriebssystem)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Welche yuzu-Version du benutzt&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Den verbundenen yuzu-Account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfekt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel funktioniert einwandfrei und ohne Audio- oder Grafikfehler.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Gut</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel funktioniert mit kleineren Grafik- oder Audiofehlern und ist von Anfang bis Ende spielbar. Eventuell sind einige Workarounds erforderlich.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Okay</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel funktioniert mit größeren Grafik- oder Audiofehlern, lässt sich aber mit Workarounds bis zum Ende durchspielen. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Schlecht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Spiel funktioniert zwar, aber nur mit starken Grafik- oder Audiofehlern. Manche Bereiche des Spiels können selbst mit Workarounds nicht abgeschlossen werden.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menü</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel ist wegen schwerwiegendsten Grafik- oder Audiofehlern unspielbar. Das Spiel lässt sich lediglich starten.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Startet nicht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Das Spiel stürzt beim Versuch zu starten ab.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Unabhängig von Geschwindigkeit oder Performance, wie gut läuft das Spiel von Start bis Ende in dieser Version von yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Vielen Dank für deinen Beitrag!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Absenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Kommunikationsfehler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Beim Senden des Berichtes ist ein Fehler aufgetreten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Weiter</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Ausgabe-Engine:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Dieser Nachbearbeitungseffekt passt die Audiogeschwindigkeit an die Emulationsgeschwindigkeit an und verhindert Audioaussetzer. Dies erhöht jedoch die Audioverzögerung.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Audiodehnung aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Audiogerät:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Globale Lautstärke verwenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Lautstärke:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Lautstärke:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Allgemeines</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>Genauigkeit der Emulation:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>Akkurat</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Unsicher</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Debug-Modus aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>Wir empfehlen, dies auf &quot;Akkurat&quot; zu stellen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Unsichere CPU-Optimierungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Diese Optionen reduzieren die Genauigkeit, können jedoch die Geschwindigkeit erhöhen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Unfuse FMA (erhöht Leistung auf CPUs ohne FMA)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Diese Option steigert die Geschwindigkeit, indem die Genauigkeit von sog. &quot;fused-multiply-add&quot; Anweisungen auf CPUs ohne native FMA-Unterstützung reduziert wird.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>Schnelleres FRSQRTE und FRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Diese Option steigert die Geschwindigkeit von einigen &quot;floating point&quot;-Anweisungen, indem weniger akkurate Schätzungen der Werte verwendet werden.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Die CPU-Einstellungen sind nur verfügbar, wenn kein Spiel aktiv ist.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>Debug-Modus für CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>Der Debug-Modus ist nur für Entwickler gedacht. Bist du dir sicher?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>CPU-Optimierungen ändern</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;
+ &lt;b&gt;Dies ist nur zur Fehlerbehebung gedacht .&lt;/b&gt;
+ &lt;br&gt;
+ Falls du dir nicht sicher bist, was hier passiert, solltest du keine dieser Optionen ändern.
+ &lt;br&gt;
+ Diese Optionen treten nur dann in Kraft, wenn auch der CPU Debug-Modus aktiviert ist.
+ &lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>Enable inline page tables</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>Enable block linking</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>Return stack buffer aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>Enable fast dispatcher</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>Enable context elimination</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>Enable constant propagation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>Enable miscellaneous optimizations</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>Enable misalignment check reduction</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Die CPU-Einstellungen sind nur verfügbar, wenn kein Spiel aktiv ist.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>GDB-Stub aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Port:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Logging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Globaler Log-Filter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Log-Konsole öffnen (nur Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Log-Verzeichnis öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>String-Argumente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>Grafik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>Wenn aktiviert, wird die Grafik-API in einem langsamen Debug-Modus gestartet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>Grafik-Debugging aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>Diese Option deaktiviert den Macro-JIT-Compiler. Dies wird die Geschwindigkeit verringern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>Macro-JIT deaktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>Speichern</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Ausführliche Service-Fehlerberichte aktivieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Dies wird automatisch beim Schließen von yuzu zurückgesetzt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Kiosk(Quest)-Modus</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>Debug-Controller einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>Standardwerte</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>yuzu-Konfiguration</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Allgemein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>Benutzeroberfläche</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Spieleliste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>System</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Nutzer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Dateisystem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Steuerung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Hotkeys</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Debug</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Grafik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>GraphicsAdvanced</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Services</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Speicherverzeichnisse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SD-Karte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Gamecard</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Pfad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Eingelegt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Aktuelles Spiel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Patchmanager</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Dekomprimierte NSOs dumpen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>ExeFS dumpen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Mod-Ladeverzeichnis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Root dumpen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Caching</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Cache-Ordner</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Metadaten der Spieleliste cachen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Metadaten-Cache zurücksetzen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Emulierten NAND-Ordner auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Emulierten SD-Ordner auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Gamecard-Pfad auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Dump-Verzeichnis auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Mod-Ladeverzeichnis auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Cache-Ordner auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>Der Metadaten-Cache ist bereits leer.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>Der Vorgang wurde erfolgreich abgeschlossen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Der Metadaten-Cache konnte nicht gelöscht werden. Er könnte in Gebrauch oder nicht vorhanden sein.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Allgemein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Geschwindigkeit auf % festlegen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Multicore-CPU-Emulation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Schließen des Emulators bestätigen, falls ein Spiel läuft</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Beim Spielstart nach Nutzer fragen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Emulation im Hintergrund pausieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>Mauszeiger verstecken</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>API-Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Gerät:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Grafik-Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Nutze Festplatten-Shader-Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Asynchrone GPU-Emulation verwenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>Seitenverhältnis:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Standard (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>4:3 erzwingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>21:9 erzwingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>Auf Fenster anpassen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>Globale Hintergrundfarbe verwenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Hintergrundfarbe:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Hintergrundfarbe:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>OpenGL GPU</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>Erweiterte Grafik-Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>Genauigkeit der Emulation:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>VSync verhindert Screen-Tearing, aber manche Grafikkarten haben eine schlechtere Leistung, wenn es aktiviert ist. Wenn du keinen Unterschied merkst, lasse es aktiviert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>VSync nutzen (Nur OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>Aktivieren dieser Einstellung reduziert Stottern durch Shader. Aktiviert OpenGL Assembly-Shader auf unterstützten Nvidia-Karten (NV_gpu_program5 wird benötigt). Dieses Feature ist experimentell.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>Nutze Assembly-Shader (experimentell, nur Nvidia OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>Nutze asynchrone Shader-Kompilierung. Dies kann Stottern durch Shader reduzieren. Dieses Feature ist experimentell.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>Nutze asynchrone Shader-Kompilierung (experimentell)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>Nutze schnelle GPU-Zeit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>Anisotrope Filterung:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>Standard</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Hotkey-Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Doppelklicke auf eine Tastensequenz, um sie zu ändern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>Alle löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>Standardwerte wiederherstellen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Aktion</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Hotkey</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Kontext</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Tastensequenz bereits belegt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>Die eingegebene Sequenz ist bereits vergeben an: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>Standardwerte wiederherstellen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>Die Standard-Sequenz ist bereits vergeben an: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>ConfigureInput</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Spieler 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Spieler 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Spieler 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Spieler 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Spieler 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Spieler 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Spieler 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Spieler 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Konsolenmodus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Im Dock</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Handheld</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Vibration</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>Bewegung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Konfigurieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Verbunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Standardwerte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>Löschen</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Eingabe einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Joyconfarben</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Spieler 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>Links</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>L-Taste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>Rechts</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>R-Taste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Spieler 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Spieler 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Spieler 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Spieler 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Spieler 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Spieler 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Spieler 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Weiteres</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Tastatur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Erweitert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Maus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>Bewegung / Touch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Konfigurieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Debug Controller</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Eingabe einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Controller verbinden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>Zwei Joycons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>Linker Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>Rechter Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>Handheld</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>Eingabegerät</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>Alle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>Tastatur/Maus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>Profil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Speichern</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Neu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Linker Analogstick</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Hoch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Links</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Rechts</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Runter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Gedrückt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modifikator</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>Radius</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>Deadzone: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Modifikator-Radius: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>Steuerkreuz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Minus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Screenshot</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Tasten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Rechter Analogstick</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>Deadzone: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Modifikator-Radius: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[wartet]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Bewegung / Touch einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>Bewegung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>Quelle für Bewegung:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Empfindlichkeit:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Touch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>Quelle für Touch:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Kalibrierung:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Einrichtung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>Tastenbelegung nutzen:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>CemuhookUDP Konfiguration</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>Du kannst alle Cemuhook-kompatiblen UDP-Eingabequellen für Bewegung und Touch verwenden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>Server:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>Port:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Pad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>Pad 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>Pad 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>Pad 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>Pad 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>Mehr erfahren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>Testen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>Maus (Rechtsklick)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>Emulator-Fenster</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Mehr erfahren&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>Testen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>Einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>Test erfolgreich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation>Daten wurden erfolgreich vom Server empfangen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>Test fehlgeschlagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>Konnte keine Daten vom Server empfangen.&lt;br&gt;Prüfe bitte, dass der Server korrekt eingerichtet wurde und dass Adresse und Port korrekt sind.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>UDP-Test oder Kalibration wird gerade durchgeführt.&lt;br&gt;Bitte warte einen Moment.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Maus einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Maustasten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Vorwärts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Rückwärts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Links:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Mitte:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Rechts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>Standardwerte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[nicht belegt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Standardwert wiederherstellen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[Taste drücken]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>Dialog</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>Info</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>Name</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>Titel ID</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>Dateiname</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>Format</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>Version</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>Größe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>Entwickler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>Add-Ons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>Allgemeines</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>System</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>Grafik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>Erw. Grafik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>Globale Konfiguration verwenden (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>Patchname</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>Version</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Nutzerverwaltung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Aktueller Nutzer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nutzername</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Bild wählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Hinzufügen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Umbenennen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Die Nutzerverwaltung ist nur verfügbar, wenn kein Spiel aktiv ist.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Nutzername eingeben</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Nutzer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Gib einen Benutzernamen für den neuen Benutzer ein:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Gib einen neuen Nutzernamen ein:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Löschen bestätigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Du bist dabei, den Nutzer &quot;%1&quot; zu löschen. Bist du dir sicher?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Profilbild wählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG Bilddateien (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Fehler beim Löschen des Bildes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Fehler beim Überschreiben des vorherigen Bildes bei: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Fehler beim Löschen der Datei</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Konnte die bestehende Datei &quot;%1&quot; nicht löschen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Fehler beim Erstellen des Ordners für die Profilbilder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Konnte Ordner &quot;%1&quot; nicht erstellen, um Profilbilder zu speichern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Fehler beim Kopieren des Profilbildes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Das Bild konnte nicht von &quot;%1&quot; nach &quot;%2&quot; kopiert werden</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT ist Nintendos Methode, Daten an Spiele zu senden, um die Community zu involvieren und zusätzliche Inhalte freizuschalten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT Backend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Erfahre mehr über BCAT, Boxcat, und aktuelle Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Der Boxcat Service ist offline oder du bist nicht mit dem Internet verbunden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Bei der Verarbeitung der Boxcat-Eventdaten ist ein Fehler aufgetreten. Kontaktiere die yuzu-Entwickler.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>Die Version von yuzu, die du verwendest, ist entweder zu neu oder zu alt für den Server. Versuche auf die neueste offizielle Version von yuzu zu updaten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Es gibt zurzeit keine Events auf Boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>Momentane Boxcat-Events</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu lädt den neuesten Boxcat-Status...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Systemeinstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>Region:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>Auto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>Standard</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>Kuba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>Ägypten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>GB-Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>Greenwich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>Hongkong</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>Island</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>Iran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>Israel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>Jamaika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>Japan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>Kwajalein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>Libyen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>Navajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>Polen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>Portugal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>Singapur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>Türkei</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>Universal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>Zulu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>USA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>Europa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>Australien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>China</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>Korea</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>Zeitzone:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Anmerkung: Diese Einstellung kann überschrieben werden, falls deine Region auf &quot;auto-select&quot; eingestellt ist.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japanisch (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Englisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Französisch (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Deutsch (German)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italienisch (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Spanisch (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinesisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Koreanisch (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Niederländisch (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugiesisch (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russisch (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanesisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Britisches Englisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Kanadisches Französisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Lateinamerikanisches Spanisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Vereinfachtes Chinesisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Traditionelles Chinesisch (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>Benutzerdefinierte Echtzeituhr</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Sprache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>RNG Seed</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>Konsolen ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Soundausgabe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Neu generieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Die Systemeinstellungen sind nur verfügbar, wenn kein Spiel aktiv ist.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Dieser Vorgang wird deine momentane &quot;virtuelle Switch&quot; mit einer Neuen ersetzen. Deine momentane &quot;virtuelle Switch&quot; wird nicht wiederherstellbar sein. Dies könnte einige unerwartete Effekte in manchen Spielen mit sich bringen. Zudem könnte der Prozess fehlschlagen, wenn zu alte Daten verwendet werden. Möchtest du den Vorgang fortsetzen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Warnung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Konsolen ID: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>Touchscreen-Belegung einrichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>Belegung:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Neu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Umbenennen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>Klicke das untere Feld um einen Punkt hinzuzufügen und drücke dann eine Taste, die diesen Punkt auslösen soll.
+Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf die Tabelle, um die Belegung des Punktes zu ändern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Punkt löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>Knopf</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>Neues Profil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Neuen Namen für das Profil eingeben.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>Profil löschen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>Profil &quot;%1&quot; löschen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>Profil umbenennen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>Neuer Name:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[Knopf drücken]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Touchscreen einrichten:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Achtung: Die Einstellungen auf dieser Seite haben Einfluss auf yuzus Touchscreen-Emulation. Sie zu ändern könnte zu unerwünschtem Verhalten, wie zu einem Ausfall des Touchscreens, führen. Du solltest sie nur verändern, falls du weißt, was du tust.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Berührungsparameter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Berührungsdurchmesser Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Finger</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Berührungsdurchmesser X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Drehwinkel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Standardwerte wiederherstellen</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Allgemein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Anmerkung: Das Ändern der Sprache wird deine Konfiguration speichern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Sprache der Benutzeroberfläche:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Theme:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Spieleliste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Add-On Spalte anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Icongröße:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Zeile 1 Text:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Zeile 2 Text:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>Screenshots</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>Frage nach, wo Screenshots gespeichert werden sollen (Nur Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>Screenshotpfad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>Screenshotpfad auswählen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Englisch</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Web Service</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Mit dem Bereitstellen deines Benutzernamens und Tokens erlaubst du yuzu, zusätzliche Nutzungsdaten zu sammeln. Diese könnten auch Informationen beinhalten, die dich identifizieren könnten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Überprüfen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Registrieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nutzername: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Was ist mein Token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Teile anonyme Nutzungsdaten mit dem yuzu-Team</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Mehr erfahren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>Telemetrie-ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Neu generieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discord-Präsenz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Zeig dein momentanes Spiel in deinem Discord-Status</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Mehr erfahren&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Registrieren&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Was ist mein Token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>Telemetrie-ID: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Nicht spezifiziert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token nicht verifiziert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Token wurde nicht verfiziert. Die Änderungen an deinem Token wurden nicht gespeichert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verifizieren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verifizierung fehlgeschlagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verifizierung fehlgeschlagen. Prüfe ob dein Nutzername und Token richtig eingegeben wurden und ob deine Internetverbindung korrekt funktioniert.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonyme Daten werden gesammelt,&lt;/a&gt; um yuzu zu verbessern.&lt;br/&gt;&lt;br/&gt;Möchstest du deine Nutzungsdaten mit uns teilen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Textprüfung fehlgeschlagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Lade Web-Applet...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Web-Applet verlassen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Verlassen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Um das Web-Applet zu verlassen, verwende die im Spiel enthaltenen Steuerelemente, wähle die die Option &apos;Web-Applet beenden&apos; in der Menüleiste oder drücke die &apos;Enter&apos;-Taste.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web-Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Diese Version von yuzu wurde ohne QtWebEngine-Unterstützung kompiliert, was bedeutet, dass yuzu das angeforderte Spielhandbuch oder die Webseite nicht richtig anzeigen kann.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>Wie viele Shader im Moment kompiliert werden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Derzeitige Emulations-Geschwindigkeit. Werte höher oder niedriger als 100% zeigen, dass die Emulation scheller oder langsamer läuft als auf einer Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Wie viele Bilder pro Sekunde angezeigt werden variiert von Spiel zu Spiel und von Szene zu Szene. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Zeit, die gebraucht wurde, um einen Switch-Frame zu emulieren, ohne Framelimit oder V-Sync. Für eine Emulation bei voller Geschwindigkeit sollte dieser Wert bei höchstens 16.67ms liegen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>DOCK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>ASYNC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>MEHRKERN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Zuletzt geladene Dateien leeren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Warnung veraltetes Spielformat</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Du nutzt eine entpackte ROM-Ordnerstruktur für dieses Spiel, welches ein veraltetes Format ist und von anderen Formaten wie NCA, NAX, XCI oder NSP überholt wurde. Entpackte ROM-Ordner unterstützen keine Icons, Metadaten oder Updates.&lt;br&gt;&lt;br&gt;&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;Unser Wiki&lt;/a&gt; enthält eine Erklärung der verschiedenen Formate, die yuzu unterstützt. Diese Nachricht wird nicht noch einmal angezeigt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>ROM konnte nicht geladen werden!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>ROM-Format wird nicht unterstützt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Beim Initialisieren des Video-Kerns ist ein Fehler aufgetreten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>Beim Laden des Video-Kerns trat ein Fehler auf. Bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen. Weitere Informationen: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Wie kann ich eine Log-Datei hochladen?&lt;/a&gt;. Stelle sicher, dass die aktuellsten Grafiktreiber für deine Grafikkarte installiert sind.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>Fehler beim Laden des ROMs!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Ein unbekannter Fehler ist aufgetreten. Bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Speicherdaten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Mod-Daten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Konnte Verzeichnis %1 nicht öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Verzeichnis existiert nicht!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Fehler beim Öffnen des transferierbaren Shader-Caches</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Es existiert kein Shader-Cache für diesen Titel.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>Inhalte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>Update</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>Eintrag entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>Installiertes Spiel %1 entfernen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>Erfolgreich entfernt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>Das Spiel wurde entfernt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>Fehler beim Entfernen von %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>Das Spiel ist nicht im NAND installiert und kann somit nicht entfernt werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>Das Update wurde entfernt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>Es ist kein Update für diesen Titel installiert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>Es sind keine DLC für diesen Titel installiert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>%1 DLC entfernt. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>Transferierbaren Shader-Cache entfernen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>Spiel-Einstellungen entfernen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>Datei entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>Fehler beim Entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>Der transferierbare Shader-Cache wurde entfernt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>Konnte den transferierbaren Shader-Cache nicht entfernen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>Fehler beim Entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>Es existieren keine Spiel-Einstellungen für dieses Spiel.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>Die Spiel-Einstellungen wurden entfernt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>Die Spiel-Einstellungen konnten nicht entfernt werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>RomFS-Extraktion fehlgeschlagen!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Das RomFS konnte wegen eines Fehlers oder Abbruchs nicht kopiert werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Komplett</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Nur Ordnerstruktur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>RomFS Extraktions-Modus auswählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Bitte wähle, wie das RomFS gespeichert werden soll.&lt;br&gt;&quot;Full&quot; wird alle Dateien des Spiels extrahieren, während &lt;br&gt;&quot;Skeleton&quot; nur die Ordnerstruktur erstellt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>RomFS wird extrahiert...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Abbrechen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>RomFS wurde extrahiert!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>Der Vorgang wurde erfolgreich abgeschlossen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Fehler beim Öffnen von %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Verzeichnis auswählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Einstellungen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Spiel-Einstellungen konnten nicht geladen werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Switch-Programme (%1);;Alle Dateien (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Datei laden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Öffne das extrahierte ROM-Verzeichnis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Ungültiges Verzeichnis ausgewählt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Das Verzeichnis, das du ausgewählt hast, enthält keine &apos;main&apos;-Datei.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>Installierbares Switch-Programm (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge Image (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>Dateien installieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Datei &quot;%1&quot; wird installiert...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>NAND-Installation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Systemanwendung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Systemarchiv</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Systemanwendungsupdate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Firmware-Paket (Typ A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Firmware-Paket (Typ B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Spiel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Spiel-Update</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>Spiel-DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Delta-Titel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Wähle den NCA-Installationstyp aus...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Bitte wähle, als was diese NCA installiert werden soll:
+(In den meisten Fällen sollte die Standardeinstellung &apos;Spiel&apos; ausreichen.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Installation fehlgeschlagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Der Titel-Typ, den du für diese NCA ausgewählt hast, ist ungültig.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Datei nicht gefunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Datei &quot;%1&quot; nicht gefunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Fortsetzen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Fehleranzeige</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Fehlender yuzu-Account</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Um einen Kompatibilitätsbericht abzuschicken, musst du einen yuzu-Account mit yuzu verbinden.&lt;br&gt;&lt;br/&gt;Um einen yuzu-Account zu verbinden, prüfe die Einstellungen unter Emulation &amp;gt; Konfiguration &amp;gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>Fehler beim Öffnen der URL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>URL &quot;%1&quot; kann nicht geöffnet werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Amiibo-Datei (%1);; Alle Dateien (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Amiibo laden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Fehler beim Öffnen der Amiibo Datei</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Die Amiibo Datei &quot;%1&quot; konnte nicht zum Lesen geöffnet werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Fehler beim Lesen der Amiibo-Daten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Amiibo-Daten können nicht vollständig gelesen werden. Es wurde erwartet, dass %1 Bytes gelesen werden, es konnten aber nur %2 Bytes gelesen werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Fehler beim Laden der Amiibo-Daten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Amiibo-Daten konnten nicht geladen werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Screenshot aufnehmen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG Bild (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Geschwindigkeit: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Geschwindigkeit: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Spiel: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Frame: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Das Spiel, dass du versuchst zu spielen, benötigt bestimmte Dateien von deiner Switch-Konsole.&lt;br/&gt;&lt;br/&gt;Um Informationen darüber zu erhalten, wie du diese Dateien von deiner Switch extrahieren kannst, prüfe bitte die folgenden Wiki-Seiten: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;System-Archive und Shared Fonts von einer Switch-Konsole extrahieren&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Willst du zur Spiele-Liste zurückkehren und die Emulation beenden? Das Fortsetzen der Emulation könnte zu Spielfehlern, Abstürzen, beschädigten Speicherdaten und anderen Fehlern führen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu konnte ein Switch Systemarchiv nicht finden. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu konnte ein Switch Systemarchiv nicht finden: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Systemarchiv nicht gefunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Systemarchiv fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu konnte die Switch Shared Fonts nicht finden. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Shared Fonts nicht gefunden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Shared Font fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Schwerwiegender Fehler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Ein schwerwiegender Fehler ist aufgetreten, bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen. Weitere Informationen: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Wie kann ich eine Log-Datei hochladen&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Willst du zur Spiele-Liste zurückkehren und die Emulation beenden? Das Fortsetzen der Emulation könnte zu Spielfehlern, Abstürzen, beschädigten Speicherdaten und anderen Fehlern führen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Fataler Fehler aufgetreten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Schlüsselableitung bestätigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Du bist im Begriff, alle Schlüssel neu abzuleiten. Falls du nicht weißt, was das heißt oder was du hier tust, könnte dieser Prozess möglicherweise destruktiv sein. Bitte stelle sicher, dass du wirklich fortfahren willst und optional Sicherungen deiner Daten machst.
+
+Dieser Prozess wird die generierten Schlüsseldateien löschen und die Schlüsselableitung neu starten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>Fuses fehlen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation> - BOOT0 fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - BCPKG2-1-Normal-Main fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - PRODINFO fehlt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>Derivationskomponenten fehlen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>Einige Komponenten, die yuzu benötigt, um Schlüssel zu generieren, wurden nicht gefunden. &lt;br&gt;Bitte folge &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;dem yuzu Schnellstart-Guide&lt;/a&gt; um alle deine Schlüssel und Spiele zu übertragen.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Schlüssel werden abgeleitet...
+Dies könnte, je nach Leistung deines Systems, bis zu einer Minute dauern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Schlüsselableitung</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>RomFS wählen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Wähle, welches RomFS du speichern möchtest.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Bist du sicher, dass du yuzu beenden willst?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Bist du sicher, dass du die Emulation stoppen willst? Jeder nicht gespeicherte Fortschritt geht verloren.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>Die derzeit laufende Anwendung hat yuzu aufgefordert, sich nicht zu beenden.
+
+Möchtest du dies umgehen und sie trotzdem beenden?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL nicht verfügbar!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzu wurde nicht mit OpenGL-Unterstützung kompiliert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkan nicht verfügbar!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>yuzu wurde nicht mit Vulkan-Unterstützung kompiliert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>Fehler beim Initialisieren von OpenGL 4.3!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>Deine Grafikkarte unterstützt kein OpenGL 4.3, oder du hast nicht den neusten Treiber installiert.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>Fehler beim Initialisieren von OpenGL!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>Deine GPU unterstützt anscheinend nicht eine oder mehrere von yuzu benötigte OpenGL-Erweiterungen. Bitte stelle sicher, dass du den neusten Grafiktreiber für deine GPU installiert hast.&lt;br&gt;&lt;br&gt;Nicht unterstützte Erweiterungen:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Name</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Kompatibilität</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Add-ons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Dateityp</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Größe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Spielstand-Verzeichnis öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Mod-Verzeichnis öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Transferierbaren Shader-Cache öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>Entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>Installiertes Update entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>Alle installierten DLCs entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>Shader-Cache entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>Spiel-Einstellungen entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>Alle installierten Inhalte entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>RomFS speichern</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Title-ID in die Zwischenablage kopieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>GameDB-Eintrag öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Eigenschaften</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Unterordner scannen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Spieleverzeichnis entfernen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ Nach Oben</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ Nach Unten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Verzeichnis öffnen</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfekt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Das Spiel funktioniert fehlerfrei, ohne Audio- oder Grafikfehler, und sämtliche getestete Funktionalität funktioniert wie vorhergesehen ohne Workarounds.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Gut</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Das Spiel funktioniert mit kleineren Audio- oder Grafikfehlern und lässt sich bis zum Ende durchspielen.
+Eventuell sind einige Workarounds notwendig.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Okay</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Das Spiel funktioniert mit größern Audio- oder Grafikfehlern,
+lässt sich aber mit Workarounds bis zum Ende durchspielen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Schlecht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Spiel funktioniert zwar, aber nur mit starken Audio- oder Grafikfehlern. Manche Bereiche des Spiels können selbst mit Workarounds nicht abgeschlossen werden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menü</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Das Spiel ist wegen schwerwiegenden Audio- oder Grafikfehlern unspielbar. Das Spiel lässt sich lediglich starten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Startet nicht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Das Spiel stürzt beim Versuch zu starten ab.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Nicht getestet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Spiel wurde noch nicht getestet.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Doppelklicke, um einen neuen Ordner zur Spieleliste hinzuzufügen.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filter:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Wörter zum Filtern eingeben</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>Bitte bestätige, dass du diese Dateien installieren willst.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>Wenn du ein Update oder DLC installierst, wirst du die vorher installierten überschreiben.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>Installieren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>Dateien im NAND installieren</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Shader 387 / 1628 wird geladen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Shader %v / %m wird geladen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Geschätzte Zeit: 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Lädt...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Shader %1 / %2 wird geladen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Starten...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Geschätzte Zeit: %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Datei</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Letzte Dateien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Anzeige</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Debugging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Werkzeuge</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Hilfe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>Dateien im NAND installieren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Datei laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Verzeichnis laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>S&amp;chließen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pause</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Stop</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Schlüssel neu initialisieren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Über yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Einzelfenster-Modus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Einstellungen...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Dock-Widget-Header anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Filterleiste anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Statusleiste anzeigen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>Fenstergröße zurücksetzen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Vollbild</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Neustart</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Amiibo laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Kompatibilität berichten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>Mods-Seite öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>Schnellstart-Anleitung öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>FAQ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>yuzu-Verzeichnis öffnen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Screenshot aufnehmen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>Spiel-Einstellungen ändern...</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Installierte SD-Titel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Installierte NAND-Titel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Systemtitel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Neues Spieleverzeichnis hinzufügen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Strg</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[nicht gesetzt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Achse %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Taste %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[unbekannt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>Klick 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>Klick 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>Klick 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>Klick 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>Klick 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>GC Achse %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>GC Knopf %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[unbenutzt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Achse %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>GC Achse %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Ein Fehler ist aufgetreten.
+Bitte versuche es noch einmal oder kontaktiere den Entwickler der Software.
+
+Fehlercode: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Ein Fehler ist in %1 bei %2 aufgetreten.
+Bitte versuche es noch einmal oder kontaktiere den Entwickler der Software.
+
+Fehlercode: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Ein Fehler ist aufgetreten.
+Fehlercode: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Wähle einen Benutzer aus:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Nutzer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Profilauswahl</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Text eingeben:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Software-Tastatur</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Hotkey eingeben</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Stack aufrufen</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>Warten auf Mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>has waiters: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>owner handle: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>Warten auf alle Objekte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>Warten auf eines der folgenden Objekte</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>von keinem Thread pausiert</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>läuft</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>bereit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>pausiert</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>warten auf HLE Rückgabe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>schläft</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>Warten auf IPC-Antwort</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>Warten auf Objekte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>Warten auf Mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>wartet auf condition variable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>Warten auf den Adressarbiter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>ruhend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>tot</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>Kern %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Unbekannter Prozessor %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>Prozessor = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>ideal core = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>Affinitätsmaske = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>Thread-ID = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>Priorität = %1(aktuell) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>Letzte laufende Ticks = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>nicht auf Mutex warten</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>gewartet von Thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Wait Tree</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/es.ts b/dist/languages/es.ts
new file mode 100644
index 000000000..437817561
--- /dev/null
+++ b/dist/languages/es.ts
@@ -0,0 +1,4757 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="es" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Acerca de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu es un emulador experimental de código abierto de Nintendo Switch licenciado bajo GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Este software no debe ser utilizado para jugar juegos que no hayas obtenido legalmente.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Sitio web&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Código fuente&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contribuidor&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licencia&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; es una marca registrada de Nintendo. yuzu no esta afiliado con Nintendo de ninguna manera.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Comunicando con el servidor...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Toque la esquina superior izquierda&lt;br&gt;de su trackpad.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Ahora toque la esquina inferior derecha &lt;br&gt;de su trackpad.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>¡Configuración completa!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Informar de compatibilidad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Informar de compatibilidad del juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;SI decides presentar una prueba a la &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;Lista de Compatibilidad de yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, La siguiente información será obtenida y mostrada en el sitio web:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informacion de Hardware (CPU / GPU / Sistema Operativo)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Qué versión de yuzu estas utilizando&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;La cuenta de yuzu conectada&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfecto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego funciona a la perfección sin ningún problema gráfico o de audio.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Excelente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego funciona con fallos gráficos o de audio menores y es jugable desde el principio hasta el final. Puede requerir de soluciones temporales.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Bien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego funciona con importantes fallos gráficos o de audio, pero es jugable desde el principio hasta el final con soluciones temporales.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Mal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego funciona, pero tiene errores gráficos o de audio. Imposible progresar en ciertas áreas debido a errores incluso con soluciones temporales.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menú</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;No es posible jugar a este juego debido a importantes errores gráficos o de audio. Es imposible avanzar mas allá de la pantalla de inicio.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>No inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El juego se bloquea al intentar iniciar.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independientemente de la velocidad o del rendimiento, ¿cómo definiría su experiencia con el juego de principio a fin en esta versión de yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Gracias por su contribución.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Enviando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Error de comunicación.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Ha ocurrido un error mientras se mandaba el caso de prueba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Siguiente</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Motor de Salida:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Este efecto de post-procesado ajusta la velocidad del audio para igualarla a la velocidad de emulación y así prevenir parones en el audio. No obstante, esto aumenta la latencia del audio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Activar extensión del audio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Dispositivo de Audio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Usar el volumen global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Ajustar volumen:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volumen:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>Precisión: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>Preciso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Impreciso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Activar el Modo de Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>Recomendamos ajustar la precisión a &quot;Preciso&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Ajustes del Modo Impreciso de Optimización de la CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Estos ajustes reducen la precisión para mejorar el rendimiento.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Unfuse FMA (mejorar el rendimiendo en las CPU sin FMA)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta opción mejora el rendimiento al reducir la precisión de las instrucciónes fused-multiply-add en las CPU sin soporte nativo de FMA.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>Más rápido FRSQRTE y FRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta opción mejora el rendimiento de algunas funciones aproximadas de punto flotante al utilizar aproximaciones nativas menos precisas.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Los ajustes de la CPU sólo se encuentran disponibles cuando no se está ejecutando ningún juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>Ajustando la CPU al Modo de Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>El Modo de Depuración de la CPU sólo está destinado al uso de los desarrolladores. ¿Estás seguro de que quieres activar esto?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>Cambiar las optimizaciones de la CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;
+ &lt;b&gt;Sólo para la depuración.&lt;/b&gt;
+ &lt;br&gt;
+ Si no está seguro de lo que hacen estas opciónes, manténgalos todos activados.
+ &lt;br&gt;
+ Estos ajustes sólo toman efecto cuando la precisión de la CPU esta en &quot;Modo de Depuración&quot;.
+ &lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>Activar las tablas de páginas inline.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Esta optimización acelera los accesos a la memoria del programa del invitado.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Activando las inlines accede a PageTable::pointers en código emitido.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Desactivando esto obliga a que todos los accesos a la memoria pasen por las funciones Memory::Read/Memory::Write.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>Activar el enlace de bloques</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta optimización evita las búsquedas del despachador permitiendo que los bloques básicos emitidos salten directamente a otros bloques básicos si el PC de destino es estático.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>Activar el buffer de la pila de retorno</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta optimización evita las búsquedas de los despachadores al rastrear las direcciones potenciales de retorno de las instrucciones de BL. Esto se aproxima a lo que sucede con un buffer de la pila de retorno en una CPU real.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>Activar el despachador rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Habilitar un sistema de despacho de dos niveles. Un despachador más rápido escrito en ensamblado tiene un pequeño caché MRU de destinos de salto se utiliza primero. Si eso falla, el despacho vuelve al despacho más lento de C++.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>Activar la eliminación del contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esto habilita una optimización de IR que reduce los accesos innecesarios a la estructura del contexto de la CPU.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>Activar la propagación constante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esto habilita optimizaciones de IR que implican una propagación constante.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>Activar optimizaciones misceláneas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esto habilita optimizaciónes misceláneas IR.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>Activar la reducción del control de desalineación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Cuando está activada, una desalineación sólo se activa cuando un acceso cruza el límite de una página.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Cuando está desactivado, se desencadena una desalineación en todos los accesos desalineados.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Los ajustes de la CPU sólo se encuentran disponibles cuando no se está ejecutando ningún juego.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Activar GDB Stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Puerto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Registro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtro de Registro Global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Mostrar Consola del Registro (Sólo en Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Abrir Ubicación del Registro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Cadena de Argumentos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>Cuando está marcado, la API de gráficos entra en un modo de depuración más lento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>Activar la Depuración de Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>Cuando está marcado, se desactiva el compilador de macro Just In Time. Activando esto hace que los juegos se ejecuten más lento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>Desactivar Macro JIT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>Volcar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Habilitar Servicios de Reporte Detallados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Estas opciones se restablecerán automáticamente cuando yuzu se cierre.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avanzado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Modo Quiosco (Quest)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>Configurar el Control de Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>Valores Predeterminados</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configuración de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>IU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Perfiles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Sistema de Archivos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Teclas de Acceso Rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>Avanzado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>GráficosAvanzados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Servicios</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Directorios de Almacenamiento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Tarjeta SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartucho de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Ruta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Insertado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Juego Actual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Administrador de Parches</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Volcar NSOs Descomprimidos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Volcar Partición ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Carpeta raíz de carga de Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Carpeta raíz de Volcado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Cargando Caché</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Directorio de Caché</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Metadatos de Lista de Juegos en Caché</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Reiniciar Caché de Metadatos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Seleccione el directorio de NAND Emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Seleccione el directorio de SD Emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Seleccione la ruta del Cartucho...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Seleccione Directorio para Volcar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Seleccione el directorio de carga de Mod...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Seleccione el Directorio para Caché...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>El caché de metadatos ya está vacío.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>La operación se completó con éxito.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>El caché de metadatos no se pudo eliminar. Puede que se encuentre en uso actualmente o ya haya sido eliminado.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Limitar el Porcentaje de Velocidad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Emulación de CPU multinúcleo </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Confirmar salida mientras se ejecuta la emulación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Mostrar usuario actual al abrir el juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pausar emulación cuando la ventana esté en segundo plano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>Ocultar el cursor del ratón cuando no está activo.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>Ajustes de la API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Dispositivo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Ajustes de los Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Usar caché de shaders en disco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Usar emulación asíncrona de GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>Relación de Aspecto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Valor Predeterminado (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>Forzar a 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>Forzar a 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>Estirar a la Ventana</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>Usar el color de fondo global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Establecer el color de fondo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Color de Fondo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>Dispositivo Gráfico OpenGL:</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>Ajustes de los Gráficos Avanzados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>Nivel de Precisión: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>VSync evita que la pantalla se desgarre, pero algunas tarjetas gráficas tienen un rendimiento menor con VSync activado. Mantenlo activado si no notas una diferencia de rendimiento.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>Usar VSync (sólo en OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>Activando esto reduce el tartamudeo causado por la compilación de shaders. Activa shaders de ensamblaje OpenGL en los dispositivos de Nvidia soportados (se requiere NV_gpu_program5). Esta función es experimental.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>Usar shaders de ensamblaje (experimental, sólo Nvidia OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>Activa la compilación de shaders en modo asíncrono, cuál podría reducir el tartamudeo de los shaders. Esta función es experimental.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>Usar la construcción de shaders asíncronos (experimental)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>Usar Tiempo Rápido en la GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>Filtrado Anisotrópico:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>Valor Predeterminado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Configuración de Teclas de Acceso Rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Haz doble-clic para cambiar la asignación de teclas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>Eliminar Todo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurar Valores Predeterminados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Acción</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Tecla de Acceso Rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Secuencia de Teclas en Conflicto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>La secuencia de teclas introducida ya ha sido asignada a: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>Restaurar Valor Predeterminado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>Eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>La secuencia de teclas predeterminada ya ha sido asignada a: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>ConfigurarEntrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Jugador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Jugador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Jugador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Jugador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Jugador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Jugador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Jugador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Jugador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avanzado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Modo de la Consola</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Estacionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Portátil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Vibración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>Movimiento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>Controladores</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Conectado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Valores Predeterminados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>Eliminar</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Colores de Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Jugador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>Lado L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>Botón L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>Lado R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>Botón R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Jugador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Jugador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Jugador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Jugador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Jugador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Jugador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Jugador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Otro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Teclado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Avanzado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Pantalla Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Ratón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>Movimiento / Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Control de Depuración</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Conectar el Controlador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>Doble Joycons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>Joycon Izquierdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>Joycon Derecho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>Portátil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>Dispositivo de Entrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>Cualquier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>Teclado/Ratón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>Perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Guardar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Crear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Borrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Palanca Izquierda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Arriba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Izquierda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Derecha</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Abajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Presionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modificador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>Rango</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>Zona Muerta: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Rango del Modificador: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>Cruceta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Menos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Captura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Más</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Inicio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Botones Frontales</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Palanca Derecha</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>Zona Muerta: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Rango del Modificador: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[esperando]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Configurar: Movimiento / Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>Movimiento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>Proveedor de Movimiento:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Sensibilidad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>Proveedor de Táctil:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Calibración:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>Usar el mapeo de botones:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>Configuración CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>Puede utilizar cualquier fuente de entrada UDP compatible con Cemuhook para proporcionar una entrada de movimiento y de tacto.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>Servidor:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>Puerto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Pad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>Pad 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>Pad 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>Pad 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>Pad 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>Más Información</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>Probar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>Ratón (Clic Derecho)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>Ventana del Emulador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Más Información&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>Probando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>Configurando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>Prueba Existosa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation>Se recibió con éxito los datos del servidor.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>Prueba fallida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>No pudo recibir datos válidos del servidor.&lt;br&gt;Por favor, verifique que el servidor esté configurado correctamente y que la dirección y el puerto sean correctos.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>La prueba de UDP o la configuración de la calibración está en curso.&lt;br&gt;Por favor, espere a que termine el proceso.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configurar el Ratón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Botones del Ratón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Adelante:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Atrás:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Izquierda:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Medio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Derecha:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Borrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>Valores Predeterminados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[no establecido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Restaurar Valor Predeterminado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[pulsa un botón]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>Diálogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>Información</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>Nombre</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>ID del Título</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>Nombre del Archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>Versión</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>Tamaño</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>Desarrollador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>Complementos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>Gráficos Avanzados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>Propiedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>Usar la configuración global (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>Nombre del Parche</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>Versión</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Administrador de Perfiles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Usuario Actual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nombre de Usuario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Seleccionar Imagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Añadir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Renombrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>El sistema de perfiles solo se encuentra disponible cuando no se está ejecutando ningún juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Introduzca el Nombre de Usuario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Usuarios</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Introduzca un nombre para el nuevo usuario:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Introduzca un nuevo nombre de usuario:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation> Confirmar Eliminanación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Estás a punto de eliminar al usuario &quot;%1&quot; ¿Estás seguro?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Seleccione una Imagen de Usuario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Imagenes JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Error al eliminar la imagen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Ocurrió un error al intentar sobrescribir la imagen anterior en: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Error eliminando archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>No se pudo eliminar el archivo existente: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Error al crear el directorio de imagen del usuario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>No se puede crear el directorio % 1 para almacenar imágenes de usuario.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Error al copiar la imagen del usuario.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>No se puede copiar la imagen de %1 a %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT es la forma con la que Nintendo envía datos de sus juegos para así interconectar su comunidad y desbloquear contenido adicional.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>Backend de BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;Saber más sobre BCAT, Boxcat, y sus eventos actuales.&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>El servicio boxcat se encuentra fuera de linea o usted no está conectado a internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Hubo un error al intentar procesar los datos del evento de boxcat. Por favor, contacte con los desarrolladores de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>La versión de yuzu que está utilizando actualmente es demasiado nueva o demasiado antigua para este servidor. Intente actualizar a la última versión oficial de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>No hay ningún evento de boxcat en estos momentos.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>Eventos de Boxcat Actuales</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu está verificando el estado actual de boxcat...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Ajustes del sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>Región:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>Auto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>Valor Predeterminado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>Cuba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>Egipto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>GB-Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>Greenwich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>Hongkong</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>Islandia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>Iran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>Israel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>Jamaica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>Japón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>Kwajalein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>Libia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>Navajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>Polonia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>Portugal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>Singapur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>Turquía</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>Universal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>Zulu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>EEUU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>Europa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>Australia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>China</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>Corea</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>Zona Horaria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Nota: esto puede ser ignorado cuando la opción de región está en &quot;autoseleccionar&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japonés (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Inglés (english)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francés (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Alemán (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiano (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Español</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chino</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coreano (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Holandés (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugués (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Ruso (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Inglés Británico</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Francés Canadiense</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Español latinoamericano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chino Simplificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chino Tradicional (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>RTC Personalizado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Idioma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Semilla de GNA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Estereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Envolvente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID de la Consola:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Modo de salida del sonido:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM aaaa h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Regenerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Los ajustes del sistema solo se encuentran disponibles cuando no se está ejecutando ningún juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Esto reemplazará tu Switch virtual con una nueva. Tu Switch virtual actual no será recuperable. Esto podría causar efectos inesperados en determinados juegos. Si usas un archivo de configuración obsoleto, esto podría fallar. ¿Continuar?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Advertencia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID de Consola: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>Configurar las Asignaciones de la Pantalla Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>Asignaciones:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Crear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Borrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Renombrar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>Haz clic en el área inferior para añadir un punto, y luego presiona un botón para unir.
+Arrastre los puntos para cambiar de posición, o haga doble clic en las celdas de la tabla para editar los valores.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Borrar Punto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>Botón</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>Crear Perfíl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Introduzca un nombre para el nuevo perfíl:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>Borrar Perfíl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>Borrar Perfíl %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>Renombrar Perfíl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>Nuevo nombre:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[presionar tecla]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configurar pantalla táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Advertencia: Los ajustes en esta página afectarán al funcionamiento de la pantalla táctil emulada de yuzu. Cambiarlas podría resultar en un comportamiento inapropiado, como que la pantalla táctil deje de funcionar parcialmente. Se recomienda usar esta pestaña sólo si sabes lo que estás haciendo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parámetros Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diámetro Táctil Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Dedo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diámetro Táctil X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Ángulo Rotacional</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Recuperar ajustes predeterminados</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>General</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Nota: Cambiar el idioma guardará tu configuración actual.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Lenguage de la Interfaz:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Tema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Mostrar Columna de Accesorios</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Tamaño de los Iconos:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Texto de la Fila 1:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Texto de la Fila 2:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>Capturas de Pantalla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>Pregunte Dónde Guardar las Capturas de Pantalla (sólo para Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>Trayectoria de las Capturas de Pantalla:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>Selecciona la Trayectoria de las Capturas de Pantalla:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Inglés</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulario</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Servicio Web de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Al proporcionar su nombre de usuario y token, das tu consentimiento a que yuzu recopile datos de uso adicionales, que pueden incluir información de identificación del usuario.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verificar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Registrarse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nombre de usuario:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>¿Cuál es mi token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetría</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Compartir datos de uso anónimos con el equipo de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Saber más</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID de Telemetría:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Regenerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Presencia de Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Mostrar el juego actual en tu estado de Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Saber más&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Regístrate&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;¿Cuál es mi token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID de Telemetría: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Sin especificar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>No se pudo verificar el token</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>El token pudo ser verificado. El cambio realizado a su token no se ha guardado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verificando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verificación fallida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verificación fallida. Compruebe que ha ingresado el token correctamente, y que esté funcionando su conexión a internet.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Se recogen datos anónimos&lt;/a&gt; para ayudar a mejorar yuzu. &lt;br/&gt;&lt;br/&gt;¿Quieres compartir tus datos de uso con nosotros?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetría </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Comprobación de Texto Fallida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Cargando Web Applet...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Cerrar Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Salir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Para salir de la aplicación web, use los controles provistos por el juego y seleccione la opción &quot;Cerrar Web Applet&quot; en la barra del menú, o presione la tecla &quot;Enter&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Esta versión de yuzu fue creada sin soporte para QtWebEngine, lo que significa que yuzu no puede mostrar correctamente el manual del juego o la página solicitada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>La cantidad de shaders que se están construyendo actualmente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>La velocidad de emulación actual. Los valores superiores o inferiores al 100% indican que la emulación se está ejecutando más rápido o más lento que en una Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Cuántos fotogramas por segundo está mostrando el juego actualmente. Esto variará de un juego a otro y de una escena a otra.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tiempo que lleva emular un fotograma de la Switch, sin tener en cuenta la limitación de fotogramas o sincronización vertical. Para una emulación óptima, este valor debería ser como máximo de 16.67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>ESTACIONADO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>ASINC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>MULTINÚCLEO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Limpiar Archivos Recientes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Advertencia Formato de Juego Obsoleto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Está utilizando el formato de directorio de ROM deconstruido para este juego, que es un formato desactualizado que ha sido reemplazado por otros, como NCA, NAX, XCI o NSP. Los directorios de ROM deconstruidos carecen de íconos, metadatos y soporte de actualización.&lt;br&gt;&lt;br&gt;Para obtener una explicación de los diversos formatos de Switch que soporta yuzu,&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;echa un vistazo a nuestra wiki&lt;/a&gt;. Este mensaje no se volverá a mostrar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>¡Error al cargar la ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>El formato de la ROM no es compatible.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Se produjo un error al inicializar el núcleo de video.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu ha encontrado un error al ejecutar el núcleo de video, consulte el registro para obtener más detalles. Para obtener más información sobre cómo acceder al registro, consulte la siguiente página: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Cómo cargar el archivo de registro.&lt;/a&gt; Asegúrese de tener los últimos controladores de gráficos para su GPU.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>¡Error al cargar la ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Error desconocido. Por favor, consulte el registro para ver más detalles.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Iniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Datos de Juegos Guardados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Datos de Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Error al abrir la carpeta %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>¡La carpeta no existe!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Error al Abrir el Caché Transferible de Shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>No existe un Caché de shaders para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>Contenidos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>Actualización</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>Eliminar el Título</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>Eliminar el Juego Instalado %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>Se Eliminó con Éxito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>Se eliminó con éxito el juego de base instalado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>Error en la eliminación de %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>El juego base no está instalado en el NAND y no se puede eliminar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>Se eliminó con éxito la actualización instalada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>No hay ninguna actualización instalada para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>No hay ningún DLC instalado para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>Se eliminó con éxito %1 DLC instalado(s).</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>¿Desea eliminar el Caché Transferible de Shaders?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>¿Desea Eliminar la Configuración Personalizada del Juego?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>Eliminar Archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>Error al Eliminar el Caché Transferible de Shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>El Caché Transferible de Shaders fue eliminado con éxito.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>No se ha podido eliminar el caché de shaders transferibles.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>Error al Eliminar la Configuración Personalizada del Juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>No existe una configuración personalizada para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>Se eliminó con éxito la configuración personalizada del juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>No se ha podido eliminar la configuración personalizada del juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>¡La extracción de RomFS falló!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Se produjo un error al copiar los archivos RomFS o el usuario canceló la operación.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Completo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Base</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Elegir modo para volcar el RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Seleccione la forma en que desea volcar el RomFS. &lt;br&gt;Copiará todos los archivos en el nuevo directorio &lt;br&gt; mientras que el esqueleto solo creará la estructura del directorio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Extrayendo RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>¡La extracción RomFS tuvo éxito!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>La operación se completó con éxito.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Error al intentar abrir %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Seleccionar Directorio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Propiedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>No se pudieron cargar las propiedades del juego.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Ejecutable de Switch (%1);;Todos los archivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Cargar archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Abrir el directorio de la ROM extraída</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Directorio no válido seleccionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>El directorio que ha seleccionado no contiene ningún archivo &apos;main&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>Archivo de Switch Instalable (*.nca *.nsp *.xci);;Archivo de Contenidos Nintendo (*.nca);;Paquete de Envío Nintendo (*.nsp);;Imagen de Cartucho NX (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>Instalar Archivos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Instalando el archivo &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>Instalar Resultados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Aplicación del sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Archivo del Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Actualización de la aplicación del sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Paquete de Firmware (Tipo A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Paquete de Firmware (Tipo B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Actualización del juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC del Juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Titulo Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Seleccione el tipo de instalación NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Seleccione el tipo de título en el que desea instalar este NCA como:
+(En la mayoría de los casos, el &apos;Juego&apos; predeterminado está bien).</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Fallo en la instalación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>El tipo de título que seleccionó para el NCA no es válido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Archivo no encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Archivo &quot;%1&quot; no encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continuar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Mostrar Error</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Falta la cuenta de Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Para enviar un caso de prueba de compatibilidad de juegos, debe vincular su cuenta de yuzu.&lt;br&gt;&lt;br/&gt; Para vincular su cuenta yuzu, vaya a Emulación &amp; gt; Configuración &amp; gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>Error al abrir la URL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>No se puede abrir la URL &quot;%1&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Archivo Amiibo (%1);; Todos los archivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Cargar amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Error al abrir el archivo de datos de Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>No se puede abrir el archivo de Amiibo &quot;%1&quot; para leer.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Error al leer el archivo de datos de Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>No se pueden leer completamente los datos de Amiibo. Se esperaban leer %1 bytes, pero solo se pudo leer %2 bytes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Error al cargar los datos de Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>No se pueden cargar los datos de Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Captura de Pantalla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Imagen PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Velocidad: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Velocidad: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Juego: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Fotogramas: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>El juego que estás intentando cargar requiere de archivos adicionales de su Switch antes de poder jugar. &lt;br/&gt;&lt;br/&gt;Para obtener más información sobre cómo obtener estos archivos, ve a la siguiente página de la wiki: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Volcar archivos del sistema y las fuentes compartidas desde una Consola Switch. &lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;¿Quieres volver a la lista de juegos? Continuar con la emulación puede provocar fallos, datos de guardado dañados u otros errores.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu no pudo localizar el archivo de sistema de la Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu no pudo localizar un archivo de sistema de la Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Archivo del Sistema No Encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Falta Archivo del Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu no pudo encontrar las Fuentes Compartidas de la Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Fuentes compartidas no encontradas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Faltan las Fuentes Compartidas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Error Fatal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu ha encontrado un error fatal, consulte el registro para obtener más detalles. Para obtener más información sobre cómo acceder al registro, consulte la siguiente página: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;¿Cómo cargar el archivo de registro?&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt; La emulación continua puede provocar fallos, datos de guardado dañados u otros errores.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Error Fatal Encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Confirma la Clave de Rederivación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Estás a punto de forzar todas tus claves.
+Si no sabes qué es esto,
+es una acción potencialmente destructiva.
+Por favor, asegúrese de que esto
+es lo que desea hacer si es necesario.
+
+ Esto eliminará los archivos de las claves generados automáticamente y volverá a ejecutar el módulo de derivación de claves.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>Falta fuses</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation>- Falta BOOT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - Falta BCPKG2-1-Normal-Main</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - Falta PRODINFO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>Faltan Componentes de Derivación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>Faltan componentes que pueden impedir que la derivación de la clave se complete. &lt;br&gt;Por favor siga &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;la guía de inicio rápido de yuzu&lt;/a&gt; para conseguir todas tus llaves y juegos.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Derivando claves...
+Esto puede llevar unos minutos dependiendo
+del rendimiento de su sistema.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Obtención de claves</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Selecciona el destinatario para volcar el RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Por favor, seleccione los RomFS que desea volcar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>¿Estás seguro de que quieres cerrar yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>¿Estás seguro de que quieres detener la emulación? Cualquier progreso no guardado se perderá.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>La aplicación que se está ejecutando actualmente ha solicitado que yuzu no sea cerrado.
+
+¿Desea cerrarlo de todas formas?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL no está disponible!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzu no ha sido compilado con soporte de OpenGL.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkan no está disponible!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>yuzu no ha sido compilado con soporte de Vulkan.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>¡Error al inicializar OpenGL 4.3!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>Tu GPU no soporta OpenGL 4.3 o no tienes instalado el último controlador de la tarjeta gráfica.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>¡Error al inicializar OpenGL!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>Es posible que su GPU no soporta una o más extensiones OpenGL requeridas. Por favor, asegúrese de tener el último controlador de la tarjeta gráfica&lt;br&gt;&lt;br&gt;Extensiones no soportadas:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nombre</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilidad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Complementos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Tipo de Archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Tamaño</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Abrir ubicación de los archivos de guardado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Abrir ubicación de Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Abrir Caché Transferible de Shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>Eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>Eliminar la Actualización Instalada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>Eliminar todos los DLC instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>Eliminar el Caché de Shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>Eliminar Configuración Personalizada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>Eliminar Todos los Contenidos Instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Volcar RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copiar ID de título al portapapeles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Navegar a la entrada de BD del juego</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Propiedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Escanear subdirectorios</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Eliminar Directorio de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ Mover Hacia Arriba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ Mover Hacia Abajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Abrir Ubicación del Directorio</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfecto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>El juego funciona a la perfección sin fallos de audio o gráficos, todas las funciones probadas funcionan según lo previsto
+sin ninguna solución necesaria.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Excelente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>El juego funciona con fallos gráficos o de audio menores y se puede jugar de principio a fin. Puede requerir de
+soluciones temporales.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Bien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>El juego funciona con importantes fallos gráficos o de audio, pero el juego se puede jugar de principio a fin con
+soluciones temporales.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Mal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>El juego funciona, pero con importantes fallos gráficos o de audio. Es imposible avanzar en zonas específicas
+incluso con soluciones temporales.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menú</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>No es posible jugar a este juego debido a importantes errores gráficos o de audio. Es imposible avanzar mas allá de la pantalla
+de inicio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>No inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>El juego se bloquea al intentar iniciar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Sin probar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>El juego todavía no ha sido probado.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Haga doble clic para agregar un nuevo directorio a la lista de juegos.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtrar:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Introduce patrón para filtrar</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>Por favor, confirme que estos son los archivos que desea instalar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>Instalando una Actualización o DLC reemplazará la que está instalada anteriormente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>Instalar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>Instalar archivos al NAND...</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Cargando Shaders 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Cargando Shaders %v de %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Tiempo Estimado 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Cargando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Cargando Shaders %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Iniciando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Tiempo estimado %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Archivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Archivos recientes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulación</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Ver</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Depuración</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Herramientas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Ayuda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>Instalar archivos al NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Cargar archivo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Cargar carpeta...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>S&amp;alir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Iniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pausar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Detener</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Reiniciar claves...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Acerca de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Modo Ventana Única</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configurar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Mostrar los Encabezados del Widget</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Mostrar Barra de Filtro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Mostrar Barra de Estado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>Reiniciar el Tamaño de la Ventana</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Pantalla completa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Reiniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Cargar Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Informar de compatibilidad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>Abrir la Página de Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>Abrir la Guía de Inicio Rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>Preguntas Más Frecuentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Abrir la carpeta yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Captura de Pantalla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>Configurar el Juego Actual...</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroPerfil</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Títulos instalados en la SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Títulos instalados en la NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Títulos del Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Agregar un Nuevo Directorio de Juegos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[no establecido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Rotación %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Eje %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Botón %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[desconocido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>Clic 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>Clic 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>Clic 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>Clic 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>Clic 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>Eje GC %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>Botón GC %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[no usado]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Eje %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>Eje GC %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Ha ocurrido un error.
+Inténtelo nuevamente o contacte con el desarrollador del software.
+
+Código del error: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Ha ocurrido un error en el %1 a las %2.
+Inténtelo nuevamente o contacte con el desarrollador del software.
+
+Código del error: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Ha ocurrido un error.
+Códio del error: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Seleccione un usuario:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Usuarios</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Selector de perfil</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Introducir texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Software del Teclado</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Ingrese combinación de teclas de acceso rápido</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Pila de Llamadas</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>esperando por mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>tiene receptores: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>manejo del propietario: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>esperando todos los objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>esperando uno de los siguientes objetos</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>esperado por ningún hilo</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>corriendo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>listo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>en pausa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>esperando para el retorno HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>dormido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>esperando para respuesta IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>esperando objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>esperando por mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>esperando variable condicional</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>esperando al árbitro de dirección</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>inactivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>muerto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>núcleo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Procesador desconocido %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>procesador = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>núcleo ideal = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>máscara de afinidad = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id de hilo = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>prioridad = %1(presente) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>Últimos ticks consecutivos = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>no esperando por mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>esperado por hilo</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Árbol de Espera</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/fr.ts b/dist/languages/fr.ts
new file mode 100644
index 000000000..83e678f93
--- /dev/null
+++ b/dist/languages/fr.ts
@@ -0,0 +1,4732 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="fr" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>À propos de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu est un émulateur expérimental open-source pour la Nintendo Switch licencié sous GPLv2.0. &lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n&apos;avez pas obtenu légalement.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;
+&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Site Web&lt;/span&gt;&lt;/a&gt;|&lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Code Source&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributeurs&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licence&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; est une marque déposée de Nintendo. yuzu n&apos;est en aucun cas affilié à Nintendo.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Communications avec le serveur...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Annuler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Touchez le coin supérieur&lt;br&gt;gauche de votre pavé tactile.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Touchez le coin supérieur gauche&lt;br&gt; de votre pavé tactile.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Configuration terminée!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Signaler la compatibilité</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Signaler la compatibilité d&apos;un jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Si vous choisissez à soumettre un test d&apos;essai à la liste de compatibilité yuzu&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, Les informations suivantes seront collectées et publiées sur le site :
+&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informations Système (Processeur / Carte Graphique / Système d&apos;exploitation)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;La version de yuzu que vous employez&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Le compte yuzu sous lequel vous êtes connecté&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Parfait</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu fonctionne parfaitement sans problèmes audio-visuels.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Super</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu fonctionne avec des problèmes audio-visuels mineurs et est jouable du début jusqu&apos;à la fin. Peut nécessiter des patchs temporaires. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Correct</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu fonctionne avec des problèmes audio ou graphiques majeurs, mais est jouable du début à la fin avec des modifications.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Mauvais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu fonctionne, mais avec des problèmes graphiques ou audio majeurs. Impossible de progresser à certains endroits spécifiques à cause de ces problèmes même en utilisant des modifications. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu est complètement injouable à cause de problèmes graphique ou audio majeur. Impossible de progresser plus loin que le menu de départ.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Ne se lance pas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Le jeu crash au lancement. .&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sans prendre en compte la vitesse ou les performances, À quel point ce jeu est-il bien émulée du début à la fin sur cette version de yuzu ?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Merci de votre Suggestion !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Soumission en cours</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Erreur de communication</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Une erreur est survenue en envoyant l&apos;erreur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Suivant</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Moteur de Sortie :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Il s&apos;agit d&apos;un effet de post-traitement qui ajuste la vitesse de l&apos;audio sur celle de l&apos;émulation afin de prévenir les lags du son. Ceci augmente toutefois la latence du son.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Activer l&apos;étirement du son</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Appareil audio :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Utiliser le volume global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Régler le volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Général</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>Précision:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>Précis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Peu sûr</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Activer le mode de débogage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>Nous recommandons de régler la précision sur &quot;Précis&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Paramètres d&apos;optimisation du CPU non sûrs</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Ces réglages réduisent la précision pour la vitesse.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Non-utilisation du FMA (améliorer les performances des CPU sans FMA) </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+Cette option améliore la vitesse en réduisant la précision des instructions fusionnées-multiple-ajoutées sur les CPU sans support FMA natif.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>FRSQRTE et FRECPE plus rapides</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+Cette option améliore la vitesse de certaines fonctions approximatives en virgule flottante en utilisant des approximations natives moins précises.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Les paramètres du CPU ne sont disponibles que lorsque le jeu n&apos;est pas en marche.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>Mise en mode de débogage du CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>Le mode de débogage du CPU est uniquement destiné à l&apos;usage des développeurs. Êtes-vous sûr de vouloir l&apos;activer?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>Activer les optimisations du CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;
+&lt;b&gt;Pour le débogage uniquement.&lt;/b&gt;
+&lt;br&gt;
+Si vous n&apos;êtes pas sûr de ce qu&apos;elles font, laissez-les toutes activées.
+&lt;br&gt;
+Ces réglages ne prennent effet que lorsque la précision du CPU est en &quot;mode débogage&quot;.
+&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Activer le &quot;stub&quot; de GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Port :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>S&apos;enregistrer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtre de log global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Montrer le journal de logs (Uniquement sur Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Ouvrir l&apos;emplacement du journal de logs</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Chaîne d&apos;arguments</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Activer les services de rapport verbeux</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Ceci sera automatiquement mis à zéro quand yuzu se fermera</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avancé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Mode Kiosk (Quest)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configuration de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Général</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>UI</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Liste des jeux</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Profils</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Système de fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Contrôles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Raccourcis clavier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Débogage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Vidéo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Son</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Services</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Répertoire de stockage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Carte SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartouche de jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Chemin</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Inséré</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Jeu en cours</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Gestionnaire de correctif</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Extraire les fichiers NSOs décompressés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Extraire l&apos;ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Racine de chargement de mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Extraire la racine</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Mise en cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Répertoire du cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Mettre en cache la métadonnée de la liste des jeux</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Mettre à zéro le cache des métadonnées</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Sélectionner le répertoire NAND émulé...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Sélectionner le répertoire SD émulé...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Sélectionner le chemin de la cartouche de jeu...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Sélectionner le répertoire d&apos;extraction...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Sélectionner le répertoire de chargement de mod...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Sélectionner le répertoire du cache...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>Le cache des métadonnées est déjà vide.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>L&apos;opération s&apos;est terminée avec succès.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Le cache des métadonnées n&apos;a pas pu être supprimé. Il pourrait être utilisé ou non-existant.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Général </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Limiter la vitesse en pourcentages</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Confirmer la sortie pendent l&apos;émulation en cours</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Demander l&apos;utilisateur au lancement d&apos;un jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Mettre en pause l’émulation lorsque mis en arrière plan </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Utiliser les shader cache de disque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Utiliser l&apos;émulation GPU asynchrone</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Couleur de L’arrière plan :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Paramètres de raccourcis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Double-cliquez sur une liaison pour la changer.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Action</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Raccourci clavier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Contexte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Séquence de touches conflictuelle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Joueur 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Joueur 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Joueur 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Joueur 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Joueur 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Joueur 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Joueur 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Joueur 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avancé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configurer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurer les touches</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Stick Gauche</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Boutons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Stick Droit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configuration de la souris</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Boutons de la souris</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Avant :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Arrière :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Gauche :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Milieu :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Droite :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Effacer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[pas défini]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Réinitialiser </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[appuyez sur une touche]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Gestionnaire de profil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Utilisateur actuel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nom d&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Mettre une image</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Ajouter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Renommer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Supprimer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>La gestion de profil est accessible uniquement lorsque aucun jeu n&apos;est en cours.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Entrez un nom d&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Utilisateurs</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Entrez un nom d&apos;utilisateur pour le nouvel utilisateur :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Entrez un nouveau nom d&apos;utilisateur :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Confirmez la suppression</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Vous êtes sur le point de supprimer l&apos;utilisateur avec le nom &quot;%1&quot;. Êtes vous sûr?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Sélectionner l&apos;image de l&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Images JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Erreur dans la suppression de l&apos;image</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Une erreur est survenue en essayant de changer l&apos;image précédente à : %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Erreur dans la suppression du fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Impossible de supprimer le fichier existant : %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Erreur dans la création du répertoire d&apos;image de l&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Impossible de créer le répertoire %1 pour stocker les images de l&apos;utilisateur.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Erreur dans la copie de l&apos;image de l&apos;utilisateur</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Impossible de copier l&apos;image de %1 à %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>Le BCAT et le chemin que Nintendo utilise pour envoyer des informations au jeux pour encourager sa communauté à débloquer du contenu additionel.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT Back-end</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Apprenez en plus sur le BCAT, le Boxcat, et les événements courants&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Le service boxcat est hors-ligne ou vous n&apos;êtes pas connectés à internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Une erreur est survenu lors du traitement des données boxcat. Contactez les développeurs de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>La version de yuzu que vous utilisez est trop vieille ou trop nouvelle pour le serveur. Essayez la dernière mise à jour officiel de yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Il n&apos;y a pour le moment aucun événements sur boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu récupere le statut de boxcat le plus récent...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Paramètres Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Note : ceci peut être remplacé quand le paramètre de région est réglé sur automatique</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japonais (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Anglais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Français (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Allemand (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italien (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Espagnol (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinois</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coréen (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Néerlandais (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugais (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russe (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taïwanais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Anglais Britannique</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Français Canadien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Espagnol d&apos;Amérique Latine</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chinois Simplifié</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chinois Traditionnel (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>RTC Customisé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Langue</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Seed RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stéréo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID de la Console :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Mode de sortie Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Regénérer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Les paramètres systèmes ne sont accessibles que lorsque le jeu n&apos;est pas en cours.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Ceci remplacera la Switch virtuelle actuelle par une nouvelle. La Switch actuelle ne sera plus récupérable. cela peut entrainer des effets non désirés pendant le jeu. Ceci peut échouer si une configuration de sauvegarde périmée est utilisée. Continuer ?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Avertissement</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID de la Console : 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configurer l&apos;Écran Tactile</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Avertissement : les paramètres de cette page affecte la fonctionnalité intrinsèque de l&apos;écran tactile émulée de yuzu. Toute modification pourrait aboutir à un comportement non désiré, comme par exemple : l&apos;écran tactile qui ne fonctionne plus, de manières partielle ou totale. N&apos;utilisez cette page que si ne vous ne savez ce que vos faites.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Paramètres Tactiles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diamètres Tactiles Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Doigt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diamètres Tactiles X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Angle de Rotation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurer</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Général</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Note : Changer la langue appliquera votre configuration.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Language de l&apos;interface :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Thème :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Liste de jeux</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Afficher la colonne Des Add-Ons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Grosseur de l&apos;icône</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Texte rangée 1 :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Texte rangée 2 :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Anglais</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forme</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Service Web yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>En fournissant votre surnom et token, vous acceptez de permettre à yuzu de collecter des données d&apos;utilisation supplémentaires, qui peuvent contenir des informations d&apos;identification de l&apos;utilisateur.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Vérifier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Se connecter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Pseudonyme :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Qu&apos;est ce que mon token ? </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Télémétrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Partager les données d&apos;utilisation anonymes avec l&apos;équipe yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>En savoir plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID Télémétrie :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Regénérer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Statut Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Afficher le jeu en cours dans le Statut Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;En savoir plus&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Se connecter&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Quel est mon token ?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID Télémétrie : 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Non-spécifié</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token non vérifié</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Le token n&apos;a pas été vérifié. Le changement à votre token n&apos;a pas été enregistré.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Vérification...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Échec de la vérification</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Échec de la vérification. Vérifiez si vous avez correctement entrez votre token, et que votre connection internet fonctionne.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Des données anonymes sont collectées&lt;/a&gt; pour aider à améliorer yuzu. &lt;br/&gt;&lt;br/&gt;Voulez-vous partager vos données d&apos;utilisations avec nous ?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Télémétrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Échec de la vérification du texte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Chargement du Web Applet...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Quitter le Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Quitter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Pour quitter l&apos;application web, utilisez les contrôles réserver par le jeu pour sélectionner exit, sélectionner l&apos;option &apos;Quitter le Web Applet&apos; dans la barre menu, ou appuyez sur la touche &apos;Entrer&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Cette version de yuzu à été construit sans le support de QtWebEngine, ce qui veut dire que yuzu ne peut pas correctement afficher le manuel du jeu ou la page web demandée.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Valeur actuelle de la vitesse de l&apos;émulation. Des valeurs plus hautes ou plus basses que 100% indique que l&apos;émulation fonctionne plus vite ou plus lentement qu&apos;une véritable Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Combien d&apos;image par seconde le jeu est en train d&apos;afficher. Ceci vas varier de jeu en jeu et de scènes en scènes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Temps pris pour émuler une image par seconde de la switch, sans compter le limiteur d&apos;image par seconde ou la synchronisation verticale. Pour une émulation à pleine vitesse, ceci devrait être au maximum à 16.67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Effacer les Fichiers Récents</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Avertissement : Le Format de jeu est dépassé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Vous utilisez un format de ROM déconstruite pour ce jeu, qui est donc un format dépassé qui à été remplacer par d&apos;autre. Par exemple les formats NCA, NAX, XCI, ou NSP. Les destinations de ROM déconstruites manque des icônes, des métadonnée et du support de mise à jour.&lt;br&gt;&lt;br&gt;Pour une explication des divers formats Switch que yuzu supporte, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;Regardez dans le wiki&lt;/a&gt;. Ce message ne sera pas montré une autre fois.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Erreur lors du chargement de la ROM !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Le format de la ROM n&apos;est pas supporté.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Une erreur s&apos;est produite lors de l&apos;initialisation du noyau dédié à la vidéo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu a rencontré une erreur fatale, veuillez consulter les logs pour plus de détails. Pour plus d&apos;informations sur l&apos;accès aux logs, veuillez consulter la page suivante : &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt; Comment télécharger le fichier des logs &lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Une erreur inconnue est survenue. Veuillez consulter le journal des logs pour plus de détails.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Démarrer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Enregistrer les données</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Donnés du Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Erreur dans l&apos;ouverture du dossier %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Le dossier n&apos;existe pas !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Erreur lors de l&apos;ouverture des Shader Cache Transferable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Un shader cache pour ce titre n&apos;existe pas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>L&apos;extraction de la RomFS a échoué !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Une erreur s&apos;est produite lors de la copie des fichiers RomFS ou l&apos;utilisateur a annulé l&apos;opération.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Plein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Squelette</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Sélectionnez le mode d&apos;extraction de la RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Veuillez sélectionner la manière dont vous souhaitez que le fichier RomFS soit extrait.&lt;br&gt;Full copiera tous les fichiers dans le nouveau répertoire, tandis que&lt;br&gt;skeleton créera uniquement la structure de répertoires.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Extraction de la RomFS ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Annuler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Extraction de la RomFS réussi !</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>L&apos;opération s&apos;est déroulée avec succès.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Erreur lors de l&apos;ouverture %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Sélectionner un répertoire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Propriétés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Les propriétés du jeu n&apos;ont pas pu être chargées.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Exécutable Switch (%1);;Tous les fichiers (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Charger un fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Ouvrir le dossier des ROM extraites</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Destination sélectionnée invalide</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Le répertoire que vous avez sélectionné ne contient pas de fichier &quot;main&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Installation du fichier &quot;%1&quot; ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Application Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Archive Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Mise à jour de l&apos;application système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Paquet micrologiciel (Type A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Paquet micrologiciel (Type B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Mise à jour de jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC de jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Titre Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Sélectionner le type d&apos;installation du NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Veuillez sélectionner le type de titre auquel vous voulez installer ce NCA :
+(Dans la plupart des cas, le titre par défaut : &apos;Jeu&apos; est correct.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Échec de l&apos;installation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Le type de titre que vous avez sélectionné pour le NCA n&apos;est pas valide.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Fichier non trouvé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Fichier &quot;%1&quot; non trouvé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continuer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Erreur d&apos;affichage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Compte yuzu manquant</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Pour soumettre un test de compatibilité pour un jeu, vous devez lier votre compte yuzu.&lt;br&gt;&lt;br/&gt;Pour lier votre compte yuzu, aller à Emulation &amp;gt; Configuration&amp;gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Fichier Amiibo (%1);; Tous les fichiers (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Charger un Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Erreur lors de l&apos;ouverture du fichier de données Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Impossible d&apos;ouvrir le fichier Amiibo &quot;%1&quot; à lire.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Erreur lors de la lecture du fichier de données Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Impossible de lire entièrement les données Amiibo. On s&apos;attend à lire %1 octets, mais il n&apos;a pu lire que %2 octets</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Erreur lors du chargement des données Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Impossible de charger les données Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Capture d&apos;écran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Image PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Vitesse : %1% / %2% </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Vitesse : %1% </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Jeu : %1 FPS </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Frame : %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Le jeu que vous essayez de charger a besoin de fichiers additionnels que vous devez extraire depuis votre Switch avant de jouer.&lt;br/&gt;&lt;br/&gt;Pour plus d&apos;information sur l&apos;extraction de ces fichiers, veuillez consulter la page du wiki suivante : &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Extraction des archives système et des Shared Fonts depuis la Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu n&apos;a pas été capable de localiser un système d&apos;archive Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu n&apos;a pas été capable de localiser un système d&apos;archive Switch. %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Archive système introuvable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Archive Système Manquante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>Yuzu n&apos;a pas été capable de localiser les polices partagées de la Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Les polices partagées non pas été trouvées</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Polices Partagée Manquante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Erreur fatale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu a rencontré une erreur fatale, veuillez consulter les logs pour plus de détails. Pour plus d&apos;informations sur l&apos;accès aux logs, veuillez consulter la page suivante : &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt; Comment télécharger le fichier des logs &lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Erreur Fatale rencontrée</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Confirmer la réinstallation de la clé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Vous êtes sur le point de réinstaller toutes vos clés.
+Si vous ne savez pas ce que cela veut dire et ce que vous faites,
+cela peut être une action potentiellement destructrice.
+S&apos;il vous plaît assurez-vous que c&apos;est bien ce que vous voulez
+et éventuellement faites des sauvegardes.
+
+Cela supprimera vos fichiers de clé générés automatiquement et ré exécutera le module d&apos;installation de clé.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Installation des clés...
+Cela peut prendre jusqu&apos;à une minute en fonction
+des performances de votre système.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Installation des clés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Sélectionner la cible d&apos;extraction du RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Veuillez sélectionner quel RomFS vous voulez extraire.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Êtes vous sûr de vouloir fermer yuzu ?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Êtes-vous sûr d&apos;arrêter l&apos;émulation ? Tout progrès non enregistré sera perdu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>L&apos;application en cours a demandé à yuzu de ne pas quitter.
+
+Voulez-vous ignorer ceci and quitter quand même ?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nom</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilité</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Extensions</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Type de fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Taille</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Ouvrir l&apos;emplacement des données de sauvegarde</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Ouvrir l&apos;emplacement des données des mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Ouvrir le cache de shaders transférable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Extraire la RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copier l&apos;ID du titre dans le Presse-papiers</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Accédez à l&apos;entrée GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Propriétés</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Scanner les sous-dossiers</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Supprimer le répertoire du jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Ouvrir l&apos;emplacement du répertoire</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Parfait</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Le jeu fonctionne parfaitement, de manière fluide sans aucun bug audio ou graphique, toutes les fonctionnalités testées fonctionnent comme prévu sans
+aucune modification nécessaire.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Bon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Le jeu fonctionne correctement avec des bugs audio ou graphiques mineurs et est jouable du début à la fin. Nécessite peut être des
+modifications</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Ok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Le jeu fonctionne avec des bugs audio ou graphiques majeurs, mais il est jouable du début à la fin avec des modifications.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Mauvais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Le jeu fonctionne mais avec des bugs audio et graphiques majeurs. Impossible de progresser dans certaines zones à causes des bugs
+même avec des modifications.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Le jeu est complètement injouable à cause de bugs audio et graphiques. Impossible de progresser plus loin que l&apos;écran de démarrage.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Ne démarre pas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Le jeu crash au lancement.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Non testé</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Le jeu n&apos;a pas encore été testé.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Double-cliquez pour ajouter un nouveau dossier à la liste de jeux</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtre :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Entrez un motif à filtrer</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Chargement des shaders 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Chargement des shaders %v sur %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Temps Estimé 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Chargement...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Chargement des shaders %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Lancement...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Temps Estimé %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Fichier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Fichiers récents</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Émulation</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Vue</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Débogage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Outils</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Aide</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Charger un fichier ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Charger un dossier ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>Q&amp;uitter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Démarrer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pause</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Arrêter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Réinitialiser les clés ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>À propos de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Mode fenêtre unique</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configurer ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Afficher les &quot;Dock Widget Headers&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Afficher la barre de filtre</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Afficher la barre d&apos;état</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Plein écran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Redémarrer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Charger un Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Signaler la compatibilité</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Ouvrir le dossier de yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Prendre une capture d&apos;ecran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfil</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Titres installés sur la SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Titres installés sur la NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Titres Système</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Ajouter un nouveau répertoire de jeu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Maj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[non défini]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Chapeau %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Axe %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Bouton %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[inconnu]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[inutilisé]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Axe %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Une erreur s&apos;est produite.
+Veuillez essayer à nouveau ou contactez le développeur du logiciel.
+
+Code d&apos;Erreur: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Une erreur s&apos;est produite le %1 à %2.
+Veuillez essayer à nouveau ou contactez le développeur du logiciel.
+
+Code d&apos;Erreur: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Une erreur s&apos;est produite.
+Code d&apos;Erreur: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Choisir un utilisateur :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Utilisateurs</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Sélecteur de profil</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Entrer le texte :</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Clavier virtuel</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Entrez une hotkey</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Pile d&apos;exécution</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>en attente du &quot;mutex&quot; 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>En attente : %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>propriétaire de la manche : 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>en attente de tous les objets</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>en attente d&apos;un des objets suivants</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>en cours</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>prêt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>en pause</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>en attente du retour de HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>en veille</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>en attente de réponse IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>En attente d&apos;objets</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>En attente du &quot;mutex&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>en attente de la variable conditionnelle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>En attente de l&apos;adresse arbitre</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>dormant</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>Mort</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>idéal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>cœur %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Processeur inconnu %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>Processeur = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>Cœur idéal = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>masque d&apos;affinité = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id du fil = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>priorité = %1(courant) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>dernier tick en cours = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>en attente du &quot;mutex&quot;</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>attendu par un fil</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Arbre d&apos;attente</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/it.ts b/dist/languages/it.ts
new file mode 100644
index 000000000..09c832fd3
--- /dev/null
+++ b/dist/languages/it.ts
@@ -0,0 +1,4724 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="it" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Riguardo yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu è un emulatore open source sperimentale per Nintendo Switch sotto licenza GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Questo software non dovrebbe essere utilizzato per giocare a giochi che non hai ottenuto legalmente&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Sito&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Codice Sorgente&lt;/span&gt;&lt;/a&gt; I &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributori&lt;/span&gt;&lt;/a&gt; I &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licenza&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; è un marchio registrato di Nintendo. yuzu non è affiliato a Nintendo in alcun modo.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>In comunicazione con il server...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Annulla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Configurazione completata!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Segnala Compatibilità</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Segnala la Compatibilità di un Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Se dovessi scegliere di inviare un rapporto alla &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;Lista Compatibilità di yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, Le seguenti informazioni saranno raccolte e visualizzate sul sito: &lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informazioni sull&apos;Hardware (CPU / GPU / Sistema Operativo)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Quale versione di yuzu stai utilizzando&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;L&apos;account di yuzu connesso&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfetto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco funziona impeccabilmente senza errori di audio o grafici.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Buono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco funziona con qualche errore grafico minore o glitch nell&apos;audio ed è giocabile dall&apos;inizio alla fine. Potrebbe richiedere qualche metodo alternativo.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Ok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco funziona e ha errori grafici gravi o glitch nell&apos;audio ma è possibile giocare dall&apos;inizio alla fine con dei metodi alternativi.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Scadente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco presenta alcuni errori audio o video. E&apos; impossibile proseguire in alcune aree a causa della presenza di glitch persino utilizzando metodi alternativi.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Questo gioco è completamente ingiocabile a causa di gravi errori audio o video. E&apos; impossibile proseguire oltre la schermata iniziale.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Non si Avvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Il gioco si blocca quando viene avviato.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Indipendentemente da velocità o prestazioni, come ti è sembrato giocare questo gioco dall&apos;inizio alla fine su questa versione di yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Grazie per la tua segnalazione!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Inviando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Errore di comunicazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>E&apos; stato riscontrato un errore mandando il Testcase</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Prossimo</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Motore dell&apos;Output:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>L&apos;effetto post-processing regola la velocità dell&apos;audio per fare in modo che sia uguale alla velocità del gioco e prevenire problemi di audio. Questo tuttavia aumenta la latenza dell&apos;audio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Abilita allungamento dell&apos;audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Dispositivo Audio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Usa volume globale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Imposta volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Generale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Non sicuro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Abilita Modalità Debug</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Impostazioni Ottimizzazione CPU non sicura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Abilita GDB Stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Porta:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Logging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtro Log Globale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Mostra Console Log (Solo per Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Apri Cartella Log</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Stringa degli Argomenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>Grafica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Abilita Servizi di Segnalazione Dettagliata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Verrà ripristinato automaticamente dopo la chiusura di yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avanzate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Modalità Kiosk (Quest)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configurazione di yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Generale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>Interfaccia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Profili</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Filesystem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Comandi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Hotkey</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Debug</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Grafica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Servizi</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Cartelle di Archiviazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Scheda SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartuccia di gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Percorso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Inserita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Gioco In Uso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Gestione Patch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Estrai NSO Decompressi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Estrai ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Cartella Caricamento Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Cartella di Estrazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Caching</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Cartella Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Crea Cache di Metadati Lista Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Elimina Cache dei Metadati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Seleziona Cartella NAND Emulata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Seleziona Cartella Scheda SD Emulata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Seleziona Percorso Cartuccia di Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Seleziona Cartella di Estrazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Seleziona Cartella per il Caricamento delle Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Seleziona Cartella della Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>La cache dei metadati è già vuota.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>L&apos;operazione è stata completata con successo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Impossibile eliminare cache dei metadati. Potrebbe essere in uso o inesistente.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Generale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Percentuale Limite Velocità</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Richiedi conferma di uscire mentre l&apos;emulazione è in corso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Richiedi utente all&apos;avvio di un gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pausa emulazione quando in background</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>Impostazioni API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Dispositivo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Impostazioni grafiche</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Usa cache shader su disco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Usa emulazione GPU asincrona</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Default (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>Usa il colore di sfondo globale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Imposta colore dello sfondo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Colore Sfondo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Impostazioni Hotkey</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Clicca due volte su un collegamento per cambiarlo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Azione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Hotkey</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Contesto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Conflitto nella Sequenza di Tasti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Giocatore 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Giocatore 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Giocatore 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Giocatore 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Giocatore 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Giocatore 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Giocatore 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Giocatore 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avanzate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Vibrazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Connesso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Predefiniti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Giocatore 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Giocatore 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Giocatore 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Giocatore 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Giocatore 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Giocatore 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Giocatore 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Giocatore 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Altro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Tastiera</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Avanzate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Configura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Debug Controller</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configura Input</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Salva</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Nuovo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Elimina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Stick Sinistro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Su</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Sinistra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Destra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Giù</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Premuto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modificatore</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Meno</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Cattura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Più</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Pulsanti Frontali</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Stick Destro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[in attesa]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Sensibilità:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Touch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Calibrazione:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Configura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configura Mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Pulsanti Mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Avanti:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Indietro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Sinistro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Centrale:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Destro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Cancella</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[non impostato]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Ripristina Predefinito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[premi un pulsante]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Gestione Profili</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Utente in Uso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nome Utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Imposta Immagine</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Aggiungi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Rinomina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Rimuovi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>La gestione dei profili è disponibile solamente quando nessun gioco è in esecuzione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>&amp;1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Inserisci Nome Utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Utenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Inserisci un nome utente per il nuovo utente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Inserisci un nuovo nome utente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Conferma Eliminazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Stai per cancellare l&apos;utente chiamato &quot;%1&quot;. Sei sicuro?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Seleziona Immagine Utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Immagini JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Impossibile eliminare l&apos;immagine</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Impossibile sovrascrivere l&apos;immagine precedente in: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Impossibile eliminare il file</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Impossibile eliminare il file già esistente: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Errore nella creazione della cartella delle immagini dell&apos;utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Impossibile creare la cartella %1 per archiviare le immagini dell&apos;utente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Impossibile copiare l&apos;immagine utente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Impossibile copiare l&apos;immagine da %1 a %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>Nintendo usa BCAT per inviare dati ai giochi per coinvolgere la comunità e sbloccare contenuti aggiuntivi.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>Backend BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Scopri di più riguardo BCAT, Boxcat, ed Eventi in Corso&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Il servizio boxcat è offline o potresti non essere connesso a internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>E&apos; stato riscontrato un errore nell&apos;elaborazione dei dati degli eventi di boxcat. Contatta gli sviluppatori di yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>La versione di yuzu che stai usando è troppo vecchia o troppo nuova per questo server. Prova ad aggiornare all&apos;ultima release ufficiale di yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Non ci sono eventi in corso su boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>yuzu sta recuperando lo stato attuale di boxcat...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Impostazioni di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>Zulu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>USA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>Europa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>Australia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>Cina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>Corea</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Nota: questo può essere ignorato quando le impostazioni della regione sono su auto-select</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Giapponese (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Inglese (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francese (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Tedesco (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Spagnolo (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Cinese</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coreano (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Olandese (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portoghese (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russo (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanese</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Inglese Britannico</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Francese Canadese</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Spagnolo Latino Americano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Cinese Semplificato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Cinese Tradizionale (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>RTC Personalizzato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Lingua</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Seed RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID Console:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Modalità di output del sonoro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Rigenera</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Le impostazioni di sistema sono disponibili solamente quando nessun gioco è in esecuzione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Questo rimpiazzerà la tua Switch virtuale con una nuova. La tua Switch virtuale non sarà recuperabile. Questo potrebbe avere effetti indesiderati nei giochi. Questo potrebbe fallire, se usi un salvataggio non aggiornato. Desideri continuare?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Attenzione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID Console: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Nuovo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Elimina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Rinomina</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Elimina Punto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>Pulsante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>Nuovo Profilo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Inserisci il nome per il nuovo profilo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>Elimina Profilo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>Elimina profilo %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>Rinomina Profilo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>Nuovo nome:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[premi pulsante]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configura Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Attenzione: Le impostazioni in questa pagina influenzano il funzionamento interno del touchscreen emulato di yuzu. Cambiarle potrebbe risultare in effetti indesiderati, ad esempio il touchscreen potrebbe non funzionare parzialmente o totalmente. Utilizza questa pagina solo se sei consapevole di ciò che stai facendo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parametri Touch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diametro Touch Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Dito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diametro Touch X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Angolo di Rotazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Ripristina Predefiniti</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Generale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Nota: Cambiare lingua applicherà i cambiamenti fatti alla configurazione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Lingua Interfaccia:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Tema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Mostra Colonna Add-On</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Dimensione Icona:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Testo Riga 1:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Testo Riga 2:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>Screenshots</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Inglese</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Servizio Web di yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Fornendo i tuoi nome utente e token, permetti a yuzu di raccogliere dati di utilizzo, che potrebbero includere informazioni di identificazione utente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verifica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Registrati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nome utente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Qual è il mio token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Condividi dati sull&apos;utilizzo anonimamente con il team di yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Per saperne di più</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID Telemetria:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Rigenera</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discord Presence</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Mostra il Gioco Attuale nel tuo Stato di Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Per saperne di più&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Registrati&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Qual è il mio token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID Telemetria: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Non specificato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token non verificato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Nome utente e token non verificati. I cambiamenti al tuo nome utente e/o token non sono stati salvati.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verifica in corso...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verifica fallita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verifica fallita. Controlla di aver inserito correttamente il tuo username e token, e che la tua connessione ad internet sia funzionante.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Vengono raccolti dati anonimi&lt;/a&gt; per aiutarci a migliorare yuzu. &lt;br/&gt;&lt;br/&gt;Desideri condividere i tuoi dati di utilizzo con noi?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Verificazione Testo Fallita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Caricamento Web Applet...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Esci dal Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Esci</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Per uscire dall&apos;applicazione web, usa i pulsanti provvisti dal gioco per uscire, seleziona l&apos;opzione &apos;Esci dal Web Applet&apos; nel menu in alto, o premi il tasto &apos;Invio&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Questa versione di yuzu è stata compilata senza supporto a QtWebEngine, quindi yuzu non potrà aprire il manuale di gioco o delle pagine web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Velocità corrente dell&apos;emulazione. Valori più alti o più bassi di 100% indicano che l&apos;emulazione sta funzionando più velocemente o lentamente rispetto a una Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Quanti frame al secondo il gioco mostra attualmente. Questo varia da gioco a gioco e da situazione a situazione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tempo utilizzato per emulare un frame della Switch, non contando i limiti ai frame o il v-sync.
+Per un&apos;emulazione alla massima velocità, il valore dev&apos;essere al massimo 16.67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Elimina File Recenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Avviso Formato di Gioco Obsoleto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Stai usando una cartella con dentro una ROM decostruita come formato per avviare questo gioco, è un formato obsoleto ed è stato sostituito da altri come NCA, NAX, XCI o NSP. Le ROM decostruite non hanno icone, metadata e non supportano gli aggiornamenti. &lt;br&gt;&lt;br&gt;Per una spiegazione sui vari formati di Switch che yuzu supporta, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;controlla la nostra wiki&lt;/a&gt;. Questo messaggio non verrà più mostrato.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Errore nel caricamento della ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Il formato della ROM non è supportato.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>E&apos; stato riscontrato un errore nell&apos;inizializzazione del core video.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu ha riscontrato un errore nell&apos;esecuzione del video core, visualizza il log per maggiori dettagli. Per maggiori informazioni su come accedere al log, visualizza la seguente pagina: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Come Caricare Il File Log&lt;/a&gt;. Assicurati di avere gli ultimi driver della tua GPU.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>E&apos; stato riscontrato un errore sconosciuto. Visualizza il log per maggiori dettagli.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Avvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Dati di Salvataggio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Dati delle Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Errore nell&apos;Apertura della Cartella %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>La cartella non esiste!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Errore nell&apos;Apertura della Cache Shader Trasferibile</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Una cache di shader per questo titolo non esiste.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>Contenuti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>Aggiorna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Estrazione RomFS Fallita!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>C&apos;è stato un errore nella copia dei file del RomFS o l&apos;operazione è stata annullata dall&apos;utente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Completa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Scheletro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Seleziona Modalità Estrazione RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Seleziona come vorresti estrarre il RomFS. &lt;br&gt;Completo copierà tutti i file in una nuova cartella mentre&lt;br&gt;scheletro creerà solamente le cartelle e le sottocartelle.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Estrazione RomFS in corso...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Annulla</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Estrazione RomFS Riuscita!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>L&apos;operazione è stata completata con successo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Errore nell&apos;Apertura di %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Seleziona Cartella</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Proprietà</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Le proprietà del gioco non sono potute essere caricate.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Eseguibile Switch (%1);;Tutti i File (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Carica File</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Apri Cartella ROM Estratta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Cartella Selezionata Non Valida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>La cartella che hai selezionato non contiene un file &quot;main&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Installazione del file &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Applicazione di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Archivio di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Aggiornamento Applicazione di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Pacchetto Firmware (Tipo A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Pacchetto Firmware (Tipo B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Aggiornamento di Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC Gioco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Titolo Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Seleziona il Tipo di Installazione NCA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Seleziona il tipo del file NCA da installare:
+(Nella maggior parte dei casi, il predefinito &apos;Gioco&apos; va bene.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Installazione Fallita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Il tipo che hai selezionato per l&apos;NCA non è valido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>File non trovato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>File &quot;%1&quot; non trovato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continua</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Messaggio di Errore</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Account di yuzu non trovato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Per segnalare la compatibilità di un gioco, devi collegare il tuo account yuzu. &lt;br&gt;&lt;br/&gt;Per collegare il tuo account yuzu, vai su Emulazione &amp;gt;
+Configurazione &amp;gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>File Amiibo (%1);; Tutti I File (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Carica Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Errore nell&apos;apertura del file dati Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Impossibile aprire e leggere il file Amiibo &quot;%1&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Errore nella lettura dei dati del file Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Impossibile leggere tutti i dati dell&apos;Amiibo. E&apos; stato possibile leggere solamente %2 byte di %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Errore nel caricamento dei dati dell&apos;Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Impossibile caricare i dati dell&apos;Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Cattura Screenshot</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Immagine PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Velocità: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Velocità: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Gioco: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Frame: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Il gioco che stai provando a caricare richiede ulteriori file che devono essere estratti dalla tua Switch prima di poter giocare. &lt;br/&gt;&lt;br/&gt;Per maggiori informazioni sull&apos;estrazione di questi file, visualizza la seguente pagina della wiki: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Estrazione di Archivi di Sistema e Font Condivisi da una Console Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Vuoi uscire e tornare alla lista dei giochi? Continuare l&apos;emulazione potrebbe risultare in crash, salvataggi corrotti o altri bug.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu non ha potuto individuare un archivio di sistema della Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu non ha potuto individuare un archivio di sistema della Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Archivio di Sistema Non Trovato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Archivio di Sistema Mancante</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu non ha potuto individuare i font condivisi della Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Font Condivisi Non Trovati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Font Condivisi Mancanti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Errore Fatale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu ha riscontrato un errore fatale, visualizza il log per maggiori dettagli. Per maggiori informazioni su come accedere al log, visualizza la seguente pagina: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Come Caricare Il File Log&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Vuoi uscire e tornare alla lista dei giochi? Continuare l&apos;emulazione potrebbe risultare in crash, salvataggi corrotti o altri bug.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Errore Fatale riscontrato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Conferma Riderivazione Chiave</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Stai per forzare la ri-derivazione di tutte le tue chiavi.
+Se non sai cosa significa o cosa stai per fare,
+questa azione potrebbe causare gravi problemi.
+Assicurati di volerlo fare
+e facoltativamente fai dei backup.
+
+Questo eliminerà i tuoi file di chiavi autogenerati e ri-avvierà il processo di derivazione delle chiavi.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Derivazione chiavi...
+Questa operazione potrebbe durare fino a un minuto in
+base alle prestazioni del tuo sistema.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Derivazione Chiavi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Seleziona Target dell&apos;Estrazione del RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Seleziona quale RomFS vorresti estrarre.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Sei sicuro di voler chiudere yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Sei sicuro di voler fermare l&apos;emulazione? Tutti i progressi non salvati verranno perduti.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>L&apos;applicazione in uso ha richiesto a yuzu di non uscire.
+
+Desideri uscire comunque?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilità</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Add-on</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Tipo di file</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Dimensione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Apri Cartella Dati di Salvataggio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Apri Cartella Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Apri Cache Shader Trasferibile</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Estrai RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copia il Title ID negli Appunti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Vai alla pagina di GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Proprietà</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Scansiona Sottocartelle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Rimuovi Cartella Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Apri Cartella</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfetto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Il gioco funziona perfettamente senza alcuni glitch audio o video, tutte le funzionalità testate funzionano come dovrebbero senza alcun metodo alternativo necessario.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Buono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Il gioco presenta qualche errore grafico minore o glitch nell&apos;audio ed è giocabile dall&apos;inizio alla fine. Potrebbe richiedere dei metodi alternativi.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Ok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>l gioco presenta alcuni errori gravi audio o video. E&apos; impossibile proseguire in alcune aree a causa della presenza di glitch persino utilizzando metodi alternativi. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Scadente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Il gioco funziona, ma presenta alcuni errori gravi audio o video. È impossibile proseguire in alcune aree a causa della presenza di glitch
+persino utilizzando metodi alternativi.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Il gioco è completamente ingiocabile a causa di errori gravi audio o video. È impossibile proseguire oltre la Schermata
+Iniziale.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Non si Avvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Il gioco si blocca quando viene avviato.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Non Testato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Il gioco non è ancora stato testato.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Clicca due volte per aggiungere una nuova cartella alla lista dei giochi</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Inserisci pattern per filtrare</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>387 / 1628 Shader Caricate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Caricamento di %v su %m Shader</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Tempo Stimato 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Caricamento...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>%1 / %2 Shader Caricate</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Avvio in corso...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Tempo Stimato %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;File</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>File Recenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulazione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Visualizza</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Debugging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Strumenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Aiuto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Carica File...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Carica Cartella...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>&amp;Esci</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Avvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pausa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Stop</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Ri-inizializzando le chiavi...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Riguardo yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Modalità Finestra Singola</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configura...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Visualizza le Intestazioni del Dock dei Widget</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Mostra Barra Filtri</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Mostra Barra Stato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Schermo Intero</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Riavvia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Carica Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Segnala Compatibilità</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Apri Cartella yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Cattura Screenshot</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Titoli SD Installati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Titoli NAND Installati</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Titoli di Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Aggiungi Nuova Cartella Giochi</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[non impostato]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Asse %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Pulsante %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[sconosciuto]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>Clicca 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>Clicca 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>Clicca 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>Clicca 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>Clicca 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>Assi GC %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>Pulsanti GC %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[inutilizzato]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Asse %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>Assi GC %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>E&apos; stato riscontrato un errore.
+Riprova o contatta gli sviluppatori del software.
+
+Codice Errore: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>E&apos; stato riscontrato un errore su %1 a %2.
+Riprova o contatta gli sviluppatori del software.
+
+Codice Errore: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>E&apos; stato riscontrato un errore.
+Codice Errore: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Seleziona un utente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Utenti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Selettore Profili</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Inserisci testo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Tastiera Software</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Inserisci una hotkey</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Stack chiamata</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>attende il mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>ha dei waiter: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>proprietario handle: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>attende tutti gli oggetti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>attende uno dei seguenti oggetti</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>atteso da nessun thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>in funzione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>pronto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>in pausa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>attende ritorno dell&apos;HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>dormendo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>attende una risposta dell&apos;IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>attende degli oggetti</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>attende un mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>aspettando la condition variable</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>attende un indirizzo arbitro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>dormiente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>morto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideale</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>core %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Processore sconosciuto %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>processore = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>core ideale = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>affinity mask = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id thread = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>priorità = %1(corrente) / %2(normale)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>last running ticks = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>non attende un mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>atteso dal thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Wait Tree</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/ja_JP.ts b/dist/languages/ja_JP.ts
new file mode 100644
index 000000000..4c3870603
--- /dev/null
+++ b/dist/languages/ja_JP.ts
@@ -0,0 +1,4752 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="ja_JP" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>yuzuについて</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzuはGPLv2.0の下で提供されている任天堂Switchの実験的なオープンソースエミュレータです。&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;このソフトウェアは違法に入手したゲームを遊ぶために使用されるべきではありません。&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;ウェブサイト&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;ソースコード&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;貢献者&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;ライセンス&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;任天堂 Switch&amp;quot;は任天堂の登録商標です。 yuzuは任天堂と提携しているわけではありません。&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>サーバと通信中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>キャンセル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>タッチパッドの左上を&lt;br&gt;タッチして下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>次にタッチパッドの右下を&lt;br&gt;タッチして下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>設定が完了しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>互換性を報告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>ゲームの互換性を報告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;テストケースを&lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu互換性リスト&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;に送信した場合、以下の情報が収集され、サイトに表示されます:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;ハードウェア情報(CPU/GPU/OS)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;実行中のyuzuバージョン&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;接続中のyuzuアカウント&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>パーフェクト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームはオーディオやグラフィックの不具合なしに完全に動作します。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>グレート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームの動作にはグラフィック、またはオーディオの軽微な不具合がありますが、最初から最後までプレイ可能です。いくつかの回避策が必要な場合があります。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>OK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームの動作にはグラフィック、またはオーディオの重大な不具合がありますが、回避策を使うことで最初から最後までプレイ可能です。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>NG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームは動作しますが、グラフィック、またはオーディオに重大な不具合があります。回避策を使用しても不具合が原因で特定の場所から進めなくなります。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>イントロ/メニューまで</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;グラフィック、またはオーディオの重大な不具合のため、ゲームはプレイ不可能です。スタート画面から先に進めることは出来ません。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>起動せず</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ゲームは起動時にクラッシュします。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;速度やパフォーマンスに関係なく、このゲームはこのバージョンのyuzuで最初から最後までどの程度うまく実行できていますか?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>ご協力ありがとうございます!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>送信中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>通信エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>テストケースを送信中にエラーが発生しました</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>次</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>オーディオ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>出力エンジン:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>後処理エフェクトは、エミュレーション速度と一致するようにオーディオ速度を調整し、オーディオの乱れを防ぐのに役立ちます。ただしオーディオの遅延が長くなります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>オーディオストレッチの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>オーディオデバイス:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>共通の音量設定を使用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>音量設定:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>音量:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>正確度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>正確</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>不安定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>デバッグモードを使用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>正確度は”正確”に設定することを推奨します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>CPU最適化設定(不安定)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>これらの設定は高速化のために正確性を犠牲にします。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>FMAの統合を解除(FMAを持たないCPUにおいて速度を改善する)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;このオプションは、ネイティブFMAのサポートを持たないCPUで融合積和命令の精度を下げることで速度を改善させます。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>高速FRSQRTEとFRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;このオプションは、精度の低いネイティブ近似を使用することにより、一部の近似浮動小数点関数の速度を改善させます。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>CPU設定は、ゲームが実行されていない場合にのみ使用できます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>CPUをデバッグモードに設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>CPUデバッグモードは、開発者による使用のみを目的としています。有効にしてもよろしいですか?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>CPUの最適化を切り替え</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;
+ &lt;b&gt;デバッグ用&lt;/b&gt;
+ &lt;br&gt;
+ これらの機能が何を意味するのか分からない場合は、すべて有効にしておいてください。
+ &lt;br&gt;
+ これらの設定は、CPU精度が”デバッグモード”の場合にのみ有効になります。
+ &lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>インラインページテーブルの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;この最適化により、ゲストプログラムによるメモリアクセスが高速化されます。&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;これを有効にすると、PageTable :: pointersへのアクセスが発行されたコードにインライン化されます。&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;これを無効にすると、すべてのメモリアクセスが強制的にMemory :: Read / Memory :: Write関数を経由します。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>ブロックリンクの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;この最適化では、宛先PCが静的な場合、発行された基本ブロックが他の基本ブロックに直接ジャンプできるようにすることで、ディスパッチャーのルックアップを回避します。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>リターンスタックバッファの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;この最適化は、BL命令の潜在的な戻りアドレスを追跡することにより、ディスパッチャーの検索を回避します。これは、実際のCPUのリターンスタックバッファで何が起こるかを概算します。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>高速ディスパッチャの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;2層のディスパッチシステムを有効にします。アセンブリで記述されたより高速なディスパッチャには、ジャンプ先の小さなMRUキャッシュが最初に使用されます。それが失敗した場合、ディスパッチはより遅いC ++ディスパッチャーにフォールバックします。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>コンテキスト消去の有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;CPUコンテキスト構造への不要なアクセスを削減するIR最適化を有効にします。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>定数伝播の有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;一定の伝播を伴うIR最適化を有効にします。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>その他の最適化を有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;その他のIR最適化を有効にします。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>ミスアライメントチェックの削減を有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;有効にすると、アクセスがページの境界を越えたときにのみ、ミスアライメントがトリガーされます。&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;無効にすると、ミスアライメントされたすべてのアクセスでミスアライメントがトリガーされます。&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>CPU設定は、ゲームが実行されていない場合にのみ使用できます。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>GDBスタブの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>ポート:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>ログの記録</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>グローバルログフィルター</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>ログコンソールの表示(Windowsのみ)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>ログ出力フォルダを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>自作</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>引数文字列</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>オンにすると、グラフィックAPIはより遅いデバッグモードになります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>グラフィックデバッグの有効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>オンにすると、マクロJust In Timeコンパイラが無効になります。有効にすると、ゲームの実行が遅くなります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>マクロJITの無効化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>ダンプ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>詳細なレポートサービスを有効にする</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>yuzuを終了したときに自動的にリセットされます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>拡張</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>キオスク (クエスト) モード</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>デバッグコントローラ設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>デフォルト</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>yuzuの設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>UI</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>ゲームリスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>システム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>プロファイル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>ファイルシステム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>コントロール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>ホットキー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>デバッグ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>拡張</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>拡張グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>オーディオ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>サービス</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>ストレージディレクトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SDカード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>ゲームカード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>パス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>挿入する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>現在のゲーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>パッチマネージャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>解凍されたNSOをダンプ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>ExeFSをダンプ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Modロードディレクトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>ダンプディレクトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>キャッシュ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>キャッシュディレクトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>ゲームリストのメタデータをキャッシュする</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>メタデータのキャッシュをクリアする</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>NANDディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>SDディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>ゲームカードのパスを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>ダンプディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Modロードディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>キャッシュディレクトリを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>メタデータのキャッシュはすでに空です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>操作は成功しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>メタデータのキャッシュを削除できませんでした。使用中、または存在していない可能性があります。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>速度パーセントの制限</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>マルチコアCPUエミュレーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>エミュレーション実行中の終了確認</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>ゲーム起動時に確認を表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>バックグラウンド時にエミュレーションを停止</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>未使用時にマウスを非表示</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>API設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>デバイス:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>グラフィック設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>ディスクシェーダキャッシュを使用する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>非同期GPUエミュレーションを使用する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>アスペクト比:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>デフォルト (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>4:3を強制</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>21:9を強制</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>ウィンドウに合わせる</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>共通の背景色を使用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>背景色の設定:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>背景色:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>OpenGLグラフィックデバイス</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>拡張グラフィック設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>正確性レベル:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>VSyncは画面のティアリングを防ぎますが、一部のグラフィックカードはVSyncが有効になっているとパフォーマンスが低下します。パフォーマンスの違いに気づかない場合は、有効にしてください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>VSyncを使用(OpenGLのみ)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>これを有効にすると、シェーダースタッターが減少します。サポートされているNvidiaデバイスでOpenGLアセンブリシェーダーを有効にします(NV_gpu_program5が必要です)。この機能は実験的なものです。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>アセンブリシェーダーを使用します(実験的、Nvidia OpenGLのみ)。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>非同期シェーダーコンパイルを有効にします。これにより、シェーダースタッターが減少する場合があります。この機能は実験的なものです。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>非同期シェーダー構築を使用します(実験的)。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>高速CPU時間を使用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>異方性フィルタリング:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>デフォルト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>ホットキー設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>変更するには項目をダブルクリックしてください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>すべて消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>デフォルトに戻す</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>アクション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>ホットキー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>コンテキスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>キー設定の衝突</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>入力されたキーシーケンスは既に割り当てられています:%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>デフォルトに戻す</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>デフォルトのキーシーケンスは既に割り当てられています:%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>入力設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>プレイヤー1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>プレイヤー2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>プレイヤー3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>プレイヤー4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>プレイヤー5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>プレイヤー6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>プレイヤー7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>プレイヤー8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>拡張</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>コンソールモード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>接続</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>接続解除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>バイブレーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>モーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>コントローラ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>接続</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>デフォルト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>消去</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>入力設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Joyconカラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>プレイヤー1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>L本体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>Lボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>R本体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>Rボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>プレイヤー2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>プレイヤー3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>プレイヤー4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>プレイヤー5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>プレイヤー6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>プレイヤー7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>プレイヤー8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>その他</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>キーボード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>拡張</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>タッチスクリーン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>マウス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>モーション / タッチ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>コントローラのデバッグ</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>入力設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>コントローラの接続</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>プロコントローラ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>デュアルジョイコン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>ジョイコン左</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>ジョイコン右</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>携帯</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>入力デバイス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>すべて</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>キーボード / マウス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>プロファイル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>セーブ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>新規</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>左スティック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>上</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>左</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>右</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>下</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>押下</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>変更</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>範囲</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>デッドゾーン:0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>変更範囲:0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>Dパッド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>マイナス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>キャプチャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>プラス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>ホーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>ボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>右スティック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>デッドゾーン:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>変更範囲:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[待機中]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>モーション / タッチ設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>モーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>モーションプロバイダ:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>感度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>タッチ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>タッチプロバイダ:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>キャリブレーション:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>ボタンマッピングの使用:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>CemuhookUDP設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>モーションとタッチ入力を提供するために、Cemuhook互換のUDP入力ソースを使用します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>サーバー:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>ポート:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>パッド:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>パッド1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>パッド2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>パッド3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>パッド4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>詳細情報</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>テスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>マウス(右クリック)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>エミュレータウィンドウ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;詳細情報&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>テスト中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>設定中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>テスト成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation>サーバーからデータ受信成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>テスト失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>サーバーから有効なデータを受信できませんでした。&lt;br&gt;サーバーが正しくセットアップされ、アドレスとポートが正しいことを確認してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>UDPテスト、またはキャリブレーション設定が実行中です。&lt;br&gt;完了するまでお待ちください。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>マウス設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>マウスボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>進む:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>戻る:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>左:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>中央:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>右:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>デフォルト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[未設定]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>デフォルトに戻す</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[キーを押す]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>ダイアログ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>情報</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>タイトルID</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>ファイル名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>フォーマット</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>バージョン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>サイズ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>開発者</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>アドオン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>システム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>拡張グラフィック</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>オーディオ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>プロパティ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>共通設定を使用(%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>パッチ名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>バージョン</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>プロファイルマネージャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>現在のユーザー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>ユーザー名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>画像を設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>追加</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>リネーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>プロファイル管理はゲームが実行されていない場合のみ可能です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>ユーザー名を入力</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>ユーザー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>新しいユーザーのユーザー名を入力:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>新しいユーザー名を入力:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>削除確認</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>ユーザー名”%1”を削除しようとしています。間違いありませんか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>ユーザー画像を選択 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG画像 (*.jpg *.jpeg) </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>画像削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>既存画像の上書き時にエラーが発生しました: %1 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>ファイル削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>ファイルを削除できませんでした: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>ユーザー画像ディレクトリ作成失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>ユーザー画像保存ディレクトリ”%1”を作成できませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>ユーザー画像コピーエラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>画像を”%1”から”%2”へコピー出来ませんでした。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCATは任天堂のゲームへのデータ送信方法であり、コミュニティに参加して追加コンテンツのロックを解除します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCATバックエンド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;BCAT, Boxcat, 現在のイベントについての詳細情報&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Boxcatサービスはオフラインまたはインターネットに接続されていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Boxcatイベントデータを処理中にエラー発生しました。yuzu開発者に連絡してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>使用中のyuzuのバージョンは、サーバと比較して新しすぎまたは古すぎます。最新の公式リリースに更新してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Boxcatには現在イベントがありません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>現在のBoxcatイベント</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>yuzuが最新のBoxcat状況を取得中です...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>システム設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>リージョン:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>自動</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>デフォルト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>キューバ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>エジプト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>アイルランド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>GB-Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>グリニッジ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>香港</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>アイスランド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>イラン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>イスラエル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>ジャマイカ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>日本</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>クェゼリン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>リビア</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>ナバホ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>ポーランド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>ポルトガル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>シンガポール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>トルコ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>ユニバーサル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>ズールー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>USA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>ヨーロッパ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>オーストラリア</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>中国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>韓国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>台湾</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>タイムゾーン:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>注意:地域設定が自動選択の場合、設定が上書きされる可能性があります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>日本(日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>英語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>フランス(フランス語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>ドイツ(ドイツ語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>イタリア(イタリア語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>スペイン(スペイン語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>中国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>韓国(韓国語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>オランダ(オランダ語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>ポルトガル(ポルトガル語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>ロシア(ロシア語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>台湾</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>イギリス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>カナダ・フランス語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>ラテンアメリカ・スペイン語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>簡体字中国語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>繁体字中国語(正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>カスタムRTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>言語</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>RNG速度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>モノラル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>ステレオ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>サラウンド</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>コンソールID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>サウンド出力モード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>再作成</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>システム設定はゲームが実行されていない場合のみ可能です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>現在の仮想スイッチが新しいものに置換されます。現在の仮想スイッチは復旧できません。ゲームに予期せぬ影響を与える可能性があります。古い設定などを使うと失敗するかもしれません。続行しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>警告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>コンソールID: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>タッチスクリーンマッピング設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>マッピング:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>新規</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>リネーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>下の領域をクリックしてポイントを追加し、ボタンを押してバインドします。
+ポイントをドラッグして位置を変更するか、テーブルのセルをダブルクリックして値を編集します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>ポイント削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>ボタン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>新規プロファイル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>新規プロファイルの名称を入力</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>プロファイル削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>プロファイル%1を削除しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>プロファイルリネーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>新規名称:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[キーを押す]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>タッチスクリーン設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>警告:このページの設定は、yuzuのタッチスクリーンエミュレートの内部動作に影響します。これらを変更すると、タッチスクリーンが部分的に機能しなくなったりするなど、望ましくない動作が生じる可能性があります。それらを理解した上で、このページを使用してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>タッチパラメータ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>タッチ直径Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>指</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>タッチ直径X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>回転角</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>デフォルトに戻す</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>全般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>注意:言語を変更した時点で、設定が適用されます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>UI言語:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>テーマ:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>ゲームリスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>アドオン列を表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>アイコンサイズ:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>1行目:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>2行目:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>スクリーンショット</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>スクリーンショットの保存場所を確認する(Windowsのみ)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>スクリーンショットパス:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>スクリーンショットパスを選択...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>英語</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>フォーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Webサービス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>ユーザー名とトークンを入力すると、yuzuがユーザー識別情報を含む可能性がある追加の統計情報データを収集することを許可することに同意したと見なされます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>検証</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>サインアップ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>トークン:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>ユーザー名:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>私のトークンはなんですか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>テレメトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>匿名統計情報データをyuzuチームと共有</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>詳細情報</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>テレメトリID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>再作成</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discordプレゼンス</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Discordステータスに実行中のゲームを表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;詳細情報&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;サインアップ&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;私のトークンはなんですか?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>テレメトリID:0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>未設定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>トークンは検証されていません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>トークンは検証されていません。トークンの変更はまだ保存されていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>検証中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>検証失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>検証に失敗しました。トークンを正しく入力したこと、およびインターネット接続が機能していることを確認してください。</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>yuzuを改善するための&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;匿名データが収集されました&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;統計情報データを共有しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>テレメトリ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>テキストチェック失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Webアプレットをロード中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Webアプレットを終了する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>終了</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Webアプリケーションを終了するには、ゲームが提供する&quot;終了&quot;を選択するか、メニューの&quot;Webアプレットを終了する&quot;を選択するか、&quot;Enter&quot;キーを押してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Webアプレット</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>使用中のyuzuはQtWebEngineをサポートしていないため、ゲームの説明書やWebページを正しく表示できません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>ビルド中のシェーダー数</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>現在のエミュレーション速度。値が100%より高いか低い場合、エミュレーション速度がSwitchより速いか遅いことを示します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>ゲームが現在表示している1秒あたりのフレーム数。これはゲームごと、シーンごとに異なります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Switchフレームをエミュレートするのにかかる時間で、フレームリミットやV-Syncは含まれません。フルスピードエミュレーションの場合、最大で16.67ミリ秒になります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>DOCK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>ASYNC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>MULTICORE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>最近使用したファイルを消去</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>古いゲームフォーマットの警告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>このゲームでは、分解されたROMディレクトリフォーマットを使用しています。これは、NCA、NAX、XCI、またはNSPなどに取って代わられた古いフォーマットです。分解されたROMディレクトリには、アイコン、メタデータ、およびアップデートサポートがありません。&lt;br&gt;&lt;br&gt;yuzuがサポートするSwitchフォーマットの説明については、&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;wikiをチェックしてください&lt;/a&gt;。このメッセージは二度と表示されません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>ROMロード中にエラーが発生しました!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>このROMフォーマットはサポートされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>ビデオコア初期化中にエラーが発生しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>ビデオコアの実行中にyuzuでエラーが発生しました。詳細については、ログを参照してください。ログへのアクセスの詳細については、次のページを参照してください:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;ログファイルをアップロードする方法&lt;/a&gt;。最新のグラフィックドライバを使用していることを確認して下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>ROMロードエラー!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>不明なエラーが発生しました。詳細はログを確認して下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>開始</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>データのセーブ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Modデータ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>”%1”フォルダを開けませんでした</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>フォルダが存在しません!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>シェーダキャッシュを開けませんでした</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>このタイトル用のシェーダキャッシュは存在しません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>コンテンツ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>アップデート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>エントリ削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>インストールされているゲーム%1を削除しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>削除成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>インストールされたゲームを正常に削除しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>%1削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>ゲームはNANDにインストールされておらず、削除できません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>インストールされたアップデートを正常に削除しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>このタイトルのアップデートはインストールされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>このタイトルにはDLCがインストールされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>%1にインストールされたDLCを正常に削除しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>転送可能なシェーダーキャッシュを削除しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>カスタムゲーム設定を削除しますか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>ファイル削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>転送可能なシェーダーキャッシュの削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>転送可能なシェーダーキャッシュが正常に削除されました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>転送可能なシェーダーキャッシュを削除できませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>カスタム設定の削除エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>このタイトルのカスタム設定は存在しません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>カスタムゲーム設定を正常に削除しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>カスタムゲーム設定の削除に失敗しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>RomFSの解析に失敗しました!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>RomFSファイルをコピー中にエラーが発生したか、ユーザー操作によりキャンセルされました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>フル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>スケルトン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>RomFSダンプモードの選択</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>RomFSのダンプ方法を選択してください。&lt;br&gt;”完全”はすべてのファイルが新しいディレクトリにコピーされます。&lt;br&gt;”スケルトン”はディレクトリ構造を作成するだけです。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>RomFSを解析中・・・</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>キャンセル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>RomFS解析成功!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>操作は成功しました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>”%1”を開けませんでした</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>ディレクトリの選択</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>プロパティ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>ゲームプロパティをロード出来ませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Switch実行ファイル (%1);;すべてのファイル (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>ファイルのロード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>展開されているROMディレクトリを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>無効なディレクトリが選択されました</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>選択されたディレクトリに”main”ファイルが見つかりませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>インストール可能なスイッチファイル (*.nca *.nsp *.xci);;任天堂コンテンツアーカイブ (*.nca);;任天堂サブミッションパッケージ (*.nsp);;NXカートリッジイメージ (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>ファイルのインストール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>&quot;%1&quot;ファイルをインストールしています・・・</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>インストール結果</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>システムアプリケーション</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>システムアーカイブ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>システムアプリケーションアップデート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>ファームウェアパッケージ(Type A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>ファームウェアパッケージ(Type B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>ゲーム</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>ゲームアップデート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>ゲームDLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>差分タイトル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>NCAインストール種別を選択・・・</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>インストールするNCAタイトル種別を選択して下さい:
+(ほとんどの場合、デフォルトの”ゲーム”で問題ありません。)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>インストール失敗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>選択されたNCAのタイトル種別が無効です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>ファイルが存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>ファイル”%1”が存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>続行</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>表示エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>yuzuアカウントが存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>ゲームの互換性テストケースを送信するには、yuzuアカウントをリンクする必要があります。&lt;br&gt;&lt;br/&gt;yuzuアカウントをリンクするには、エミュレーション > 設定 > Web から行います。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>URLオープンエラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>URL&quot;%1&quot;を開けません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>amiiboファイル (%1);;すべてのファイル (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>amiiboのロード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>amiiboデータファイルを開けませんでした</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>amiiboデータファイル”%1”を読み込めませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>amiiboデータファイルを読み込み中にエラーが発生した</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>amiiboデータを完全には読み取ることができませんでした。%1バイトを読み込もうとしましたが、%2バイトしか読み取れませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>amiiboデータ読み込み中にエラーが発生しました</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>amiiboデータをロードできませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>スクリーンショットのキャプチャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG画像 (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>速度:%1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>速度:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>ゲーム:%1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>フレーム:%1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>ロードしようとしているゲームはプレイする前にスイッチからダンプされた追加のファイルを必要とします。&lt;br/&gt;&lt;br/&gt;これらのファイルのダンプの詳細については、次のWikiページを参照してください:&lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;スイッチコンソールからのシステムアーカイブと共有フォントをダンプする&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;ゲームリストに戻りますか?エミュレーションを続けると、クラッシュ、保存データの破損、またはその他のバグが発生する可能性があります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzuはSwitchのシステムアーカイブ &quot;%1&quot; を見つけられませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzuはSwitchのシステムアーカイブ &quot;%1&quot; &quot;%2&quot; を見つけられませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>システムアーカイブが見つかりません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>システムアーカイブが見つかりません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzuはSwitchの共有フォント &quot;%1&quot; を見つけられませんでした。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>共有フォントが存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>共有フォントが存在しません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>致命的なエラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzuが致命的なエラーを検出しました。詳細については、ログを参照してください。ログへのアクセスの詳細については、次のページを参照してください。&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;ログファイルをアップロードする方法&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;ゲームリストに戻りますか?エミュレーションを続けると、クラッシュ、保存データの破損、またはその他のバグが発生する可能性があります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>致命的なエラー発生</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>キーの再取得確認</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>すべてのキーを再作成しようとしています。
+これが何を意味するのか分からない場合、または何をしようとしているのか分からない場合、
+これは破壊的な可能性がある実行です。
+これがあなたが望むものであることを確認し、
+そしてオプションでバックアップを作成します。
+
+これにより、自動生成されたキーファイルが削除され、キー導出モジュールが再実行されます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>ヒューズがありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation> - BOOT0がありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - BCPKG2-1-Normal-Mainがありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - PRODINFOがありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>派生コンポーネントがありません</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>コンポーネントが見つからず、キーの導出が完了しない可能性があります。&lt;br&gt;ゲームとキーを取得するために、&lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;yuzuクイックスタートガイド&lt;/a&gt;の手順に従って下さい。&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>キーを作成中...
+システムのパフォーマンスによっては
+1分以上かかります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>派生キー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>RomFSダンプターゲットの選択</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>ダンプしたいRomFSを選択して下さい。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>yuzuを終了してもよろしいですか?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>エミュレーションを停止してもよろしいですか?セーブされていない進行状況は失われます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>現在動作中のアプリケーションはyuzuに終了しないよう要求しています。
+
+無視してとにかく終了しますか?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGLは使用できません!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzuはOpenGLサポート付きでコンパイルされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkanは使用できません!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>yuzuはVulkanサポート付きでコンパイルされていません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>OpenGL4.3初期化エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>GPUがOpenGL 4.3をサポートしていないか、最新のグラフィックスドライバーではありません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>OpenGL初期化エラー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>GPUが1つ以上の必要なOpenGL拡張機能をサポートしていない可能性があります。最新のグラフィックドライバを使用していることを確認してください。&lt;br&gt;&lt;br&gt;サポートされていない拡張機能:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>互換性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>アドオン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>ファイル種別</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>サイズ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>セーブデータディレクトリを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Modデータディレクトリを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>シェーダキャッシュを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>インストールされているアップデートを削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>全てのインストールされているDLCを削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>シェーダーキャッシュを削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>カスタム設定を削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>全てのインストールされているコンテンツを削除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>RomFSをダンプ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>タイトルIDをクリップボードへコピー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>GameDBエントリを表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>プロパティ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>サブフォルダをスキャンする</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>ゲームディレクトリを削除する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ 上へ移動</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ 下へ移動</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>ディレクトリの場所を開く</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>パーフェクト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>ゲームはオーディオ、またはグラフィックの不具合なしで完璧に動作し、回避策なしで期待通りに動作します。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>グレート</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>ゲームの動作にはグラフィック、またはオーディオの軽微な不具合がありますが、最初から最後までプレイ可能です。いくつかの回避策が必要な場合があります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>OK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>ゲームの動作にはグラフィック、またはオーディオの重大な不具合がありますが、回避策を使うことで最初から最後までプレイ可能です。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>NG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>ゲームは動作しますが、グラフィック、またはオーディオに重大な不具合があります。回避策を使用しても不具合が原因で特定の場所から進めなくなります。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>イントロ/メニュー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>グラフィック、またはオーディオの重大な不具合のため、ゲームはプレイ不可能です。スタート画面から先に進めることは出来ません。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>起動せず</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>ゲームは起動時にクラッシュしました。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>未テスト</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>ゲームはまだテストされていません。</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>新しいゲームリストフォルダを追加するにはダブルクリックしてください。</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>フィルター:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>フィルターパターンを入力</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>これらがインストールするファイルであることを確認してください。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>アップデート、またはDLCをインストールすると、以前にインストールしたものが上書きされます。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>インストール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>ファイルをNANDへインストール</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>シェーダをロード中 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>シェーダをロード中 %v / %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>予想時間 5分4秒</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>ロード中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>シェーダをロード中 %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>起動中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>予想時間 %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>ファイル(&amp;F)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>最近使用したファイル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>エミュレーション(&amp;E)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>表示(&amp;V)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>デバッグ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>ツール</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>ヘルプ(&amp;H)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>ファイルをNANDへインストール...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>ファイルをロード...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>フォルダをロード...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>終了(&amp;E)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>実行(&amp;S)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>中断(&amp;P)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>停止(&amp;S)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>キーの再初期化...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>yuzuについて</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>単一ウィンドウモード</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>設定...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>ドックウィジェットヘッダーの表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>フィルターバーの表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>ステータスバーの表示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>ウィンドウサイズをリセット</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>フルスクリーン</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>再起動</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>amiiboをロード...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>互換性を報告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>Modページを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>クイックスタートガイドを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>FAQ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>yuzuディレクトリを開く</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>スクリーンショットをキャプチャ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>現在のゲームの設定...</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>マイクロプロファイル</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>インストール済みSDタイトル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>インストール済みNANDタイトル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>システムタイトル</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>新しいゲームディレクトリを追加する</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[未設定]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>十字キー %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>軸 %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>ボタン %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[不明]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>クリック0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>クリック1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>クリック2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>クリック3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>クリック4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>GC Axis %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>GC Button %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[未使用]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>軸 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>GC Axis %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>エラーが発生しました。
+もう一度試す、またはソフト開発者に連絡してください。
+
+エラーコード: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>%2 の %1 でエラーが発生しました。
+もう一度試す、またはソフト開発者に連絡してください。
+
+エラーコード: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>エラーが発生しました。
+エラーコード: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>ユーザー選択:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>ユーザー</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>プロファイル選択</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>テキストを入力:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>ソフトウェアキーボード</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>ホットキーを入力</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>コールスタック</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>ミューテックス 0x%1 待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>待機:%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>オーナーハンドル: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>全オブジェクト待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>以下のオブジェクトのうちの一つを待機中</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>スレッドなしで待機</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>実行中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>準備完了</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>中断</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>HLEリターン待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>休止中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>IPC返答待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>オブジェクト待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>ミューテックス待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>条件変数待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>アドレス決定待ち</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>休止</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>死亡</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>理想的</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>コア %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>不明なプロセッサ %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>プロセッサ = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>イデアルコア = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>アフィニティマスク = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>スレッドID = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>優先度 = %1(現在) / %2(通常)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>最終実行刻み = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>ミューテックス待ちではない</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>スレッドによる待機</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>待ちツリー</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/nl.ts b/dist/languages/nl.ts
new file mode 100644
index 000000000..0f5178458
--- /dev/null
+++ b/dist/languages/nl.ts
@@ -0,0 +1,4719 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="nl" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Over yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu is een experimentele open-bron emulator voor de Nintendo Switch, in licentie gegeven onder GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;De software zou niet gebruikt moeten worden om games te spelen die je niet legaal verkregen hebt.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Broncode&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Bijdragers&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licentie&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is een handelsmerk van Nintendo. yuzu is op geen enkele manier met Nintendo verbonden.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Communiceren met de server...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Annuleren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Rapporteer Compatibiliteit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Rapporteer Game Compatibiliteit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Als je kiest een test case op te sturen naar de &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu compatibiliteitslijst&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, zal de volgende informatie worden verzameld en getoond op de site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Informatie (CPU / GPU / Besturingssysteem)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Welke versie van yuzu je draait&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Het verbonden yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfect</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;De game werkt foutloos, zonder geluid of grafische problemen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Goed</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game werkt met kleine grafische of geluid problemen en is speelbaar van start tot eind. Kan enkele tijdelijke oplossingen nodig hebben.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>OK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game werkt met grote grafische of geluid problemen, maar is speelbaar van start tot eind met tijdelijke oplossingen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Slecht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game werkt, maar met grote grafische of geluid problemen. Specifieke gebieden kunnen niet worden uitgespeeld vanwege problemen, zelfs met tijdelijke oplossingen. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is compleet onspeelbaar dankzij grote grafische of geluid problemen. Onmogelijk voorbij het startscherm te komen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Start Niet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;De game crasht wanneer je het probeert op te starten.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Onafhankelijk van snelheid of prestaties, Hoe goed speelt de game van start tot eind op deze versie van yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Bedankt voor je inzending!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Inzenden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Communicatiefout</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Er was een fout tijdens het insturen van de Testcase.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Volgende</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Geluid</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Output Engine:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Dit nabewerkings effect past de snelheid van het geluid zodanig aan dat de snelheid van de emulatie en van het geluid op elkaar afgestemd zijn. Dit zorgt er wel voor dat de latency van het geluid hoger word.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Geluid Rekking Aanzetten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Geluid Apparaat:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>GDB Stub Aanzetten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Poort:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Loggen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Globale Log Filter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Laat Log Venster Zien (Alleen voor Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Open Log Locatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Argumenten Rij</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Gebruik Uitgebreide Rapporteer Services</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Deze optie hersteld wanneer je yuzu afsluit.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Geavanceerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Kiosk (Quest) Modus</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>yuzu Configuratie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Algemeen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>UI</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Game Lijst</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Systeem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Profielen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Bestandssysteem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Bediening</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Sneltoetsen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Debug</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Grafisch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Geluid</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Services</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Opslag Folders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SD Kaart</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Gamekaart</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Pad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Ingevoerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Huidig Spel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Patch Beheer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Dump Uitgepakte NSO&apos;s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Dump ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Mod Laad Root</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Dump Root</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Caching</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Cache Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Cache Spel Lijst Metadata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Herstel Metadata Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Selecteer Geëmuleerde NAND Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Selecteer Geëmuleerde SD Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Selecteer Gamekaart Pad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Selecteer Dump Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Selecteer Mod Laad Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Selecteer Cache Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>De metadata cache is al leeg.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>De operatie is succesvol voltooid.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>De metadata cache kon niet worden verwijderd. Het wordt mogelijk gebruikt of bestaat niet.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Algemeen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Limiteer Snelheid Percentage</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Bevestig sluiten terwijl emulatie nog bezig is</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Vraag voor gebruiker bij het opstartten van het spel.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pauzeer Emulatie wanneer yuzu op de achtergrond openstaat</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Gebruik schijf shader cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Gebruik asynchroon GPU emulatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Achtergrondkleur:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Sneltoets Instellingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Dubbel-klik op een binding om het te veranderen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Actie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Sneltoets</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Context</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Ongeldige Toets Volgorde</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Speler 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Speler 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Speler 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Speler 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Speler 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Speler 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Speler 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Speler 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Geavanceerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configureer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configureer Invoer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Linker Stick</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Gezicht Knoppen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Rechter Stick</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configureer Muis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Muisknoppen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Voorwaarts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Terug:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Links:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Middel:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Rechts:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Herstellen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[niet ingesteld]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Herstel Standaardwaarde</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[druk op knop]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Profiel Beheer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Huidige Gebruiker</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Gebruikersnaam</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Selecteer Afbeelding</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Voeg Toe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Hernoem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Verwijder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Profiel beheer is alleen beschikbaar wanneer het spel niet bezig is.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Voer een Gebruikersnaam in</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Gebruikers</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Voer een gebruikersnaam in voor de nieuwe gebruiker:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Voer nieuwe gebruikersnaam in:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Bevestig Verwijdering</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Je staat op het punt een gebruiker met de naam &quot;%1&quot; te verwijderen. Weet je het zeker?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Selecteer gebruiker&apos;s foto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG foto&apos;s (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Fout tijdens verwijderen afbeelding</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Er is een fout opgetreden bij het overschrijven van de vorige afbeelding in: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Fout tijdens verwijderen bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Kan bestaand bestand niet verwijderen: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Fout tijdens het maken van de map met afbeeldingen van de gebruiker</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Fout tijdens het maken van map %1 om gebruikersafbeeldingen in te bewaren.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Fout tijdens het kopiëren van de gebruiker afbeelding</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Kan afbeelding niet kopiëren van %1 naar %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT is Nintendo&apos;s manier om informatie naar games te versturen om de gemeenschap te stimuleren of om nieuwe inhoud te ontgrendelen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT Backend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Leer meer over BCAT, Boxcat, en actuele Evenementen&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>De boxcat service is offline of je bent niet verbonden met het internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Er was een fout tijdens het processen van de boxcat evenement data. Contacteer de yuzu ontwikkelaars.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>De versie van yuzu die je gebruikt is te nieuw of te oud voor de server. Probeer te updaten naar de nieuwste officiële release van yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Er zijn momenteel geen evenementen op boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu haalt de laatste boxcat-status op...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Systeeminstellingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Noot: dit kan worden overschreven wanneer de regio instelling op automatisch selecteren staat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japans (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Engels (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Frans (Français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Duits (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiaans (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Spaans (Español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinees (正體中文 / 简体中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Koreaans (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Nederlands (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugees (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russisch (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanese</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Brits Engels</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Canadees Frans</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Latijns Amerikaans Spaans</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Vereenvoudigd Chinees</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Traditioneel Chinees (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>Handmatige RTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Taal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>RNG Seed</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>Console ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Geluid uitvoer mode</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM jjjj u:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Herstel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Systeeminstellingen zijn enkel toegankelijk wanneer er geen game draait.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Dit vervangt je huidige virtuele Switch met een nieuwe. Je huidige virtuele Switch kan dan niet meer worden hersteld. Dit kan onverwachte effecten hebben in spellen. Dit werkt niet als je een oude config savegame gebruikt. Doorgaan?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Waarschuwing</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Console ID: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configureer Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Waarschuwing: Instellingen in deze pagina hebben invloed op de interne werking van yuzu&apos;s geemuleerde touchscreen. Veranderingen kunnen ongewenste resultaten hebben, zoals ervoor zorgen dat het touchscreen half of niet werkt. Gebruik deze pagina enkel als je weet wat je doet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Touch Parameters</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Touch Diameter Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Vinger</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Touch Diameter X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Rotatiehoek</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Herstel Standaardwaardes</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Algemeen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Notitie: De taal veranderen past uw configuratie toe.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Interface taal:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Thema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Game Lijst</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Toon Add-Ons Kolom</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Icoon Grootte:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Rij 1 Text:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Rij 2 Text:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Engels</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Vorm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Web Service</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Door je gebruikersnaam en token te geven, ga je akkoord dat yuzu extra gebruiksdata verzameld, waaronder mogelijk gebruikersidentificatie-informatie.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verifieer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Registreer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Gebruikersnaam:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Wat is mijn token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Deel anonieme gebruiksdata met het yuzu team</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Leer meer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>Telemetrie ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Regenereer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discord Presence</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Toon huidige game in je Discord status</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Leer meer&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Registreer&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Wat is mijn token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>Telemetrie ID: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Niet gespecificeerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token niet geverifieerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Token is niet geverifieerd. De verandering aan uw token zijn niet opgeslagen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verifiëren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verificatie mislukt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verificatie mislukt. Check dat uw token correct is en dat uw internet werkt.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Annonieme gegevens worden verzameld&lt;/a&gt; om yuzu te helpen verbeteren. &lt;br/&gt;&lt;br/&gt; Zou je jouw gebruiksgegevens met ons willen delen?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetrie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Tekst Check Is Gefaald</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Web Applet Laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Web Applet Afsluit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Afsluiten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Om de webtoepassing af te sluiten, gebruik de bedieningselementen van het spel om afsluiten te selecteren, selecteer de optie &apos;Web Applet Afsluiten&apos; in de menubalk of druk op de &apos;Enter&apos; toets.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Deze versie van yuzu is gebouwd zonder ondersteuning van QtWebEngine, wat betekent dat yuzu de gevraagde spelhandleiding of webpagina niet correct kan weergeven.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Huidige emulatie snelheid. Waardes hoger of lager dan 100% betekent dat de emulatie sneller of langzamer loopt dan de Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Hoeveel frames per seconde de game op dit moment weergeeft. Dit zal veranderen van game naar game en van scène naar scène.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tijd gebruikt om een frame van de Switch te emuleren, waarbij framelimiteren of v-sync niet wordt meegerekend. Voor emulatie op volledige snelheid zou dit maximaal 16.67 ms zijn.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Recente Bestanden Verwijderen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Waarschuwing Verouderd Spel Formaat</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Je gebruikt gedeconstrueerd ROM map formaat voor dit Spel, dit is een verouderd formaat en is vervangen door formaten zoals NCA, NAX, XCI of NSP. Gedeconstrueerd ROM map heeft geen iconen, metadata en update understeuning.&lt;br&gt;&lt;br&gt;Voor een uitleg over welke Switch formaten yuzu ondersteund, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;kijk op onze wiki&lt;/a&gt;. Dit bericht word niet nog een keer weergegeven.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Fout tijdens het laden van een ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Het formaat van de ROM is niet ondersteunt.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Er is een fout opgetreden tijdens het initialiseren van de videokern.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu is een fout tegengekomen tijdens het uitvoeren van de video kern, kijk alstublieft naar de log for meer details. Voor meer informatie op het vinden van de log, kijk alstublieft naar de volgende pagina : &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;hoe upload je een log bestand&lt;/a&gt;. Bevestig dat je dat laaste grafische driver hebt voor je GPU.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Een onbekende fout heeft plaatsgevonden. Kijk in de log voor meer details.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Save Data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Mod Data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Fout tijdens het openen van %1 folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Folder bestaat niet!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Fout Bij Het Openen Van Overdraagbare Shader Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Er bestaat geen shader cache voor deze game</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>RomFS Extractie Mislukt!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Er was een fout tijdens het kopiëren van de RomFS bestanden of de gebruiker heeft de operatie geannuleerd.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Vol</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Skelet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Selecteer RomFS Dump Mode</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Selecteer alstublieft hoe je de RomFS wilt dumpen.&lt;br&gt;Volledig kopieërd alle bestanden in een map terwijl &lt;br&gt; skelet maakt alleen het map structuur.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>RomFS uitpakken...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Annuleren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>RomFS Extractie Geslaagd!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>De operatie is succesvol voltooid.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Fout bij openen %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Selecteer Map</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Eigenschappen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>De eigenschappen van de game kunnen niet geladen worden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Switch Executable (%1);;Alle bestanden (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Laad Bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Open Gedecomprimeerd ROM Map</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Ongeldige Map Geselecteerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>De map die je hebt geselecteerd bevat geen &apos;main&apos; bestand.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Bestand &quot;%1&quot; Installeren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Systeem Applicatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Systeem Archief</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Systeem Applicatie Update</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Filmware Pakket (Type A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Filmware Pakket (Type B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Game</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Game Update</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>Game DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Delta Titel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Selecteer NCA Installatie Type...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Selecteer het type titel hoe je wilt dat deze NCA installeerd:
+(In de meeste gevallen is de standaard &apos;Game&apos; juist.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Installatie Mislukt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Het type title dat je hebt geselecteerd voor de NCA is ongeldig.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Bestand niet gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Bestand &quot;%1&quot; niet gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Doorgaan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Fout Weergave</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Je yuzu account mist</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Om game campatibiliteit te raporteren, moet je je yuzu account koppelen.&lt;br&gt;&lt;br/&gt; Om je yuzu account te koppelen, ga naar Emulatie &amp;gt; Configuratie &amp;gt; Web.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Amiibo Bestand (%1);; Alle Bestanden (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Laad Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Fout tijdens het openen van het Amiibo gegevens bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Kan Amiibo bestand &quot;%1&quot; niet lezen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Fout tijdens het lezen van het Amiibo gegevens bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Kan de volledige Amiibo gegevens niet lezen. Verwacht om %1 bytes te lezen, maar het is alleen mogelijk om %2 bytes te lezen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Fout tijdens het laden van de Amiibo data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Kan de Amiibo gegevens niet laden.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Screenshot Vastleggen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG afbeelding (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Snelheid: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Snelheid: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Game: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Frame: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>De game die je probeert te laden heeft extra bestanden nodig van je Switch voordat je het kan spelen. &lt;br/&gt;&lt;br/&gt;Voor meer informatie over het dumpen van deze bestanden, volg alsjeblieft onze wiki pagina: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Het dumpen van Systeem Archieven en de Gedeelde Lettertypen van een Switch console &lt;/a&gt;. &lt;br/&gt;&lt;br/&gt;Wil je terug gaan naar de game lijst? Verdergaan met de emulatie zal misschien gevolgen hebben als vastlopen, beschadigde opslag data, of andere problemen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu was niet in staat om de Switch systeem archieven te vinden. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu was niet in staat om de Switch systeem archieven te vinden. %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Systeem Archief Niet Gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Systeem Archief Mist</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu was niet in staat om de Switch shared fonts te vinden. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Shared Fonts Niet Gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Gedeelde Lettertypes Niet Gevonden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Fatale Fout</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu is een fatale fout tegengekomen, zie de log voor meer details. Voor meer informatie over toegang krijgen tot de log, zie de volgende pagina: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Hoe upload je een log bestand&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Zou je terug willen naar de game lijst? Doorgaan met emulatie kan resulteren in vastlapen, corrupte save gegevens, of andere problemen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Fatale Fout opgetreden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Bevestig Sleutel Herafleiding</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Je bent op het punt al je sleutels geforceerd opnieuw te verkrijgen.
+Als je niet weet wat dit doet of wat je aan het doen bent,
+dit is potentieel een vernietigende actie.
+Zorg ervoor dat je zeker weet dat dit is wat je wilt doen
+en optioneel maak backups.
+
+Dit zal je automatisch gegenereerde sleutel bestanden verwijderen en de sleutel verkrijger module opnieuw starten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Dit zal misschien een paar minuten duren gebaseerd
+op je systeem&apos;s performatie.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Sleutels afleiden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Selecteer RomFS Dump Doel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Selecteer welke RomFS je zou willen dumpen.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Weet je zeker dat je yuzu wilt sluiten?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Weet je zeker dat je de emulatie wilt stoppen? Alle onopgeslagen voortgang will verloren gaan.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>De momenteel actieve toepassing heeft yuzu gevraagd om niet af te sluiten.
+
+Wilt u dit omzeilen en toch afsluiten?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Naam</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibiliteit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Toevoegingen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Bestands type</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Grootte</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Open Locatie Van Save Gegevens </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Open Mod Data Locatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Open Overdraagbare Shader Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Dump RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Kopieer Titel ID naar Klembord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Navigeer naar GameDB inzending</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Eigenschappen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Scan Subfolders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Verwijder Game Directory</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Open Directory Locatie</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfect</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Game functioneerd foutloos, zonder auditieve of grafische glitches. Alle geteste functionaliteit werkt zoals bedoeld zonder dat er tijdelijke oplossingen nodig zijn.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Goed</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Game werkt met kleine grafische of auditieve glitches en is speelbaar van start tot eind. Kan enkele workarounds nodig hebben.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Okay</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Game werkt met grove grafische of auditieve glitches, maar is speelbaar van start tot eind met workarounds</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Slecht</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Game werkt, maar met grove grafische of auditieve glitches. Specifieke gebieden kunnen niet worden uitgespeeld vanwege glitches, zelfs met workarounds.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Game is compleet onspeelbaar dankzij grove grafische of auditieve glitches. Onmogelijk voorbij het startscherm te gaan.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Start Niet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>De Game crasht wanneer hij probeert op te starten.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Niet Getest</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Deze Game is nog niet getest.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Dubbel-klik om een ​​nieuwe map toe te voegen aan de lijst met games</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filter:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Voer patroon in om te filteren:</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Shaders Laden 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>%v Shaders Laden van de %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Geschatte Tijd 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Laden...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Shaders Laden %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Starten...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Geschatte Tijd %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Bestand</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Recente Bestanden</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulatie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Weergeven</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Debugging</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Hulpmiddelen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Help</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Laad Bestand...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Laad Folder...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>A&amp;fsluiten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pauzeren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Stop</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Sleutels opnieuw initialiseren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Over yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Enkel Venster Modus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configureren...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Dock Widget Rubriek Weergeven</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Laat filter balk zien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Laat status balk zien</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Volledig Scherm</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Herstart</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Laad Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Raporteer Compatibiliteit</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Open yuzu Folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Screenshot Vastleggen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfiel</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Geïnstalleerde SD Titels</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Geïnstalleerde NAND Titels</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Systeem Titels</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Voeg Nieuwe Game Map Toe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[niet aangegeven]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Axis %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Knop %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[onbekend]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[ongebruikt]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Axis %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Er is een fout opgetreden.
+Probeer het opnieuw of neem contact op met de ontwikkelaar van de software.
+
+Fout Code: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Er is een fout opgetreden bij %1 en %2.
+Probeer het opnieuw of neem contact op met de ontwikkelaar van de software.
+
+Fout Code: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Er is een fout opgetreden.
+Fout Code: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Selecteer een gebruiker:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Gebruikers</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Profiel keuzeschakelaar</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Voer tekst in:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Software Toetsenbord</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Voer een hotkey in</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Call stack</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>wachten op mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>heeft wachtende: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>eigenaar handvat: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>wachten op alle objecten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>wachten op een van de volgende objecten</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>Bezig met uitvoeren</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>klaar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>gepauzeerd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>wachten op HLE terugkeer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>slapen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>wachten op IPC antwoord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>wachten op objecten</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>wachten op mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>wachten op conditie variabele</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>wachten op adres arbiter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>slapend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>dood</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideaal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>kern %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Onbekende processor %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>processor = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>ideale kern = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>affiniteit masker = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>draad id = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>prioriteit = %1(huidige) / %2(normaal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>laatste lopende ticks = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>Niet wachtend op mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>Wachtend door draad</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Wacht Boom</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/pl.ts b/dist/languages/pl.ts
new file mode 100644
index 000000000..4cd9b6fca
--- /dev/null
+++ b/dist/languages/pl.ts
@@ -0,0 +1,4713 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="pl" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>O yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu jest eksperymentalnym emulatorem o otwartym kodzie źródłowym dla Nintendo Switch w ramach licencji GLPv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;To oprogramowanie nie powinno być używane do gier, których nie nabyłeś legalnie.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Strona&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Kod źródłowy&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Współautorzy&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licencja&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; jest znakiem towarowym Nintendo. yuzu nie jest stowarzyszony w żaden sposób z Nintendo.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Anuluj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Konfiguracja zakończona!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Zgłoś kompatybilność</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Zgłoś kompatybilność gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Jeśli postanowisz wysłać wyniki testu na &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;listę kompatybilności yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, następujące informacja zostaną zebrane i wyświetlone na stronie:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informacja o sprzęcie komputerowym (procesor / karta graficzna / system operacyjny)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Wersja yuzu, której używasz&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Połączone konto yuzu&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Idealna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra działa bez zarzutu, bez błędów graficznych lub dźwiękowych.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Świetna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra działa z pomniejszymi błędami dźwiękowymi lub graficznymi, jest grywalna od początku do końca. Może wymagać kilku obejść/poprawek.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>W porządku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra działa z większymi błędami dźwiękowymi lub graficznymi, ale jest grywalna od początku do końca. Może wymagać kilku obejść/poprawek.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Zła</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra działa z większymi błędami dźwiękowymi lub graficznymi. Niemożliwe jest przejście konkretnych miejsc nawet z obejściami.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra w żadnym stopniu nie jest grywalna ze względu na poważne błędy graficzne lub dźwiękowe. Niemożliwe jest przejście ekranu początkowego.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Nie uruchomia się</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Gra zawiesza się podczas próby uruchomienia się.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pomijając prędkość lub wydajność, jak dobrze ta gra zachowuje się w tej wersji yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Dziękujemy za Twoją opinię!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Wysyłanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Błąd komunikacyjny</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Wystąpił błąd podczas wysyłania wyników testu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Następny</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Dźwięk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Silnik wyjścia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Ten efekt post-przetwarzania dostosowuje prędkość dźwięku tak, aby zgadzała się z dźwiękiem emulowanym pomagając przy tym zapobiec zacinaniu się dźwięku. Zwiększa jednak opóźnienie dźwięku.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Włącz rozciąganie dźwięku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Urządzenie dźwiękowe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Ustaw głośność:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Głośność</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Ogólne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Niebezpieczne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Włącz Debug Mode</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Ustawienia CPU są dostępny gdy gra nie jest uruchomiona </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Włącz namiastkę GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Port</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Logowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Globalny filtr rejestrów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Pokaż okno rejestrów (tylko Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Otwórz miejsce rejestrów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Linijka argumentu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Zaawansowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Wyczyść</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>Domyślne</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Ustawienia yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Ogólne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>UI</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista Gier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>System</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Profile</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>System plików</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Sterowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Skróty klawiszowe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Wyszukiwanie usterek</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Grafika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>Zaawansowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Dźwięk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Usługi</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Karta SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Gamecard</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Ścieżka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Obecna Gra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Ogólne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Emulacja CPU Wielordzeniowa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Potwierdź wyjście podczas emulacji</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>Ustawienia API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Urządzenie:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Ustawienia Graficzne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>Format obrazu:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Domyślne (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>Wymuś 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>Wymuś 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>Rozciągnij do Okna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Ustaw kolor tła:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Kolor tła</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>Urządzenie graficzne OpenGL</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>Domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Ustawienia Skrótów Klawiszowych</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>Wyczyść wszystko</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>Przywróć domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Akcja</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Skrót klawiszowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Kontekst</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>Przywróć ustawienia domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>Wyczyść</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Gracz 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Gracz 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Gracz 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Gracz 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Gracz 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Gracz 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Gracz 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Gracz 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Zaawansowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Tryb Konsoli</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Zadokowany</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Niezadokowany</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Wibracje</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>Ruch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Ustaw</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>Kontrolery</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Połączone</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>Wyczyść</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Ustawienie wejścia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Kolory Joyconów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Gracz 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Gracz 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Gracz 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Gracz 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Gracz 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Gracz 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Gracz 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Gracz 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Inne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Klawiatura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Zaawansowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Ekran dotykowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Mysz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>Ruch / Dotyk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Konfiguruj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Debuguj kontroler</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Ustawienie wejścia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Połacz Kontroler</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>Para Joyconów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>Lewy Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>Prawy Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>Handheld</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>Urządzenie Wejściowe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>Klawiatura/Mysz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>Profil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Zapisz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Nowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Usuń</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Lewa gałka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Góra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Lewo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Prawo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Dół</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Naciśnięty</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modyfikator</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>Zasięg</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>Martwa strefa: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Zasięg Modyfikatora: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>D-Pad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Minus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Zrzut ekranu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Przednie klawisze</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Prawa gałka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>Martwa strefa: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Zasięg Modyfikatora: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[oczekiwanie]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Konfiguruj Ruch / Dotyk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>Ruch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Czułość:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Dotyk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Kalibracja:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Konfiguruj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>Serwer:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>Port:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Pad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>Dowiedz się więcej</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>Test</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>Myszka (Prawy Przycisk)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>Testowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>Konfigurowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>Test Udany</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Ustaw mysz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Przyciski myszy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Do przodu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Do tyłu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Lewy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Środkowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Prawy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Wyczyść</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>Domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[nie ustawione]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Przywróć ustawienie domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[naciśnij przycisk]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>Informacje</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>Nazwa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>Identyfikator gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>Nazwa pliku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>Format</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>Wersja</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>Rozmiar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>Deweloper</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>Dodatki</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>Ogólne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>System</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>Grafika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>Dźwięk</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>Właściwości</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>Wersja</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Menedżer Profili</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Obecny użytkownik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nazwa Użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Ustaw zdjęcie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Dodaj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Zmień nazwę</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Usuń</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Menedżer Profili nie jest dostępny gdy gra jest uruchomiona.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Wpisz nazwę użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Użytkownicy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Wprowadź nazwę dla nowego użytkownika:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Wpisz nową nazwę użytkownika:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Potwierdź usunięcie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Zamierzasz usunąć użytkownika &quot;%1&quot;. Jesteś pewien?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Ustaw zdjęcie użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Obrazki JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Bład usunięcia zdjęcia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Błąd podczas próby nadpisania poprzedniego zdjęcia dla: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Błąd usunięcia pliku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Nie można usunąć istniejącego pliku: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Błąd podczas tworzenia folderu ze zdjęciem użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Nie można utworzyć ścieżki %1 do przechowywania zdjęć użytkownika.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Błąd kopiowania zdjęcia użytkownika</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Nie można skopiować zdjęcia z %1 do %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Ustawienia systemu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>Region:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>Automatyczny</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>Domyślne</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>Cuba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>Egipt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>Irlandia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>Greenwich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>Hongkong</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>Islandia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>Iran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>Izrael</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>Jamajka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>Japonia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>Kwajalein</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>Libia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>Navajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>Polska</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>Portugalia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Uwaga: można to zmienić, gdy ustawienie regionu jest wybierane automatycznie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japoński (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Angielski (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francuski (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Niemiecki (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Włoski (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Hiszpański (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chiński</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Koreański (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Duński (Holandia)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Portugalski (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Rosyjski (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Tajwański</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Angielski (Brytyjski)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Fancuski (Kanada)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Hiszpański (Latin American)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chiński (Uproszczony)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chiński tradycyjny (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Język</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Ziarno RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Stereo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>Indentyfikator konsoli:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Tryb wyjścia dźwięku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Wygeneruj ponownie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Ustawienia systemu są dostępne tylko wtedy, gdy gra nie jest uruchomiona.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>To zamieni twojego obecnego Switch&apos;a z nowym. Twojego obecnego Switch&apos;a nie będzie można przywrócić. To może wywołać nieoczekiwane problemy w grach. To może nie zadziałać, jeśli używasz nieaktualnej konfiguracji zapisu gry. Kontynuować?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Ostrzeżenie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Identyfikator konsoli: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Skonfiguj ekran dotykowy</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Ostrzeżenie: Ustawienia na tej stronie mają wpływ na działanie emulowanego ekranu dotykowego Yuzu. ch zmiana może spowodować niepożądane zachowanie, takie jak częściowo lub całkowicie nie działający ekran dotykowy. Powinieneś/naś używać tej strony tylko wtedy, gdy wiesz, co robisz.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parametry dotyku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Średnica dotyku Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Palec</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Średnica dotyku X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Kąt rotacji</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Przywróć domyślne</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista Gier</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Angielski</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Forma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Usługa internetowa yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Podając swoją nazwę użytkownika i token, zgadzasz się na umożliwienie yuzu zbierania dodatkowych danych o użytkowaniu, które mogą zawierać informacje umożliwiające identyfikację użytkownika.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Zweryfikuj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Zaloguj się</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nazwa użytkownika:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Czym jest mój token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Udostępniaj anonimowe dane o użytkowaniu zespołowi yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Dowiedz się więcej</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>Identyfikator telemetrii:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Wygeneruj ponownie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Obecność na discordzie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Pokazuj obecną grę w twoim statusie Discorda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Dowiedz się więcej&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Zaloguj się&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Czym jest mój token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>Identyfikator telemetrii: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Weryfikowanie...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Weryfikowanie nieudane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Dane anonimowe są gromadzone&lt;/a&gt; aby ulepszyć yuzu. &lt;br/&gt;&lt;br/&gt;Czy chcesz udostępnić nam swoje dane o użytkowaniu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Sprawdzanie tekstu nie powiodło się</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Aktualna prędkość emulacji. Wartości większe lub niższe niż 100% wskazują, że emulacja działa szybciej lub wolniej niż Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Ile klatek na sekundę gra aktualnie wyświetla. To będzie się różnić w zależności od gry, od sceny do sceny.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Czas potrzebny do emulacji klatki na sekundę Switcha, nie licząc ograniczania klatek ani v-sync. Dla emulacji pełnej szybkości powinno to wynosić co najwyżej 16,67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Usuń &quot;Ostatnie pliki&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>OSTRZEŻENIE! Nieaktualny format gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Używasz zdekonstruowanego formatu katalogu ROM dla tej gry, który jest przestarzałym formatem, który został zastąpiony przez inne, takie jak NCA, NAX, XCI lub NSP. W zdekonstruowanych katalogach ROM brakuje ikon, metadanych i obsługi aktualizacji.&lt;br&gt;&lt;br&gt; Aby znaleźć wyjaśnienie różnych formatów Switch obsługiwanych przez yuzu,&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt; sprawdź nasze wiki&lt;/a&gt;. Ta wiadomość nie pojawi się ponownie.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Błąd podczas wczytywania ROMu!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Ten format ROMu nie jest wspierany.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Wystąpił błąd podczas inicjowania rdzenia wideo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu napotkał błąd podczas działania rdzenia wideo, proszę zobaczyć log po więcej szczegółów. Aby uzyskać więcej informacji na temat uzyskiwania dostępu do pliku log, zobacz następującą stronę: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Jak przesłać plik log? &lt;/a&gt;Upewnij się, że masz najnowsze sterowniki karty graficznej.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Wystąpił nieznany błąd. Więcej informacji można znaleźć w pliku log.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Błąd podczas otwarcia folderu %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Folder nie istnieje!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Wypakowanie RomFS nieudane!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Wystąpił błąd podczas kopiowania plików RomFS lub użytkownik anulował operację.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Wybierz tryb zrzutu RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Proszę wybrać w jaki sposób chcesz, aby zrzut pliku RomFS został wykonany. &lt;br&gt;Pełna kopia ze wszystkimi plikami do nowego folderu, gdy &lt;br&gt;skielet utworzy tylko strukturę folderu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Wypakowywanie RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Anuluj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Wypakowanie RomFS zakończone pomyślnie!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>Operacja zakończona sukcesem.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Wybierz folder...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Właściwości</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Właściwości tej gry nie mogły zostać załadowane.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Plik wykonywalny Switcha (%1);;Wszystkie pliki (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Załaduj plik...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Otwórz folder wypakowanego ROMu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Wybrano niewłaściwy folder</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Folder wybrany przez ciebie nie zawiera &apos;głownego&apos; pliku.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Instalowanie pliku &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Aplikacja systemowa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Archiwum systemu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Aktualizacja aplikacji systemowej</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Paczka systemowa (Typ A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Paczka systemowa (Typ B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Gra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Aktualizacja gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>Dodatek do gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Tytuł Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Wybierz typ instalacji NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Wybierz typ tytułu, do którego chcesz zainstalować ten NCA, jako:
+(W większości przypadków domyślna &quot;gra&quot; jest w porządku.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Instalacja nieudana</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Typ tytułu wybrany dla NCA jest nieprawidłowy.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Nie znaleziono pliku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Nie znaleziono pliku &quot;%1&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Kontynuuj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Brakuje konta Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Aby przesłać test zgodności gry, musisz połączyć swoje konto yuzu.&lt;br&gt;&lt;br/&gt; Aby połączyć swoje konto yuzu, przejdź do opcji Emulacja &amp;gt; Konfiguracja &amp;gt; Sieć.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Plik Amiibo (%1);;Wszyskie pliki (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Załaduj Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Błąd otwarcia pliku danych Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Nie można otworzyć pliku Amiibo &quot;%1&quot; do odczytu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Błąd podczas odczytu pliku danych Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Nie można w pełni odczytać danych Amiibo. Oczekiwano odczytu %1 bajtów, ale był on w stanie odczytać tylko %2 bajty.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Błąd podczas ładowania pliku danych Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Nie można załadować danych Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Zrób zrzut ekranu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Obrazek PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Prędkość: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Prędkość: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Gra: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Klatka: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Gra, którą próbujesz wczytać, wymaga dodatkowych plików z Switch&apos;a, które zostaną zrzucone przed graniem.&lt;br/&gt;&lt;br/&gt; Aby uzyskać więcej informacji na temat wyrzucania tych plików, odwiedź następującą stronę wiki:&lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt; Zrzut archiw systemu i udostępnionych czcionek z konsoli Nintendo Switch&lt;/a&gt;. &lt;br/&gt;&lt;br/&gt;Czy chcesz wrócić do listy gier? Kontynuacja emulacji może spowodować awarie, uszkodzone dane zapisu lub inne błędy.
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Archiwum systemu nie znalezione.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Czcionki nie zostały znalezione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Fatalny błąd</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu napotkał błąd, proszę zobaczyć log po więcej szczegółów. Aby uzyskać więcej informacji na temat uzyskiwania dostępu do pliku log, zobacz następującą stronę: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Jak przesłać plik log&lt;/a&gt;?&lt;br/&gt;&lt;br/&gt; Czy chcesz wrócić do listy gier? Kontynuacja emulacji może spowodować awarie, uszkodzone dane zapisu lub inne błędy.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Potwierdź ponowną aktywacje klucza</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Zamierzasz zmusić wszystkie swoje klucze do ponownej aktywacji.
+Jeśli nie wiesz, co to oznacza i co robisz,
+jest to potencjalnie destrukcyjne działanie.
+Upewnij się, że to jest to, czego chcesz
+i opcjonalnie tworzyć kopie zapasowe.
+
+Spowoduje to usunięcie wygenerowanych automatycznie plików kluczy i ponowne uruchomienie modułu pochodnego klucza.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Wyprowadzanie kluczy...
+Zależnie od tego może potrwać do minuty
+na wydajność twojego systemu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Wyprowadzanie kluczy...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Wybierz cel zrzutu RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Proszę wybrać RomFS, jakie chcesz zrzucić.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Czy na pewno chcesz zamknąć yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Czy na pewno chcesz zatrzymać emulację? Wszystkie niezapisane postępy zostaną utracone.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nazwa gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Kompatybilność</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Dodatki</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Typ pliku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Rozmiar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Otwórz lokalizację zapisów</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Otwórz lokalizację modyfikacji</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Zrzuć RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Kopiuj identyfikator gry do schowka</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Nawiguj do wpisu kompatybilności gry</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Właściwości</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfekcyjnie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Funkcje gry są bezbłędne, bez żadnych zakłóceń audio i graficznych, wszystkie przetestowane funkcje działają zgodnie z przeznaczeniem bez
+wszelkich potrzebnych obejść.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Świetnie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Funkcje gry z drobnymi usterkami graficznymi lub dźwiękowymi i można je odtwarzać od początku do końca. Może wymagać niektórych
+obejść.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>W porządku</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Funkcje gry z dużymi usterkami graficznymi lub dźwiękowymi, ale gra jest odtwarzana od początku do końca z użyciem
+obejść.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Zła</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Funkcje gry, ale z dużymi usterkami graficznymi lub dźwiękowymi. Nie można wykonać postępu w określonych obszarach z powodu problemów
+nawet z obejściami.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>Gra jest całkowicie niemożliwa do zagrania z powodu poważnych usterków graficznych lub dźwiękowych. Nie można przejść ekran
+startowy.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Nie uruchamia się</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Ta gra się zawiesza przy próbie startu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Nie testowane</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Ta gra nie została jeszcze przetestowana.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Kliknij podwójnie aby dodać folder do listy gier</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filter:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Wpisz typ do filtra</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Uruchamianie...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Plik</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Ostatnie pliki</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulacja</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Widok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Debugowanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Narzędzia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Pomoc</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Załaduj plik...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Załaduj folder...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>&amp;Wyjście</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pauza</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Stop</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Reinicjalizuj klucze...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>O yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Tryb jednego okna</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Konfiguruj...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Wyświetlaj nagłówki Dock Widget</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Pokaż pasek fitrowania</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Pokaż pasek statusu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Pełny ekran</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Zrestartuj</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Załaduj Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Zgłoś kompatybilność</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Otwórz folder yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Zrób zrzut ekranu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[nie ustawione]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Krzyżak %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Oś %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Przycisk %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[nieznane]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[nieużywane]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Oś %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Wybierz użytkownika:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Wybór profilu</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Wpisz tekst:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Klawiatura systemowa</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Stos wywołań</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>czekam na mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>ma oczekujących: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>uchwyt właściciela: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>czekam na wszystkie objekty</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>oczekiwanie na jeden z następujących obiektów</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>Uruchomione</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>Gotowe</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>Spauzowana</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>czekam na powrót HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>spanie</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>czekam na odpowiedź IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>oczekiwanie na obiekty</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>czekam na mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>czekam na arbitra adresu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>drzemiący</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>śmierć</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>rdzeń %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Nieznany procesor %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>procesor = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>idealny rdzeń = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>maska powinowactwa = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>identyfikator wątku = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>piorytet = %1(obecny) / %2(normalny)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>ostatnie działające kleszcze = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>nie czekam na mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>czekanie na wątek</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Drzewo czekania</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/pt_BR.ts b/dist/languages/pt_BR.ts
new file mode 100644
index 000000000..c842793f4
--- /dev/null
+++ b/dist/languages/pt_BR.ts
@@ -0,0 +1,4757 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="pt_BR" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Sobre o yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu é um emulador experimental de código aberto para o Nintendo Switch licenciado sob a GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Esse programa não deve ser utilizado para jogar jogos que você não obteve legalmente.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Site&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Código fonte&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contribuidores&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licença&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; é uma marca comercial da Nintendo. O yuzu não é afiliado com a Nintendo de nenhuma forma.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>Comunicando com o servidor...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Toque no canto superior esquerdo &lt;br&gt;do seu touchpad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Agora toque no canto inferior direito &lt;br&gt;do seu touchpad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>Configuração concluída!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>OK</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Informar compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Informar compatibilidade de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Ao enviar um caso de teste à &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;lista de compatibilidade do yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, as seguintes informações serão recolhidas e exibidas no site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informações de hardware (CPU / GPU / sistema operacional)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Qual versão do yuzu você está usando&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A conta do yuzu conectada&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfeito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona impecavelmente, sem falhas no áudio ou nos gráficos.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Ótimo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona com leves falhas gráficas ou de áudio e é jogável do início ao fim. Pode exigir algumas soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Razoável</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona com grandes falhas gráficas ou de áudio, mas é jogável do início ao fim com o uso de soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Ruim</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona, só que com problemas significativos nos gráficos ou no áudio. Não é possível progredir em áreas específicas por conta de tais problemas, mesmo com soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;É impossível jogar o jogo devido a grandes problemas nos gráficos ou no áudio. Não é possível passar da tela inicial do jogo.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Não inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo trava ou se encerra abruptamente ao se tentar iniciá-lo.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sem considerar velocidade e desempenho, quão bem o jogo se comporta, do início ao fim, nesta versão do yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Agradecemos pelo seu relatório!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Enviando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Erro de comunicação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Um erro ocorreu ao enviar o caso de teste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Próximo</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Áudio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Mecanismo de saída:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Este efeito de pós-processamento ajusta a velocidade do áudio para acompanhar a velocidade de emulação e ajuda a evitar cortes no áudio. No entanto, isto aumenta a latência do áudio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Ativar alongamento de áudio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Dispositivo de áudio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>Usar volume global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>Definir volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>Precisão:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>Preciso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>Não seguro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>Ativar modo de depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>Recomendamos definir a precisão para &quot;Preciso&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Ajustes de otimização não seguros de CPU </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Estes ajustes reduzem a precisão para aprimorar a velocidade.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Não usar FMA (melhora o desempenho em CPUs sem FMA)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta opção melhora o desempenho reduzindo a precisão de instruções de multiplicação-adição unificada (FMA) em CPUs sem suporte nativo a este recurso.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>FRSQRTE e FRECPE mais rápidos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta opção melhora o desempenho de algumas instruções de ponto flutuante usando aproximações nativas menos precisas.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Os ajustes de CPU só estão disponíveis enquanto o jogo não estiver em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>Ativando o modo de depuração de CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>O modo de depuração de CPU é intencionado para uso por desenvolvedores. Deseja mesmo ativá-lo?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>Ative ou desative otimizações de CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;
+ &lt;b&gt;Apenas para depuração.&lt;/b&gt;
+ &lt;br&gt;
+ Se você não tem certeza do que estas opções fazem, mantenha-as todas ativadas.
+ &lt;br&gt;
+ Estes ajustes apenas têm efeito quando a precisão da CPU for definida como &quot;Ativar modo de depuração&quot;.
+ &lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>Ativar tabelas de página em linha</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Esta otimização acelera acessos de memória pelo programa convidado.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Quando ativada, permite o acesso inline a PageTable::pointers no código emitido.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Quando desativada, força a passagem de todos os acessos de memória pelas funções Memory::Read/Memory::Write.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>Ativar vinculação de blocos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta otimização evita buscas do dispatcher ao permitir que blocos básicos emitidos pulem diretamente para outros blocos básicos se o PC de destino for estático.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>Ativar buffer de pilha de retorno</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Esta otimização evita buscas do dispatcher ao monitorar possíveis endereços de retorno de instruções BL. Isto se aproxima do que ocorre em um buffer de pilha de retorno em um CPU real.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>Ativar dispatcher rápido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Ativa um sistema de dispatch de dois níveis. Um dispatcher mais rápido, escrito em assembly e que possui um pequeno cache MRU de destinos de pulo, é usado primeiro. Caso falhe, o dispatcher mais lento escrito em C++ será usado em seu lugar.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>Ativar eliminação de contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Ativa uma otimização da IR que reduz acessos desnecessários à estrutura de contexto da CPU.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>Ativar propagação de constantes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Ativa otimizações da IR que envolvem propagação de constantes.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>Ativar otimizações diversas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div&gt;Ativa otimizações diversas para a IR.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>Ativar redução de checagem de desalinhamento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Quando ativada, um desalinhamento só será disparado quando um acesso cruza o limite de uma página.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Quando desativada, um desalinhamento será disparado em todos os acessos desalinhados.&lt;/div&gt;
+ </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Os ajustes de CPU estão disponíveis apenas quando não houver nenhum jogo em execução.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Ativar GDB stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Porta:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Registros de depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtro global de registros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Mostrar console de registros (apenas Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Abrir local dos registros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Linha de argumentos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>Quando ativado, a API gráfica entra em um modo de depuração mais lento.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>Ativar depuração de gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>Quando ativado, desativa o macro compilador Just in Time. Ativar isto faz os jogos rodarem mais lentamente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>Desativar macro JIT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>Extrair</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Ativar serviços de relatório detalhado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Isto será restaurado automaticamente assim que o yuzu for fechado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Modo quiosque (Quest)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>Configurar controle de depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>Padrão</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configurações do yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>Interface</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Perfis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Sistema de arquivos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Teclas de atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>GráficosAvançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Áudio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Rede</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Serviços</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Pastas de armazenamento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Cartão SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartão de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Caminho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Inserido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Jogo atual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Gerenciador de patches</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Extrair NSOs descompactados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Extrair ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Raiz de carregamento de mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Extrair raiz</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Ajustes de cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Diretório do cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Metadados da lista de jogos em cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Restaurar cache de metadados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Selecione a pasta da NAND emulada...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Selecione a pasta do SD emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Selecione o local do Gamecard...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Selecione a pasta de extração...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Selecione a pasta de carregamento de mods...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Selecione a pasta de cache...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>O cache de metadados já está vazio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>A operação foi concluída com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>O cache de metadados não pôde ser excluído. Ele pode estar em uso no momento ou não existe.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Limitar percentual de velocidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Emulação de CPU multinúcleo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Confirmar saída quando a emulação estiver em execução</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Escolher um usuário ao iniciar um jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pausar emulação quando a janela ficar em segundo plano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>Esconder cursor do mouse quando em inatividade</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>Configurações de API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>Dispositivo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>Configurações gráficas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Usar cache de shaders em disco</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Usar emulação assíncrona da GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>Proporção de tela:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>Padrão (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>Forçar 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>Forçar 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>Esticar para a janela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>Usar cor de fundo global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>Configurar cor de fundo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Cor de fundo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>Dispositivo gráfico do OpenGL</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>Configurações gráficas avançadas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>Nível de precisão:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>A sincronização vertical (VSync) evita que as imagens do jogo pareçam cortadas, porém algumas placas gráficas apresentam redução de desempenho quando estiver ativa. Deixe-a ativada se você não reparar alguma diferença de desempenho.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>Usar sincronização vertical (apenas OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>Ativar isto reduz engasgos de shaders. Ativa os shaders assembly do OpenGL em dispositivos compatíveis da Nvidia (é necessária a extensão NV_gpu_program5). Esta é uma opção experimental.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>Usar shaders assembly (experimental, apenas OpenGL + Nvidia)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>Realiza a compilação de shaders de forma assíncrona, o que pode reduzir engasgos de shaders. Esta opção é experimental.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>Usar compilação assíncrona de shaders (experimental)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>Usar tempo de resposta rápido da GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>Filtragem anisotrópica:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>Padrão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Configurações de teclas de atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Clique duas vezes em um atalho para alterá-lo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>Limpar tudo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurar padrões</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Ação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Combinação de teclas já utilizada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>A sequência de teclas pressionada já esta atribuída para: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>Restaurar padrão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>A sequência de teclas padrão já esta atribuida para: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>ConfigurarEntrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Jogador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Jogador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Jogador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Jogador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Jogador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Jogador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Jogador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Jogador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Modo do console</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Na base</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Fora da base</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Vibração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>Movimento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>Controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>Conectado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>Padrões</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar entrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Cores dos Joycon</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Jogador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>Joycon esq.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>Botão L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>Joycon dir.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>Botão R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Jogador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Jogador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Jogador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Jogador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Jogador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Jogador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Jogador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>Outro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Teclado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>Touchscreen</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>Mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>Movimento/toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>Controle de depuração</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar controles</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Conectar controle</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>Par de Joycons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>Joycon Esquerdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>Joycon Direito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>Portátil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>Dispositivo de entrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>Qualquer</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>Teclado/mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>Perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>Salvar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>Novo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>Excluir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Analógico esquerdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>Cima</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>Esquerda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>Direita</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>Baixo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>Pressionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>Modificador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>Alcance</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>Zona morta: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Alcance de modificador: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>D-pad</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>Menos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>Capturar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>Mais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Botão Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Botões de rosto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Analógico direito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>Zona morta: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Alcance de modificador: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[esperando]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Configurar movimento/toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>Movimento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>Fonte dos dados de movimento:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>Sensibilidade:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>Toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>Fonte dos dados de toque:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>Calibração:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>Usar mapeamento de botões:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>Configuração CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>Você pode utilizar qualquer dispositivo de entrada compatível com o Cemuhook UDP para prover dados de movimento e toque.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>Servidor:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>Porta:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Direcionais:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>Direcional 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>Direcional 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>Direcional 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>Direcional 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>Saiba mais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>Teste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>Mouse (botão direito)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>Janela do emulador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Saiba mais&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>Testando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>Configurando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>Teste bem-sucedido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation> Dados foram recebidos do servidor com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>O teste falhou</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>Não foi possível receber dados válidos do servidor.&lt;br&gt;Verifique se o servidor foi configurado corretamente e o endereço e porta estão corretos.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>Um teste UDP ou configuração de calibração está em curso no momento.&lt;br&gt;Aguarde até a sua conclusão.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configurar mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Botões do mouse</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Dianteiro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Traseiro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Esquerdo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Meio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Direito:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>Padrões</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[não definido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Restaurar padrão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[pressione uma tecla]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>Diálogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>Informações</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>ID do título</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>Nome do arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>Versão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>Tamanho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>Desenvolvedor</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>Adicionais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>Gráficos avanç.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>Áudio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>Usar configuração global (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>Nome do patch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>Versão</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Gerenciador de perfis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Usuário atual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nome de usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Definir imagem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Adicionar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Renomear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Excluir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Esta tela só fica disponível apenas quando não houver nenhum jogo em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Escreva o nome de usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Usuários</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Digite o nome do novo usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Digite um novo nome de usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Confirmar exclusão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Você está prestes a excluir o usuário &quot;%1&quot;. Tem certeza?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Selecione a imagem do usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Imagens JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Erro ao excluir a imagem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Ocorreu um erro ao tentar substituir a imagem anterior em: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Erro ao excluir arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Não foi possível excluir o arquivo existente: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Erro ao criar a pasta de imagens do usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Não foi possível criar a pasta %1 para armazenar as imagens do usuário.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Erro ao copiar a imagem do usuário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Não foi possível copiar a imagem de %1 para %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT é a maneira que a Nintendo usa para enviar dados aos jogos para engajar sua comunidade e desbloquear conteúdo adicional.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>Backend do BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Saiba mais sobre o BCAT, Boxcat e eventos atuais&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>O serviço Boxcat está offline ou você não está conectado à internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Ocorreu um erro ao processar os dados de eventos do Boxcat. Entre em contato com os desenvolvedores do yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>A versão do yuzu que você está usando é ou recente demais ou antiga demais para o servidor. Tente atualizar para a versão oficial mais recente do yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>Não há eventos no Boxcat atualmente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>Eventos Boxcat atuais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>O yuzu está baixando o status mais recente do Boxcat...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Configurações de sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>Região:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>Automático</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>Padrão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>Cuba</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>Egito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>GB-Eire</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>Greenwich</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>Hongkong</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>Islândia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>Irã</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>Israel</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>Jamaica</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>Japão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>Ilhas Marshall</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>Líbia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>Navajo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>Polônia </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>Portugal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>Singapura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>Turquia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>Universal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>Zulu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>EUA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>Europa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>Austrália</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>China</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>Coréia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>Taiwan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>Fuso horário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Nota: isso pode ser substituído caso a configuração de região automática esteja ativada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japônes (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Inglês (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francês (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Alemão (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiano (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Espanhol (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coreano (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Holandês (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Português</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russo (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Inglês britânico (British English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Francês canadense (Canadian French)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Espanhol latino-americano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chinês simplificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chinês tradicional (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>Data e hora personalizada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Idioma</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Semente RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Estéreo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID do console:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Modo de saída de som</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Regerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>As configurações de sistema são acessíveis apenas quando não houver nenhum jogo em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Isto substituirá o seu Switch virtual atual por um novo. O seu Switch virtual atual não poderá ser recuperado. Isto pode causar efeitos inesperados em jogos. Isto pode falhar caso você use um jogo salvo com configurações desatualizadas registradas nele. Continuar?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Aviso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID do console: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>Configurar mapeamento de toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>Mapeamento:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Novo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Excluir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Renomear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>Clique na área inferior para adicionar um ponto, e então pressione um botão para mapear.
+Mova os pontos para mudar a posição, ou clique duas vezes nas células da tabela para editar os valores.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Excluir ponto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>Botão</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>Novo perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Insira o nome do novo perfil.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>Excluir perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>Excluir perfil %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>Renomear perfil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>Novo nome:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[pressione a tecla]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configurar tela de toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Aviso: Os ajustes nesta página afetam o funcionamento interno da tela de toque emulada do yuzu. Alterá-los pode resultar em comportamentos indesejáveis, tais como a tela de toque funcionar parcialmente ou não responder por completo. Apenas faça alterações caso você saiba o que está fazendo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parâmetros de toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diâmetro de toque Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Dedo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diâmetro de toque X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Ângulo rotacional</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurar padrões</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Nota: alterar o idioma aplicará as suas configurações.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Idioma da interface:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Tema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Mostrar coluna de adicionais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Tamanho do ícone:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Texto da 1ª linha:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Texto da 2ª linha:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>Capturas de tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>Perguntar onde salvar capturas de tela (apenas Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>Pasta para capturas de tela:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>Selecione a pasta de capturas de tela...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Inglês</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formulário</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Web Service</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Ao informar seu usuário e token, você concorda em permitir ao yuzu recolher dados de uso adicionais, que podem incluir informações de identificação de usuário.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verificar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Cadastrar-se</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nome de usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Qual é o meu token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Compartilhar anonimamente dados de uso com a equipe do yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Saiba mais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID de telemetria:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Gerar um novo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Presença no Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Mostrar o jogo atual no seu status do Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Saiba mais&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Cadastrar-se&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Qual é o meu token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID de telemetria: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Não especificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token não verificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>O token não foi verificado. A alteração no seu token não foi salva.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Verificando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Falha na verificação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Falha na verificação. Verifique se o seu token foi inserido corretamente e se a sua conexão à internet está funcionando.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Dados anônimos são recolhidos&lt;/a&gt; para ajudar a melhorar o yuzu. &lt;br/&gt;&lt;br/&gt;Gostaria de compartilhar os seus dados de uso conosco?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Falha na verificação de texto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>Carregando applet web...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Sair do applet web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Sair</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Para sair do aplicativo web, use os controles fornecidos pelo jogo para sair dele, selecione a opção &quot;Sair do applet web&quot; na barra de menu ou pressione a tecla &quot;Enter&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Applet web</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Esta versão do yuzu foi compilada sem suporte a QtWebEngine, o que significa que o yuzu não pode exibir adequadamente o manual do jogo ou a página da web solicitada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>A quantidade de shaders sendo construídos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Velocidade atual de emulação. Valores maiores ou menores que 100% indicam que a emulação está rodando mais rápida ou lentamente que em um Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Quantos quadros por segundo o jogo está exibindo atualmente. Isto irá variar de jogo para jogo e cena para cena.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tempo que leva para emular um quadro do Switch, sem considerar o limitador de taxa de quadros ou a sincronização vertical. Um valor menor ou igual a 16.67 ms indica que a emulação está em velocidade plena.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>NA BASE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>ASYNC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>MULTINÚCLEO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Limpar arquivos recentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Aviso - formato de jogo desatualizado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Você está usando neste jogo o formato de ROM desconstruída e extraída em uma pasta, que é um formato desatualizado que foi substituído por outros, como NCA, NAX, XCI ou NSP. Pastas desconstruídas de ROMs não possuem ícones, metadados e suporte a atualizações.&lt;br&gt;&lt;br&gt;Para saber mais sobre os vários formatos de ROMs de Switch compatíveis com o yuzu, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;confira a nossa wiki&lt;/a&gt;. Esta mensagem não será exibida novamente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Erro ao carregar a ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>O formato da ROM não é suportado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Ocorreu um erro ao inicializar o núcleo de vídeo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>O yuzu encontrou um erro ao executar o núcleo de vídeo. Consulte o registro para mais detalhes. Para mais informações em como acessar o registro, veja a seguinte página: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Como enviar o arquivo de registro&lt;/a&gt;. Verifique se você está usando o driver de vídeo mais atualizado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>Erro ao carregar a ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Ocorreu um erro desconhecido. Consulte o registro para mais detalhes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Iniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Dados de jogos salvos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Dados de mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Erro ao abrir a pasta %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>A pasta não existe!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Erro ao abrir o cache de shaders transferível</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Não existe um cache de shaders para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>Conteúdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>Atualização</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>Remover item</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>Remover o jogo instalado %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>Removido com sucesso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>O jogo base foi removido com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>Erro ao remover %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>O jogo base não está instalado na NAND e não pode ser removido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>A atualização instalada foi removida com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>Não há nenhuma atualização instalada para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>Não há nenhum DLC instalado para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>%1 DLC(s) instalados foram removidos com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>Excluir o cache de shaders transferível?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>Remover configurações customizadas do jogo?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>Remover arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>Erro ao remover cache de shaders transferível</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>O cache de shaders transferível foi removido com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>Falha ao remover o cache de shaders transferível.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>Erro ao remover as configurações customizadas do jogo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>Não há uma configuração customizada para este título.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>As configurações customizadas do jogo foram removidas com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>Falha ao remover as configurações customizadas do jogo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Falha ao extrair RomFS!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Houve um erro ao copiar os arquivos RomFS ou o usuário cancelou a operação.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Extração completa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Apenas estrutura</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Selecione o modo de extração do RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Selecione a forma como você gostaria que o RomFS seja extraído.&lt;br&gt;&quot;Extração completa&quot; copiará todos os arquivos para a nova pasta, enquanto que &lt;br&gt;&quot;Apenas estrutura&quot; criará apenas a estrutura de pastas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Extraindo RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Extração do RomFS concluida!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>A operação foi concluída com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Erro ao abrir %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Selecionar pasta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>As propriedades do jogo não puderam ser carregadas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Executável do Switch (%1);;Todos os arquivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Carregar arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Abrir pasta da ROM extraída</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Pasta inválida selecionada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>A pasta que você selecionou não contém um arquivo &apos;main&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>Arquivo de Switch instalável (*.nca *.nsp *.xci);; Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>Instalar arquivos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Instalando arquivo &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>Resultados da instalação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Aplicativo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Arquivo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Atualização de aplicativo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Pacote de firmware (tipo A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Pacote de firmware (tipo B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Atualização de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Título delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Selecione o tipo de instalação do NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Selecione o tipo de título como o qual você gostaria de instalar este NCA:
+(Na maioria dos casos, o padrão &apos;Jogo&apos; serve bem.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Falha ao instalar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>O tipo de título que você selecionou para o NCA é inválido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Arquivo não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Arquivo &quot;%1&quot; não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continuar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Tela de erro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Conta do yuzu faltando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Para enviar um caso de teste de compatibilidade de jogo, você precisa entrar com a sua conta do yuzu.&lt;br&gt;&lt;br/&gt;Para isso, vá para Emulação &amp;gt; Configurar... &amp;gt; Rede.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>Erro ao abrir URL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>Não foi possível abrir o URL &quot;%1&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Arquivo Amiibo (%1);; Todos os arquivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Carregar Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Erro ao abrir arquivo de dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Não foi possível abrir o arquivo de Amiibo &quot;%1&quot; para leitura.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Erro ao ler arquivo de dados de Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Não foi possível ler completamente os dados do Amiibo. O yuzu esperava ler %1 bytes, mas foi capaz de ler apenas %2 bytes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Erro ao carregar dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Não foi possível carregar os dados do Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Capturar tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Imagem PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Velocidade: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Velocidade: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Jogo: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Quadro: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>O jogo que você está tentando carregar precisa que arquivos adicionais do seu Switch sejam extraídos antes de jogá-lo.&lt;br/&gt;&lt;br/&gt;Para saber mais sobre como extrair esses arquivos, visite a seguinte página da wiki: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Extraindo arquivos de sistema e fontes compartilhadas de um Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt; Gostaria de voltar para a lista de jogos? Continuar com a emulação pode resultar em travamentos, dados salvos corrompidos ou outros problemas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>O yuzu não foi capaz de encontrar um arquivo de sistema do Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>O yuzu não foi capaz de encontrar um arquivo de sistema do Switch. %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Arquivo do sistema não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Arquivo de sistema faltando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>O yuzu não foi capaz de encontrar as fontes compartilhadas do Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Fontes compartilhadas não encontradas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Fonte compartilhada faltando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Erro fatal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>O yuzu encontrou um erro fatal. Consulte o registro para mais detalhes. Para mais informações sobre como acessar o registro, consulte a seguinte página: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Como enviar o arquivo de registro&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Gostaria de voltar para a lista de jogos? Continuar com a emulação pode resultar em travamentos, dados salvos corrompidos ou outros problemas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Erro fatal encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Confirmar rederivação de chave</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Você está prestes a rederivar todas as suas chaves forçadamente.
+Se você não sabe o que isso significa ou o que você está fazendo,
+esta é uma ação potencialmente destrutiva.
+Por favor, confirme que você quer mesmo fazer isto
+e opcionalmente faça cópias de segurança.
+
+Isto excluirá o seus arquivos de chaves geradas automaticamente, e reexecutar o módulo de derivação de chaves.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>Faltando fusíveis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation> - Faltando BOOT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - Faltando BCPKG2-1-Normal-Main</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - Faltando PRODINFO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>Faltando componentes de derivação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>Há componentes faltando que podem impedir a conclusão do processo de derivação de chaves. &lt;br&gt;Por favor, siga &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;o guia de iniciação rápida&lt;/a&gt; para obter todas as suas chaves e jogos. &lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Derivando chaves...
+Isto pode demorar até um minuto, dependendo
+do desempenho do seu sistema.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Derivando chaves</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Selecionar alvo de extração do RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Selecione qual RomFS você quer extrair.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Você deseja mesmo fechar o yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Deseja mesmo parar a emulação? Qualquer progresso não salvo será perdido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>O aplicativo rodando no momento solicitou ao yuzu para não sair.
+
+Deseja ignorar isso e sair mesmo assim?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL não disponível!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>O yuzu não foi compilado com suporte para OpenGL.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkan não disponível!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>O yuzu não foi compilado com suporte para Vulkan.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>Erro ao inicializar OpenGL 4.3!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>Sua GPU pode não suportar OpenGL 4.3, ou você não tem os drivers gráficos mais recentes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>Erro ao inicializar o OpenGL!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>Sua GPU pode não suportar uma ou mais extensões necessárias do OpenGL. Por favor, verifique se você possui a última versão dos drivers gráficos.&lt;br&gt;&lt;br&gt;Extensões não supportadas:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Adicionais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Tipo de arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Tamanho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Abrir local dos jogos salvos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Abrir local dos dados de mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Abrir cache de shaders transferível</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>Remover</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>Remover atualização instalada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>Remover todos os DLCs instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>Remover cache de shaders</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>Remover configuração customizada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>Remover todo o conteúdo instalado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Extrair RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copiar ID do título para a área de transferência</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Abrir artigo do jogo no GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Examinar subpastas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Remover pasta de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ Mover para cima</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ Mover para baixo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Abrir local da pasta</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfeito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>O jogo funciona impecavelmente, sem falhas gráficas ou de áudio. Todas as funcionalidades testadas
+do jogo funcionam como deveriam, sem nenhuma solução alternativa necessária.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Ótimo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>O jogo funciona com leves falhas gráficas ou de áudio e é jogável do início ao fim. Pode exigir
+algumas soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Razoável</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>O jogo funciona com graves falhas gráficas ou de áudio, mas é jogável do início ao fim
+com o uso de soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Ruim</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>O jogo funciona, só que com problemas significativos nos gráficos ou no áudio. Não é possível progredir em áreas
+específicas por conta de tais problemas, mesmo com soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Intro/menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>É impossível jogar o jogo devido a grandes problemas nos gráficos ou no áudio. Não é possível passar da
+tela inicial do jogo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Não inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>O jogo trava ou se encerra abruptamente ao se tentar iniciá-lo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Não testado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Esse jogo ainda não foi testado.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Clique duas vezes para adicionar uma pasta à lista de jogos</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Digite o padrão para filtrar</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>Por favor, confirme que esses são os arquivos que deseja instalar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>Instalar uma atualização ou DLC irá sobrescrever a instalada anteriormente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>Instalar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>Instalar arquivos para a NAND</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Carregando shaders 387/1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Carregando shaders (%v de %m)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Tempo estimado 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Carregando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Carregando shaders %1/%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Iniciando...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Tempo estimado %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Arquivos recentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Exibir</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Ferramentas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Ajuda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>Instalar arquivos para a NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Carregar arquivo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Carregar pasta...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>S&amp;air</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Iniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pausar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Parar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Reinicializar chaves...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Sobre o yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Modo de janela única</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configurar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Exibir barra de títulos de widgets afixados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Exibir barra de filtro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Exibir barra de status</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>Restaurar tamanho padrão de janela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Tela cheia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Reiniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Carregar Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Informar compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>Abrir pagina de mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>Abrir o guia de iniciação rápida</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>FAQ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Abrir pasta do yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Capturar tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>Configurar jogo atual..</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Títulos instalados no SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Títulos instalados na NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Títulos do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Adicionar pasta de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[não definido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Eixo %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Botão %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[desconhecido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>Clique 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>Clique 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>Clique 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>Clique 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>Clique 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>Eixo GC %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>Botão GC %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[não utilizado]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Eixo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>Eixo GC %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Ocorreu um erro.
+Tente novamente ou entre em contato com o desenvolvedor do software.
+
+Código de erro: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Ocorreu um erro em %1 em %2.
+Tente novamente ou entre em contato com o desenvolvedor do software.
+
+Código de erro: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Ocorreu um erro.
+Código do erro: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Selecione um usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Usuários</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Seletor de perfil</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Digite o texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Teclado de software</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Digite uma combinação de teclas</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Pilha de chamadas</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>esperando pelo mutex 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>possui os waiters %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>manejo de proprietário: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>esperando por todos os objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>esperando por um dos seguintes objetos</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>não aguardando pelo thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>rodando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>pronto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>pausado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>esperando pelo retorno do HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>dormindo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>esperando para resposta do IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>esperando por objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>esperando por mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>aguardando por variável da condição</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>esperando para endereção o árbitro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>inativo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>morto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>núcleo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Processador desconhecido %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>processador = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>núcleo ideal = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>máscara de afinidade = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>thread id = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>prioridade = %1(atual) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>últimos ticks executados = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>não aguardando para mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>aguardado pelo thread</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Árvore de espera</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/pt_PT.ts b/dist/languages/pt_PT.ts
new file mode 100644
index 000000000..5ba0bcf3b
--- /dev/null
+++ b/dist/languages/pt_PT.ts
@@ -0,0 +1,4725 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="pt_PT" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Sobre Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu é um emulador experimental de código aberto para a Nintendo Switch licenciado sob GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Este software não deve ser usado para jogar jogos que você não tenha obtido legalmente.&lt;/span&gt;&lt;/p&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Site&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Código Fonte&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contribuidores&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Licença&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; é uma marca comercial da Nintendo. Yuzu não é afiliado com a Nintendo de qualquer forma.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Reportar Compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Reportar compatibilidade de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Se você optar por enviar um caso de teste para a&lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;lista de compatibilidade do yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;As seguintes informações serão coletadas e exibidas no site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Informações de Hardware (CPU / GPU / Sistema operativo)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Que versão do yuzu você está executando&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;A conta yuzu conectada&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Perfeito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Jogo funciona na perfeição sem falhas de áudio ou gráficas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Ótimo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona com pequenas falhas gráficas ou de áudio e pode ser jogado do início ao fim. Pode exigir algumas soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>OK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona com grandes falhas gráficas ou de áudio, mas o jogo é jogável do início ao fim com soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Mau</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo funciona, mas com grandes falhas gráficas ou de áudio. Não é possível progredir em áreas específicas devido a falhas, mesmo com soluções alternativas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Introdução / Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo não é jogável devido a grandes falhas gráficas ou de áudio. Não é possível passar da tela inicial.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Não Inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O jogo trava ao tentar iniciar.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independente da velocidade ou performance, como este jogo funciona de principio ao fim nesta versão do yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Obrigado pelo seu envio!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Entregando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Erro de comunicação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Ocorreu um erro enquanto enviava o caso de teste</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Próximo</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Motor de Saída:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Este efeito de pós-processamento ajusta a velocidade do áudio para corresponder à velocidade de emulação e ajuda a evitar o engasgue do audio. Isto, no entanto, aumenta a latência de áudio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Activar alongamento de audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Dispositivo de áudio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Volume:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Activar GDB Stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Porta:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Entrando</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Filtro de registro global</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Mostrar o registro da consola (Apenas Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Abrir a localização do registro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Argumentos String</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Ativar o Reporte de Serviços detalhado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Isto vai ser resetado automáticamente quando o yuzu fechar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Modo Quiosque</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Configuração yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>IU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Lista de Jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Perfis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Sistema de Ficheiros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Controlos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Teclas de Atalhos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Depurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Gráficos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Audio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Rede</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Serviços</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Diretórios de armazenamento</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>Cartão SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Cartão de jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Caminho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Inserido</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Jogo Atual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Gestor de Patch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Dump NSOs Descompactados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Dump ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Raiz dos Mods</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>Raiz do Dump</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Armazenamento em cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Diretório do cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Metadata da Lista de Jogos em Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Resetar a Cache da Metadata</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Selecione o Diretório NAND Emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Selecione o Diretório SD Emulado...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Selecione o Diretório do Cartão de Jogo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Selecionar o diretório do Dump...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Selecionar o Diretório do Mod Load ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Selecionar o diretório do Cache</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>O cache de metadata já está vazio.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>A operação foi completa com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Não foi possível excluir o cache de metadata. Pode estar em uso ou inexistente.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Percentagem do limitador de velocidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Confirme a saída enquanto a emulação está em execução</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Solicitar para o utilizador na inicialização do jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Pausar o emulador quando estiver em segundo plano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Usar cache do disk shader</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Usar emulação assíncrona de GPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Cor de fundo:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Definições de Teclas de Atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Clique duas vezes numa ligação para alterá-la.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Ação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Tecla de Atalho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation> Contexto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Sequência de teclas em conflito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Jogador 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Jogador 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Jogador 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Jogador 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Jogador 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Jogador 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Jogador 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Jogador 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Avançado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Configurar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Configurar Entrada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Analógico Esquerdo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Botôes de Rosto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Analógico Direito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Configurar Rato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Botões do Rato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Frente:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Atrás:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Esquerda:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Meio:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Direita:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[não configurado]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Restaurar Padrões</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[pressiona a tecla]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Gestor de Perfis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Utilizador Atual</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Nome de Utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Definir Imagem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Adicionar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Renomear</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Remover</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>O gestor de perfis só está disponível apenas quando o jogo não está em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Introduza o Nome de Utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Utilizadores</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Introduza um nome de utilizador para o novo utilizador:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Introduza um novo nome de utilizador:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Confirmar para eliminar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Estás preste a eliminar o utilizador com o nome &quot;%1&quot;. Tens a certeza?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Definir Imagem de utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Imagens JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Error ao eliminar a imagem</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Ocorreu um erro ao tentar substituir imagem anterior em: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Erro ao eliminar o arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Não é possível eliminar o arquivo existente: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Erro ao criar o diretório de imagens do utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Não é possível criar o diretório %1 para armazenar imagens do utilizador.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Erro ao copiar a imagem do utilizador</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Não é possível copiar a imagem de %1 para %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>O BCAT é a forma pela qual a Nintendo envia dados aos jogos para envolver a sua comunidade e desbloquear conteúdos adicionais.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT Backend</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Saiba mais sobre o BCAT, Boxcat e eventos atuais&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>O serviço boxcat está offline ou não estás ligado à Internet.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Ocorreu um erro ao processar os dados do evento boxcat. Entre em contato com os desenvolvedores do yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>A versão em uso do yuzu é muito nova ou muito antiga para o servidor. Tente atualizar para a ultima versão oficial do yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>De momento, não há eventos no boxcat.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu está a atualizar o boxcat ...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Configurações de Sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Nota: isto pode ser substituído quando a configuração da região é de seleção automática</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Japonês (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Inglês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Francês (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Alemão (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Italiano (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Espanhol (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Chinês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Coreano (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Holandês (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Português (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Russo (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Taiwanês</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Inglês Britânico</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Francês Canadense</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Espanhol Latino-Americano</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Chinês Simplificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Chinês Tradicional (正 體 中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>RTC personalizado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Língua</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Semente de RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Mono</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Estéreo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Surround</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>ID da consola:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Modo de saída de som</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Regenerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>As configurações do sistema estão disponíveis apenas quando o jogo não está em execução.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Isto substituirá o seu Switch virtual actual por um novo. Seu Switch virtual actual não será recuperável. Isso pode ter efeitos inesperados nos jogos. Isto pode falhar, se você usar uma gravação de jogo de configuração desatualizado. Continuar?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Aviso</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>ID da Consola: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Configurar Ecrã Táctil</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Aviso: As configurações nesta página afectam o funcionamento interno da tela de toque emulada do yuzu. Alterá-los pode resultar em um comportamento indesejável, como a tela de toque não funcionando parcialmente ou até mesmo na sua totatildade. Você só deve usar esta página se souber o que está fazendo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Parâmetros de Toque</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Diâmetro de Toque Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Dedo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Diâmetro de Toque X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Ângulo rotacional</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Restaurar Padrões</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Geral</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Nota: Alterar o idioma aplicará sua configuração.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Idioma da interface:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Tema:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Lista de Jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Mostrar coluna de Add-Ons</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Tamanho do ícone:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Linha 1 Texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Linha 2 Texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Inglês</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Formato</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>Serviço Web do Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Ao fornecer seu nome de usuário e token, você concorda em permitir que o yuzu colete dados de uso adicionais, que podem incluir informações de identificação do usuário.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Verificar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Inscrever-se</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Token: </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Nome de usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>O que é o meu token?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Compartilhar dados de uso anônimos com a equipa Yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Saber mais</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID de Telemetria:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Regenerar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Presença do Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Mostrar o Jogo Atual no seu Estado de Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Saber mais&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Inscrever-se&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;O que é o meu Token?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID de Telemetria: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Não especificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Token não verificado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>O token não foi verificado. A alteração do token não foi gravada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>A verificar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Verificação Falhada</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Verificação Falhada. Verifique se introduziu seu nome de utilizador e o token correctamente e se a conexão com a Internet está operacional.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Dados anônimos são coletados&lt;/a&gt;para ajudar a melhorar o yuzu.&lt;br/&gt;&lt;br/&gt;Gostaria de compartilhar seus dados de uso conosco?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Telemetria</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Falha na verificação de texto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>A Carregar o Web Applet ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>Sair do Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Sair</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>Para sair do aplicativo web, use os controlos fornecidos pelo jogo para selecionar sair, selecione a opção &apos;Sair do Web Applet&apos; na barra de menus ou pressione a tecla &apos;Enter&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web Applet</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Esta versão do yuzu foi criada sem o suporte ao QtWebEngine, o que significa que o yuzu não pode exibir corretamente o manual do jogo ou a página da web solicitada.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Velocidade da emulação actual. Valores acima ou abaixo de 100% indicam que a emulação está sendo executada mais depressa ou mais devagar do que a Switch</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Quantos quadros por segundo o jogo está exibindo de momento. Isto irá variar de jogo para jogo e de cena para cena.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Tempo gasto para emular um frame da Switch, sem contar o a limitação de quadros ou o v-sync. Para emulação de velocidade máxima, esta deve ser no máximo 16.67 ms.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Limpar Arquivos Recentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Aviso de Formato de Jogo Desactualizado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Você está usando o formato de directório ROM desconstruído para este jogo, que é um formato desactualizado que foi substituído por outros, como NCA, NAX, XCI ou NSP. Os directórios de ROM não construídos não possuem ícones, metadados e suporte de actualização.&lt;br&gt;&lt;br&gt;Para uma explicação dos vários formatos de Switch que o yuzu suporta,&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;Verifique a nossa Wiki&lt;/a&gt;. Esta mensagem não será mostrada novamente.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Erro ao carregar o ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>O formato do ROM não é suportado.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Ocorreu um erro ao inicializar o núcleo do vídeo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu encontrou um erro ao executar o núcleo de vídeo, consulte o log para obter mais detalhes. Para obter mais informações sobre como acessar o log, consulte a seguinte página:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Como carregar o arquivo de log&lt;/a&gt;Assegure-se que tem os drivers gráficos mais recentes para sua GPU.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Ocorreu um erro desconhecido. Por favor, veja o log para mais detalhes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Começar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Save Data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Mod Data</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Erro ao abrir a pasta %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>A Pasta não existe!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Erro ao abrir os Shader Cache transferíveis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>O Shader Cache para este titulo não existe.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>A Extração de RomFS falhou!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Houve um erro ao copiar os arquivos RomFS ou o usuário cancelou a operação.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Cheio</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>Esqueleto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Selecione o modo de despejo do RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Por favor, selecione a forma como você gostaria que o RomFS fosse despejado&lt;br&gt;Full irá copiar todos os arquivos para o novo diretório enquanto&lt;br&gt;skeleton criará apenas a estrutura de diretórios.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Extraindo o RomFS ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Cancelar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Extração de RomFS Bem-Sucedida!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>A operação foi completa com sucesso.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Erro ao abrir %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Selecione o Diretório</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>As propriedades do jogo não puderam ser carregadas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Executáveis Switch (%1);;Todos os Ficheiros (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Carregar Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Abrir o directório ROM extraído</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Diretório inválido selecionado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>O diretório que você selecionou não contém um arquivo &apos;Main&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Instalando arquivo &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Aplicação do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Arquivo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Atualização do aplicativo do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Pacote de Firmware (Tipo A)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Pacote de Firmware (Tipo B)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Actualização do Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>DLC do Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Título Delta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Selecione o tipo de instalação do NCA ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Por favor, selecione o tipo de título que você gostaria de instalar este NCA como:
+(Na maioria dos casos, o padrão &apos;Jogo&apos; é suficiente).</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Falha na instalação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>O tipo de título que você selecionou para o NCA é inválido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Arquivo não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Arquivo &quot;%1&quot; não encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Continuar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Visualização de erros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Conta Yuzu Ausente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Para enviar um caso de teste de compatibilidade de jogos, você deve vincular sua conta yuzu.&lt;br&gt;&lt;br/&gt;Para vincular sua conta yuzu, vá para Emulação &amp;gt; Configuração &amp;gt; Rede.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Arquivo Amiibo (%1);; Todos os Arquivos (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Carregar Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Erro ao abrir o arquivo de dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Não é possível abrir o arquivo Amiibo &quot;%1&quot; para leitura.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Erro ao ler o arquivo de dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Não é possível ler completamente os dados do Amiibo. Espera-se que leia %1 bytes, mas só conseguiu ler %2 bytes.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Erro ao carregar dados do Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Não foi possível carregar os dados do Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Captura de Tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Imagem PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Velocidade: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Velocidade: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Jogo: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Quadro: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>O jogo que você está tentando carregar requer arquivos adicionais do seu Switch para serem despejados antes de jogar.&lt;br/&gt;&lt;br/&gt;Para obter mais informações sobre como despejar esses arquivos, consulte a seguinte página da wiki:&lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Despejando arquivos do sistema e as fontes compartilhadas de uma consola Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Você gostaria de regressar para a lista de jogos? Continuar a emulação pode resultar em falhas, dados de salvamento corrompidos ou outros erros.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>O yuzu não conseguiu localizar um arquivo de sistema do Switch. % 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>O yuzu não conseguiu localizar um arquivo de sistema do Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Arquivo do Sistema Não Encontrado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Arquivo de Sistema em falta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu não conseguiu localizar as fontes compartilhadas do Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Fontes compartilhadas não encontradas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Fontes compartilhadas em falta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Erro fatal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu encontrou um erro fatal, por favor veja o registro para mais detalhes. Para mais informações sobre como acessar o registro, por favor, veja a seguinte página:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Como carregar o arquivo de registro&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Você gostaria de regressar para a lista de jogos? Continuar a emulação pode resultar em falhas, dados de salvamento corrompidos ou outros erros.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Ocorreu um Erro fatal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Confirme a rederivação da chave</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Você está prestes a forçar a rederivação de todas as suas chaves.
+Se você não sabe o que isso significa ou o que você está fazendo,
+esta é uma acção potencialmente destrutiva.
+Por favor, certifique-se que isto é o que você quer
+e opcionalmente faça backups.
+
+Isso irá excluir os seus arquivos de chave gerados automaticamente e executará novamente o módulo de derivação de chave.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Derivando chaves ...
+Isto pode demorar até um minuto, dependendo
+do desempenho do seu sistema.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Derivando Chaves</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Selecione o destino de despejo do RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Por favor, selecione qual o RomFS que você gostaria de despejar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Tem a certeza que quer fechar o yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Tem a certeza de que quer parar a emulação? Qualquer progresso não salvo será perdido.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>O aplicativo atualmente em execução solicitou que o yuzu não fechasse.
+
+Deseja ignorar isso e sair mesmo assim?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Nome</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Acrescentos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Tipo de Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Tamanho</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Abrir Localização de Dados Salvos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Abrir a Localização de Dados do Mod</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Abrir Shader Cache transferíveis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Despejar RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Copiar título de ID para a área de transferência</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Navegue para a Entrada da Base de Dados de Jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Propriedades</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Examinar Sub-pastas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Remover diretório do Jogo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Abrir Localização do diretório</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Perfeito</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>O Jogo Funciona na Perfeição sem falhas de áudio ou gráficas, todas as funcionalidades testadas funcionam como planeadas sem
+quaisquer soluções alternativas necessárias.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Ótimo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>O Jogo funciona com pequenas falhas gráficas ou de áudio e pode ser jogado do principio ao fim. Pode exigir algumas
+soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Ok</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>O Jogo funciona com grandes falhas gráficas ou de áudio, mas o jogo é jogável do principio ao fim com
+soluções alternativas.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Mau</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Jogo Funcional, mas com grandes falhas gráficas ou de áudio. Incapaz de progredir em áreas específicas devido a falhas
+mesmo com soluções alternativas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Introdução / Menu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>O Jogo não é jogável devido a grandes falhas gráficas ou de áudio. Não é possível passar da Tela
+Inicial</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Não Inicia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>O jogo trava ao tentar iniciar.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Não Testado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>O jogo ainda não foi testado.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Clique duas vezes para adicionar uma nova pasta à lista de jogos</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Filtro:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Digite o padrão para filtrar</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>A Carregar Shaders 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>A Carregar Shaders %v por %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Tempo Estimado 5m 4s</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>A Carregar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>A Carregar Shaders %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>A iniciar...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Tempo Estimado %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Arquivos Recentes</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Emulação</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Vista</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Depuração</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Ferramentas</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Ajuda</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Carregar Arquivo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Carregar Pasta</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>&amp;Sair</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Começar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Pausa</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Parar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Reinicialize as chaves ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>Sobre yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Modo de Janela Única</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Configurar ...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Exibir Cabeçalhos de Ferramenta de Doca</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Mostrar Barra de Filtros</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Mostrar Barra de Estado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Tela Cheia</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Reiniciar</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Carregar Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Reportar Compatibilidade</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Abrir Pasta yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Captura de Tela</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>Micro Perfil</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Títulos SD instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Títulos NAND instalados</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Títulos do sistema</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Adicionar novo diretório de jogos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[não configurado]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Hat %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Eixo %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Botão %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[Desconhecido]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[sem uso]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Eixo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Ocorreu um erro.
+Tente novamente ou entre em contato com o desenvolvedor do software.
+
+Código do Erro: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Ocorreu um erro em %1 até %2.
+Tente novamente ou entre em contato com o desenvolvedor do software.
+
+Código do Erro: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Ocorreu um erro.
+Código de Erro: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Selecione um usuário:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Utilizadores</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Selector de perfil</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Digite o texto:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Teclado de Software</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Introduza a tecla de atalho</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Pilha de Chamadas</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>esperando por mutex 0x% 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>has waiters: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>owner handle: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>esperando por todos os objetos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>esperando por todos os objectos</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>correr</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>preparado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>pausado</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>esperando pelo retorno de HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>dormindo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>aguardando resposta do IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>esperando por objectos</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>esperando por mutex</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>A espera da variável de condição</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>esperando pelo árbitro de endereço</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>dormente</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>morto</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>núcleo %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Processador desconhecido %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>processador = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>núcleo ideal =% 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>máscara de afinidade =% 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id do segmento =% 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>prioridade =%1(atual) / %2(normal)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>últimos tiques em execução =%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>não esperar por mutex</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>esperado por tópico</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Esquema de espera</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/ru_RU.ts b/dist/languages/ru_RU.ts
new file mode 100644
index 000000000..357b8e416
--- /dev/null
+++ b/dist/languages/ru_RU.ts
@@ -0,0 +1,4720 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="ru_RU" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>О yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu - экспериментальный эмулятор для Nintendo Switch с открытым исходным кодом, лицензированный под GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Это ПО не должно использоваться для запуска игр, которые были получены нелегально.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Веб-сайт&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Исходный код&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Авторы&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Лицензия&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; является торговой маркой Nintendo. yuzu никак не связан с Nintendo.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Сообщить о совместимости</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Сообщить о совместимости игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Если вы захотите отправить отчет в &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;Список Совместимости yuzu&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, Следующая информация будет собрана и отображена на сайте:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; Информация об Аппаратном Обеспечении (ЦП / ГП / Операционная Система)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Версия yuzu&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Подключенный аккаунт yuzu&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Идеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра функционирует отлично, без звуковых и графических артефактов.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>Отлично</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра работает с небольшими графическими или звуковыми артефактами и может быть пройдена от начала до конца. Могут потребоваться обходные пути.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Нормально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра работает с существенными графическими или звуковыми артефактами, но с обходными путями может быть пройдена.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Плохо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра работает, но с существенными графическими или звуковыми артефактами. В некоторых зонах нельзя продвинуться даже с обходными путями.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Вступление/Меню</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;В игру невозможно играть из-за графических или звуковых артефактов. Невозможно продвинуться дальше Стартового Экрана.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Не Запускается</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Игра падает при старте.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Независимо от скорости и производительности, насколько хорошо эта игра работает от начала до конца в текущей версии yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Спасибо за ваш отчет!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>Отправка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>Ошибка соединения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>Произошла ошибка при отправке Отчета</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>Далее</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>Звук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>Механизм вывода:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>Этот эффект пост-обработки регулирует скорость звука в соответствии со скоростью эмуляции и помогает предотвратить заикание звука. Это, однако, увеличивает задержку звука.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>Включить растяжение звука</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>Звуковое Устройство:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>Громкость:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>Включить GDB Stub</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>Порт:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>Журналирование</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>Глобальный Фильтр Журнала</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>Показывать Журнал в Консоли (Только Для Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>Открыть Расположение Журнала</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>Строка Аргументов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>Включить Службы Подробных Отчётов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>Это будет автоматически сброшено после закрытия yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>Дополнительно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Параметры yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>Основное</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>Интерфейс</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>Список Игр</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>Система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>Профили</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>Файловая система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>Управление</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>Горячие клавиши</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>Отладка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>Графика</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>Звук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>Веб</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>Сервисы</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>Директории Хранения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SD карта</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>Картридж</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>Путь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>Вставлен</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>Текущая Игра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>Управление Патчами</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Дампить Распакованные NCO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>Дампить ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>Кэширование</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>Директория Кэша</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Сбросить Кэш Метаданных</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Выбрать Папку Для Эмулируемого NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Выбрать Папку Для Эмулируемого SD</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>Выберите Папку с Дампами</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>Выбрать Папку с Кэшем...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>Кэш метаданных уже пустой.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>Операция выполнена успешно.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Кэш метаданных не может быть удален. Он может использоваться или отсутствовать.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>Основное</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>Ограничить Скорость в Процентах</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Подтверждать выход во время эмуляции</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>Приостанавливать эмуляцию, когда в фоновом режиме</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>Использовать кэш шейдеров на диске</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Использовать асинхронную эмуляцию ГП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>Фоновый Цвет:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Настройки Горячих Клавиш</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>Действие</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>Сочетание</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>Контекст</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Конфликтующее Сочетание Клавиш</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Игрок 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Игрок 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Игрок 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Игрок 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Игрок 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Игрок 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Игрок 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Игрок 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Дополнительно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>Настроить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Настроить Ввод</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>Левый Стик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>Основные Кнопки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>Правый Стик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>Настроить Мышь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>Кнопки на Мыши</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>Вперед:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>Назад:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>Левая:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>Средняя:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>Правая:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>Очистить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>Настройки по Умолчанию</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[нажмите кнопку]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>Управление Профилями</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>Текущий Пользователь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>Имя Пользователя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>Добавить Изображение</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>Добавить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>Переименовать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>Удалить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Управление профилями доступно только тогда, когда игра не запущена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>Введите Имя Пользователя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>Пользователи</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Введите имя пользователя для нового профиля:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>Введите новое имя пользователя:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>Подтвердите Удаление</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Вы собираетесь удалить пользователя &quot;%1&quot;. Вы уверены?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>Выберите Изображение для Пользователя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG Images (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>Ошибка при удалении изображения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Ошибка при попытке перезаписи предыдущего изображения в: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>Ошибка при удалении файла</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Не получилось удалить существующий файл: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>Ошибка при создании папки пользовательских изображений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Не получилось создать папку %1 для хранения изображений пользователя.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>Ошибка при копировании изображения пользователя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Не получилось скопировать изображение из %1 в %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT - это способ Nintendo отправлять данные в игры, чтобы привлечь сообщество и разблокировать дополнительный контент.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>Произошла ошибка при обработке данных событий boxcat. Свяжитесь с разработчиками yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>Используемая вами версия yuzu слишком новая или слишком старая по сравнению с версией на сервере. Попробуйте обновить yuzu до последней официальной версии.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>Настройки Системы</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Примечание: может быть перезаписано если регион выбирается автоматически</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>Японский (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>Английский (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>Французский (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>Немецкий (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>Итальянский (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>Испанский (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>Китайский (Chinese)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>Корейский (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Голландский (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>Португальский (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>Русский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>Тайваньский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>Британский Английский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>Канадский Французский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>Латиноамериканский Испанский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>Упрощенный Китайский</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Традиционный Китайский (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>Пользовательский RTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>Язык</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>Зерно RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>Моно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>Стерео</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>Объёмный звук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>Идентификатор Консоли:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>Режим вывода звука</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>Перегенерировать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Настройки системы доступны только тогда, когда игра не запущена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Это заменит ваш текущий виртуальный Switch новым. Ваш текущий виртуальный Switch будет безвозвратно потерян. Это может иметь неожиданные последствия в играх. Может не сработать, если вы используете устаревшую конфигурацию сохраненных игр. Продолжить?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>Внимание</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Идентификатор Консоли: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Настроить Сенсорный Экран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Внимание: Настройки на этой странице влияют на внутреннюю работу эмулируемого сенсорного экрана yuzu. Их изменение может привести к нежелательному поведению, как например частичная или полная неработоспособность сенсорного экрана. Используйте их, только если вы знаете, что делаете.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Параметры Касания</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Диаметр Касания Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>Палец</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>Диаметр Касания X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>Угол Поворота</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>Настройки по Умолчанию</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>Основное</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Примечание: Изменение языка приведет к применению настроек.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>Язык интерфейса:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>Тема:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>Список Игр</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Показывать Столбец Дополнений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>Размер Иконок:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>Текст 1 Строки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>Текст 2 Строки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>Английский</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu Веб Сервис</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Предоставляя ваше имя пользователя и токен, вы разрешаете yuzu собирать дополнительную информацию об использовании, которая может включать данные идентифицирующие пользователя.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>Подтвердить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>Регистрация</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>Токен:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>Имя Пользователя:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>Что такое токен?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>Телеметрия</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Делиться анонимной информацией об использовании с командой yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>Узнать больше</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>ID Телеметрии:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>Перегенерировать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Интеграция с Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Показывать Текущую Игру в вашем Статусе Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Узнать больше&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Регистрация&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Что такое токен?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>ID Телеметрии: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>Отсутствует</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>Токен не подтвержден</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Токен не подтвержден. Ваш токен не был изменен.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>Проверка...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>Ошибка подтверждения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Ошибка подтверждения. Убедитесь в том, что токен введен корректно, и ваше интернет соединение активно.</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Анонимные данные собираются&lt;/a&gt; для улучшения yuzu. &lt;br/&gt;&lt;br/&gt;Хотели бы вы делиться данными об использовании с нами?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>Телеметрия</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>Ошибка Проверки Текста</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>Выход</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>Эта версия yuzu была собрана без поддержки QtWebEngine, что означает, что yuzu не может нормально отобразить руководство к игре или запрошенную веб-страницу.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Текущая скорость эмуляции. Значения выше или ниже 100% указывают на то, что эмуляция идет быстрее или медленнее, чем на Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Количество кадров в секунду в данный момент. Значение будет меняться от игры к игре и от сцене к сцене.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Время, которое нужно для эмуляции 1 кадра Switch, не принимая во внимание ограничение FPS или v-sync. Для полноскоростной эмуляции значение должно быть не больше 16,67 мс.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>Очистить Недавние Файлы</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Предупреждение Устаревший Формат Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Для этой игры вы используете разархивированный формат РОМа, который является устаревшим и был заменен другими, такими как NCA, NAX, XCI или NSP. В разархивированных каталогах РОМа отсутствуют значки, метаданные и поддержка обновлений. &lt;br&gt;&lt;br&gt;Для получения информации о различных форматах Switch, поддерживаемых yuzu, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;просмотрите нашу вики&lt;/a&gt;. Это сообщение больше не будет отображаться.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>Ошибка при загрузке РОМа!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Формат РОМа не поддерживается.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Произошла ошибка при инициализации видеоядра.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu обнаружил ошибку во время работы видеоядра. Проверьте журнал для подробностей. Информацию о доступе к журналу можно найти на этой странице: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Как Отправить Файл Журнала.&lt;/a&gt; Убедитесь, что у вас установлены последние графические драйверы.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Произошла неизвестная ошибка. Пожалуйста, проверьте лог для подробностей.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>Начать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>Данные Сохранений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Данные Модов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Ошибка при Открытии Папки %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>Папка не существует!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Ошибка при Открытии Переносного Кеша Шейдеров</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Шейдерный кеш для этого идентификатора не существует.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Не удалось извлечь RomFS!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Произошла ошибка при копировании файлов RomFS или пользователь отменил операцию.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>Полный</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Выберите Режим Дампа RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Пожалуйста, выберите, как вы хотите выполнить дамп RomFS. &lt;br&gt;Полное скопирует все файлы в новый каталог, в то время как &lt;br&gt;Структура создаст только структуру каталогов.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>Извлечение RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>Отмена</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Извлечение RomFS Прошло Успешно!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>Операция выполнена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>Ошибка Открытия %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>Выбрать Путь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>Свойства</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Не удалось загрузить свойства игры.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Исполняемый файл Switch (%1);;Все файлы (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>Загрузить Файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Открыть Папку Извлечённого РОМа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Выбран Неверный Каталог</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Каталог, который вы выбрали, не содержит «основного» файла.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Установка файла &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>Системное Приложение</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>Системный Архив</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>Обновление Системного Приложения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Контейнер Прошивки (Тип А)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Контейнер Прошивки (Тип Б)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>Игра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>Обновление Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>Загружаемый Контент</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>Delta Название</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Выберите Тип Установки NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Пожалуйста, выберите тип приложения, который вы хотите установить для этого NCA:
+ (В большинстве случаев, простое «Игра» подходит.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>Ошибка Установки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Тип приложения, который вы выбрали для NCA, недействителен.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>Файл не найден</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Файл &quot;%1&quot; не найден</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>Продолжить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>Показ Ошибок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>Отсутствует Аккаунт yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Чтобы отправить отчет о совместимости игры, необходимо привязать свою учетную запись yuzu.&lt;br&gt;&lt;br/&gt;Чтобы привязать свою учетную запись yuzu, перейдите в раздел Эмуляция &amp;gt; Настройка &amp;gt; Веб.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Файл Amiibo (%1);; Все Файлы (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>Загрузить Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>Ошибка открытия файла данных Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>Невозможно открыть файл Amiibo &quot;%1&quot; для чтения.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>Ошибка чтения файла данных Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>Невозможно полностью прочитать данные Amiibo. Ожидалось прочитать %1 байт, но удалось прочитать только %2 байт.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Ошибка загрузки данных Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>Невозможно загрузить данные Amiibo.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>Сделать Скриншот</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG Image (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Скорость: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>Скорость: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>Игра: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>Один кадр: %1 ms</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Игра, которую вы пытаетесь загрузить, требует, чтобы дополнительные файлы были сдамплены с вашего Switch перед началом игры. &lt;br/&gt;&lt;br/&gt;Для получения дополнительной информации о дампе этих файлов см. следующую вики-страницу: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Дамп Системных Архивов и Общих Шрифтов с консоли&lt;/a&gt;. &lt;br/&gt;&lt;br/&gt;Хотите вернуться к списку игр? Продолжение эмуляции может привести к сбоям, повреждению сохраненных данных или другим ошибкам.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu не удалось найти системный архив Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu не удалось найти системный архив Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>Системный Архив Не Найден</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>Отсутствует Системный Архив</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu не удалось найти общие шрифты Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Общие Шрифты Не Найдены</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>Общие Шрифты Отсутствуют</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>Фатальная Ошибка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu столкнулся с фатальной ошибкой, смотрите подробности в журнале. Информацию о доступе к журналу можно найти на этой странице: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Как Отправить Файл Журнала&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Хотите вернуться в список игр? Продолжение эмуляции может привести к сбоям, повреждению сохраненных данных или другим ошибкам.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>Обнаружена Фатальная ошибка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Подтвердите Перерасчет Ключа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Вы собираетесь принудительно пересчитать все ваши ключи.
+Если вы не знаете, что это значит или что вы делаете,
+это потенциально разрушительное действие.
+Пожалуйста, убедитесь, что это то, что вы хотите
+и при желании сделайте резервные копии.
+
+Это удалит ваши автоматически сгенерированные файлы ключей и повторно запустит модуль расчета ключей.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Получение ключей...
+Это может занять до минуты в зависимости
+от производительности вашей системы.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>Получение Ключей</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Выберите Цель для Дампа RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Пожалуйста, выберите, какой RomFS вы хотите сдампить.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Вы уверены, что хотите закрыть yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Вы уверены, что хотите остановить эмуляцию? Любой несохраненный прогресс будет потерян.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>Запущенное в настоящий момент приложение просит yuzu не завершать работу.
+
+Проигнорировать и выйти в любом случае?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>Имя</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>Совместимость</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>Дополнения</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>Тип Файла</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>Размер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>Открыть Папку Сохранений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>Открыть Папку Модов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>Открыть Переносной Кеш Шейдеров</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>Сдампить RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Скопировать ID приложения в буфер обмена</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Перейти к записи GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>Свойства</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>Сканировать Подпапки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>Удалить Папку с Играми</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>Открыть Расположение Папки</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>Идеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Игра идёт безупречно, без звуковых или графических артефактов, все протестированные функции работают без обходных путей.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>Отлично</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Игра работает с небольшими графическими или звуковыми артефактами и может быть пройдена от начала до конца. Могут потребоваться обходные пути.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>Нормально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Игра работает с существенными графическими или звуковыми артефактами, но с обходными путями может быть пройдена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>Плохо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Игра работает, но с существенными графическими или звуковыми артефактами. В некоторых зонах нельзя продвинуться даже с обходными путями.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>Вступление/Меню</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>В игру невозможно играть из-за графических или звуковых артефактов. Невозможно продвинуться дальше Стартового Экрана.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Не Запускается</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Игра падает при запуске.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>Не Проверено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Игру ещё не проверяли на совместимость.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Двойной клик, чтобы добавить новую папку в список игр</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>Поиск:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>Введите текст для поиска</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Загрузка Шейдера 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Загрузка Шейдера %v из %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Примерное время 5м 4с</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>Загрузка...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Загрузка Шейдеров %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>Запуск...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>Примерное Время %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>&amp;Файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>Недавние Файлы</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>&amp;Эмуляция</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>&amp;Вид</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>Отладка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>Инструменты</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>&amp;Помощь</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>Загрузить Файл...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>Загрузить Папку...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>&amp;Выход</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>&amp;Начать</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>&amp;Пауза</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>&amp;Стоп</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>Переинициализировать ключи...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>О yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>Режим Одного Окна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>Настроить...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>Отображать Заголовки Виджетов Дока</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>Показать Панель Поиска</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>Показать Панель Статуса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>Полный Экран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>Перезапустить</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>Загрузить Amiibo…</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>Сообщить о Совместимости</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>Открыть Папку yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>Сделать Скриншот</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>МикроПрофиль</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>Установленные SD Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>Установленные NAND Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>Системные Игры</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>Добавить Новую Папку с Играми</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>Д-Пад %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>Ось %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>Кнопка %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[неизвестно]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[не используется]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>Ось %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>Произошла ошибка.
+Пожалуйста попробуйте еще раз или свяжитесь с разработчиком ПО.
+
+Код Ошибки: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>Произошла ошибка на %1 в %2.
+Пожалуйста попробуйте еще раз или свяжитесь с разработчиком ПО.
+
+Код Ошибки: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>Произошла ошибка.
+Код Ошибки: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>Выберите Пользователя:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>Пользователи</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>Выбор Профиля</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>Введите текст:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>Программная Клавиатура</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>Введите сочетание</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Стэк вызовов</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>ожидание мьютекса 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>ожидающие: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>ссылка на владельца: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>в ожидании всех объектов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>в ожидании одного из объектов</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>работает</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>готов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>приостановлен</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>ожидание возврата HLE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>сон</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>ожидание ответа IPC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>ожидание объектов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>ожидание мьютекса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>ожидание условной переменной </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>ожидание адресного арбитра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>бездействует</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>мертв</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>идеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>ядро %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>Неизвестный процессор %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>процессор = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>идеальное ядро = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>маска сходства = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>id потока = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>приоритет = %1(текущий) / %2(обычный)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>последние тики = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>не ожидает мьютекс</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>ожидается потоком</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>Дерево Ожидания</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/languages/zh_CN.ts b/dist/languages/zh_CN.ts
new file mode 100644
index 000000000..f953f224a
--- /dev/null
+++ b/dist/languages/zh_CN.ts
@@ -0,0 +1,4747 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="zh_CN" version="2.1">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>关于 yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu 是一个实验性的开源 Nintendo Switch 模拟器,以 GPLv2.0+ 授权。&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;这个软件不应该用来运行非法取得的游戏。&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;网站&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;源代码&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;贡献者&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;许可证&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; 是任天堂的商标。yuzu 与任天堂没有任何关系。&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="25"/>
+ <source>Communicating with the server...</source>
+ <translation>正在与服务器通信…</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="26"/>
+ <source>Cancel</source>
+ <translation>取消</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="44"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>触摸你的触摸板&lt;br&gt;的左上角。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="47"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>触摸你的触摸板&lt;br&gt;的右下角。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="50"/>
+ <source>Configuration completed!</source>
+ <translation>配置完成!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="55"/>
+ <source>OK</source>
+ <translation>确定</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>报告兼容性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>报告游戏兼容性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;如果您选择向 &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu 兼容性列表&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;提交测试用例的话,以下信息将会被收集并显示在网站上:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;设备硬件信息 (CPU / GPU / 操作系统)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;您正在使用的 yuzu 版本&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;已关联的 yuzu 账户信息&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>完美</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏运行完美,没有音频或图形问题。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great </source>
+ <translation>良好</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏运行时会有非常轻微的图像或音频问题,但是能从头玩到尾。可能需要一些技巧才能完成游戏。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>一般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏运行时会有很多图像或音频错误,但是在使用一些特殊技巧之后能完整地完成游戏。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>较差</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏能运行,但是会有大量图像或音频错误。即使使用一些技巧也无法通过游戏的某些区域。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>开场 / 菜单</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;游戏完全没法玩,图像或音频有重大错误。通过开场菜单后无法继续。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>无法打开</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;在启动游戏时直接崩溃了。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;body&gt;&lt;html&gt;&lt;head/&gt;&lt;p&gt;在不考虑速度或帧率的情况下,使用此版本 yuzu 玩这款游戏的情况如何?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>感谢您向我们提交信息!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="61"/>
+ <source>Submitting</source>
+ <translation>提交中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Communication error</source>
+ <translation>网络错误</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+ <source>An error occured while sending the Testcase</source>
+ <translation>在提交测试用例时发生错误。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="77"/>
+ <source>Next</source>
+ <translation>下一步</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="17"/>
+ <source>Audio</source>
+ <translation>声音</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="25"/>
+ <source>Output Engine:</source>
+ <translation>输出引擎:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="37"/>
+ <source>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</source>
+ <translation>这种后处理效果可以调整音频速度以匹配模拟速度,并有助于防止音频卡顿。 但是会增加音频延迟。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="40"/>
+ <source>Enable audio stretching</source>
+ <translation>启动音频拉伸</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="49"/>
+ <source>Audio Device:</source>
+ <translation>音频设备:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="77"/>
+ <source>Use global volume</source>
+ <translation>使用全局音量</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="82"/>
+ <source>Set volume:</source>
+ <translation>音量:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="90"/>
+ <source>Volume:</source>
+ <translation>音量:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="135"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="98"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="22"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="30"/>
+ <source>Accuracy:</source>
+ <translation>精度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="38"/>
+ <source>Accurate</source>
+ <translation>精确</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="43"/>
+ <source>Unsafe</source>
+ <translation>低精度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="48"/>
+ <source>Enable Debug Mode</source>
+ <translation>启用调试模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="61"/>
+ <source>We recommend setting accuracy to &quot;Accurate&quot;.</source>
+ <translation>我们推荐将精度设置为“精确”。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="75"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>低精度 CPU 优化选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="84"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>这些设置项提高了速度,但精度有所降低。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>低精度 FMA (在 CPU 不支持 FMA 指令集的情况下提高性能)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="94"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;该选项通过降低积和熔加运算的精度而提高模拟器在不支持 FMA 指令集 CPU 上的运行速度。&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="103"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>快速 FRSQRTE 和 FRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;该选项通过使用精度较低的近似值来提高某些浮点函数的运算速度。&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="133"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>只有当游戏不在运行时,CPU 设置才可用。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="43"/>
+ <source>Setting CPU to Debug Mode</source>
+ <translation>CPU 调试模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.cpp" line="44"/>
+ <source>CPU Debug Mode is only intended for developer use. Are you sure you want to enable this?</source>
+ <translation>CPU 调试模式仅用于开发人员。您确定要打开这个选项吗?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="22"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>切换 CPU 优化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>
+ &lt;div&gt;
+ &lt;b&gt;For debugging only.&lt;/b&gt;
+ &lt;br&gt;
+ If you're not sure what these do, keep all of these enabled.
+ &lt;br&gt;
+ These settings only take effect when CPU Accuracy is &quot;Debug Mode&quot;.
+ &lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;
+&lt;b&gt;仅供调试使用。&lt;/b&gt;
+&lt;br&gt;
+如果您不确定这些选项的作用,则保持它们的启用状态。
+&lt;br&gt;
+这些选项仅在 CPU 精度处于“调试模式”时生效。
+&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="46"/>
+ <source>Enable inline page tables</source>
+ <translation>启用内嵌页表</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="49"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div style=&quot;white-space: nowrap&quot;&gt;这个选项提升了来宾程序的内存访问速度。&lt;/div&gt;
+&lt;div style=&quot;white-space: nowrap&quot;&gt;启用内嵌到 PageTable::指向已发射代码的指针。&lt;/div&gt;
+&lt;div style=&quot;white-space: nowrap&quot;&gt;禁用此选项将强制通过 Memory::Read/Memory::Write 函数进行内存访问。&lt;/div&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation>启用块链接</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="63"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;该选项通过允许发出的基本块直接跳转到其他基本块(如果目标 PC 是静态的)来避免调度器的查找。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation>启用返回堆栈缓冲区</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="75"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;该选项通过跟踪 BL 指令的潜在返回地址来避免调度器查找。这近似于 CPU 返回堆栈缓冲区的情况。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation>启用快速调度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="87"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;启用两层调度系统。首先使用一个更快的调度程序跳转至目标 MRU 缓存。如果失败,调度返回到较慢的 C++ 调度程序。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation>启用上下文消除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="99"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;启用 IR 优化,以减少 CPU 对上下文结构的不必要访问。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation>启用恒定传播</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="111"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;启用涉及恒定传播的 IR 优化。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>启用其他优化</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="123"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div&gt;启用其他的 IR 优化。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="132"/>
+ <source>Enable misalignment check reduction</source>
+ <translation>减少偏差检查</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="135"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation>
+&lt;div style=&quot;white-space: nowrap&quot;&gt;启用时,只有当访问越过页面边界时才会触发偏移。&lt;/div&gt;
+&lt;div style=&quot;white-space: nowrap&quot;&gt;禁用时,所有未对齐的访问都会触发偏移。&lt;/div&gt;
+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="163"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>只有当游戏不在运行时,CPU 设置才可用。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="22"/>
+ <source>GDB</source>
+ <translation>GDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="30"/>
+ <source>Enable GDB Stub</source>
+ <translation>开启 GDB 调试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="50"/>
+ <source>Port:</source>
+ <translation>端口:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="71"/>
+ <source>Logging</source>
+ <translation>日志</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="79"/>
+ <source>Global Log Filter</source>
+ <translation>全局日志过滤器</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="93"/>
+ <source>Show Log Console (Windows Only)</source>
+ <translation>显示日志窗口(仅限 Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="100"/>
+ <source>Open Log Location</source>
+ <translation>打开日志位置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="112"/>
+ <source>Homebrew</source>
+ <translation>自制游戏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="120"/>
+ <source>Arguments String</source>
+ <translation>参数字符串</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="135"/>
+ <source>Graphics</source>
+ <translation>图形</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="144"/>
+ <source>When checked, the graphics API enters in a slower debugging mode</source>
+ <translation>启用时,图形 API 将进入较慢的调试模式。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>启用图形调试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="157"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</source>
+ <translation>启用时,将禁用宏即时编译器。这会降低游戏运行速度。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+ <source>Disable Macro JIT</source>
+ <translation>禁用宏 JIT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+ <source>Dump</source>
+ <translation>转储</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+ <source>Enable Verbose Reporting Services</source>
+ <translation>启用详细报告服务</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>This will be reset automatically when yuzu closes.</source>
+ <translation>当 yuzu 关闭时会自动重置。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Advanced</source>
+ <translation>高级选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="207"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Kiosk (Quest) 模式</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>调试控制器设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>清除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>系统默认</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>yuzu 设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="48"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="120"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="132"/>
+ <source>UI</source>
+ <translation>界面</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="59"/>
+ <source>Game List</source>
+ <translation>游戏列表</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="64"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="121"/>
+ <source>System</source>
+ <translation>系统</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="72"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="122"/>
+ <source>Profiles</source>
+ <translation>用户配置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="133"/>
+ <source>Filesystem</source>
+ <translation>文件系统</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="123"/>
+ <source>Controls</source>
+ <translation>控制</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="99"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="124"/>
+ <source>Hotkeys</source>
+ <translation>热键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="104"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="107"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="125"/>
+ <source>CPU</source>
+ <translation>CPU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="112"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="115"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="144"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="126"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="130"/>
+ <source>Debug</source>
+ <translation>调试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="120"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="123"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="127"/>
+ <source>Graphics</source>
+ <translation>图形</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="128"/>
+ <source>Advanced</source>
+ <translation>高级选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="131"/>
+ <source>GraphicsAdvanced</source>
+ <translation>高级图形选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="129"/>
+ <source>Audio</source>
+ <translation>声音</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="152"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="155"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="131"/>
+ <source>Web</source>
+ <translation>网络</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="160"/>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="163"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="134"/>
+ <source>Services</source>
+ <translation>服务</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="22"/>
+ <source>Storage Directories</source>
+ <translation>存储目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="28"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="111"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="140"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="230"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="48"/>
+ <source>SD Card</source>
+ <translation>SD 卡</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="81"/>
+ <source>Gamecard</source>
+ <translation>游戏卡带</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="87"/>
+ <source>Path</source>
+ <translation>路径</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="97"/>
+ <source>Inserted</source>
+ <translation>已插入</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="104"/>
+ <source>Current Game</source>
+ <translation>当前游戏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="121"/>
+ <source>Patch Manager</source>
+ <translation>补丁管理</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="149"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>转储已解压的 NSO 文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="156"/>
+ <source>Dump ExeFS</source>
+ <translation>转储 ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="165"/>
+ <source>Mod Load Root</source>
+ <translation>Mod 加载根目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="172"/>
+ <source>Dump Root</source>
+ <translation>转储根目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="198"/>
+ <source>Caching</source>
+ <translation>缓存中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="204"/>
+ <source>Cache Directory</source>
+ <translation>缓存目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="239"/>
+ <source>Cache Game List Metadata</source>
+ <translation>缓存游戏列表数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="246"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="138"/>
+ <source>Reset Metadata Cache</source>
+ <translation>重置缓存数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>选择模拟 NAND 目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>选择模拟 SD 卡目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+ <source>Select Gamecard Path...</source>
+ <translation>选择游戏卡带路径...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="101"/>
+ <source>Select Dump Directory...</source>
+ <translation>选择转储目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="104"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>选择 Mod 载入目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="107"/>
+ <source>Select Cache Directory...</source>
+ <translation>选择缓存目录...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>缓存数据已为空。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+ <source>The operation completed successfully.</source>
+ <translation>操作已成功完成。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="139"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>缓存数据删除失败。它可能不存在或正在被使用。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="22"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="32"/>
+ <source>Limit Speed Percent</source>
+ <translation>运行速度限制</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="39"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>多核 CPU 仿真</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="64"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>在游戏运行时退出需要确认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="71"/>
+ <source>Prompt for user on game boot</source>
+ <translation>游戏启动时提示选择用户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="78"/>
+ <source>Pause emulation when in background</source>
+ <translation>模拟器位于后台时暂停模拟</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="85"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>自动隐藏鼠标光标</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="22"/>
+ <source>API Settings</source>
+ <translation>API 设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="46"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="67"/>
+ <source>Device:</source>
+ <translation>设备:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="83"/>
+ <source>Graphics Settings</source>
+ <translation>图形设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="89"/>
+ <source>Use disk shader cache</source>
+ <translation>使用磁盘着色器缓存</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="96"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>使用 GPU 异步模拟</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="118"/>
+ <source>Aspect Ratio:</source>
+ <translation>屏幕纵横比:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="126"/>
+ <source>Default (16:9)</source>
+ <translation>默认 (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="131"/>
+ <source>Force 4:3</source>
+ <translation>强制 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="136"/>
+ <source>Force 21:9</source>
+ <translation>强制 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="141"/>
+ <source>Stretch to Window</source>
+ <translation>拉伸窗口</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="186"/>
+ <source>Use global background color</source>
+ <translation>使用全局背景颜色</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="191"/>
+ <source>Set background color:</source>
+ <translation>设置背景颜色:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="199"/>
+ <source>Background Color:</source>
+ <translation>背景颜色:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="196"/>
+ <source>OpenGL Graphics Device</source>
+ <translation>OpenGL 图形设备</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="22"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>高级图形选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="43"/>
+ <source>Accuracy Level:</source>
+ <translation>精度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="72"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>垂直同步可防止画面产生撕裂感。但启用垂直同步后,某些设备性能可能会有所降低。如果您没有感到性能差异,请保持启用状态。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>Use VSync (OpenGL only)</source>
+ <translation>启用垂直同步 (仅限 OpenGL 模式)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="82"/>
+ <source>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</source>
+ <translation>在支持 NV_gpu_program5 的 Nvidia 设备上启用 OpenGL 程序集着色器。启用此项将减少着色器卡顿。实验性功能。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Use assembly shaders (experimental, Nvidia OpenGL only)</source>
+ <translation>启用程序集着色器 (实验性,仅限 Nvidia OpenGL)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="92"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>启用异步着色器编译,这可能会减少着色器卡顿。实验性功能。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Use asynchronous shader building (experimental)</source>
+ <translation>启用异步着色器编译 (实验性)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="102"/>
+ <source>Use Fast GPU Time</source>
+ <translation>启用快速 GPU 时钟</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="124"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>各向异性过滤:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="132"/>
+ <source>Default</source>
+ <translation>系统默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="137"/>
+ <source>2x</source>
+ <translation>2×</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="142"/>
+ <source>4x</source>
+ <translation>4×</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="147"/>
+ <source>8x</source>
+ <translation>8×</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="152"/>
+ <source>16x</source>
+ <translation>16×</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>热键设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="22"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>双击已绑定的项目以改变设定。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="42"/>
+ <source>Clear All</source>
+ <translation>全部清除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="49"/>
+ <source>Restore Defaults</source>
+ <translation>恢复默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Action</source>
+ <translation>作用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Hotkey</source>
+ <translation>热键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+ <source>Context</source>
+ <translation>位置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>按键冲突</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>输入的密钥序列已分配给: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+ <source>Restore Default</source>
+ <translation>恢复默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>Clear</source>
+ <translation>清除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>默认密钥序列已分配给: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>输入设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>玩家 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>玩家 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>玩家 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>玩家 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>玩家 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>玩家 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>玩家 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>玩家 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>高级</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>控制台模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>Docked</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Undocked</source>
+ <translation>Undocked</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>震动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="212"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="231"/>
+ <source>Motion</source>
+ <translation>体感</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="267"/>
+ <source>Configure</source>
+ <translation>设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="302"/>
+ <source>Controllers</source>
+ <translation>控制器</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="330"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="371"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="381"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="391"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="401"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="411"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="421"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="431"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="441"/>
+ <source>Connected</source>
+ <translation>已连接</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="500"/>
+ <source>Defaults</source>
+ <translation>系统默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="543"/>
+ <source>Clear</source>
+ <translation>清除</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>输入设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Joycon 颜色</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>玩家 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>左侧主体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>左侧按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>右侧主体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>右侧按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>玩家 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>玩家 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>玩家 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>玩家 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>玩家 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>玩家 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>玩家 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Other</source>
+ <translation>其他</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>键盘</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2575"/>
+ <source>Advanced</source>
+ <translation>高级选项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2582"/>
+ <source>Touchscreen</source>
+ <translation>触摸屏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Mouse</source>
+ <translation>鼠标</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <source>Motion / Touch</source>
+ <translation>体感/触摸</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Configure</source>
+ <translation>设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <source>Debug Controller</source>
+ <translation>控制器调试</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>输入设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>已连接控制器</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="88"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="358"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="379"/>
+ <source>Pro Controller</source>
+ <translation>Pro Controller</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+ <source>Dual Joycons</source>
+ <translation>双 Joycons 手柄</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="98"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="360"/>
+ <source>Left Joycon</source>
+ <translation>左 Joycon 手柄</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <source>Right Joycon</source>
+ <translation>右 Joycon 手柄</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="108"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="365"/>
+ <source>Handheld</source>
+ <translation>掌机模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="119"/>
+ <source>Input Device</source>
+ <translation>输入设备</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="141"/>
+ <source>Any</source>
+ <translation>任意</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="146"/>
+ <source>Keyboard/Mouse</source>
+ <translation>键盘/鼠标</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="182"/>
+ <source>Profile</source>
+ <translation>用户配置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="215"/>
+ <source>Save</source>
+ <translation>保存</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="231"/>
+ <source>New</source>
+ <translation>新建</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="247"/>
+ <source>Delete</source>
+ <translation>删除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="310"/>
+ <source>Left Stick</source>
+ <translation>左摇杆</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="368"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="410"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="944"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="983"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2457"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2496"/>
+ <source>Up</source>
+ <translation>上</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="441"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="480"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1014"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1053"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2527"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2566"/>
+ <source>Left</source>
+ <translation>左</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="490"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1063"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2576"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2615"/>
+ <source>Right</source>
+ <translation>右</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="572"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="611"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1145"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2658"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2697"/>
+ <source>Down</source>
+ <translation>下</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="642"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="681"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2728"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2767"/>
+ <source>Pressed</source>
+ <translation>按下</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="691"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="730"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2777"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2816"/>
+ <source>Modifier</source>
+ <translation>轻推</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="740"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2826"/>
+ <source>Range</source>
+ <translation>灵敏度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="773"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2859"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="816"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2899"/>
+ <source>Deadzone: 0%</source>
+ <translation>摇杆死区:0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="840"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2923"/>
+ <source>Modifier Range: 0%</source>
+ <translation>摇杆灵敏度:0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="886"/>
+ <source>D-Pad</source>
+ <translation>十字方向键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1270"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1309"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1319"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1358"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1423"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1462"/>
+ <source>Minus</source>
+ <translation>-</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1472"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1511"/>
+ <source>Capture</source>
+ <translation>截图</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1542"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1581"/>
+ <source>Plus</source>
+ <translation>+</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1591"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1630"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1695"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1734"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1744"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1783"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1848"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1887"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1897"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1936"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2044"/>
+ <source>Face Buttons</source>
+ <translation>主要按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2102"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2141"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2172"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2211"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2221"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2260"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2303"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2342"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2390"/>
+ <source>Right Stick</source>
+ <translation>右摇杆</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="338"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="618"/>
+ <source>Deadzone: %1%</source>
+ <translation>摇杆死区:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Modifier Range: %1%</source>
+ <translation>摇杆灵敏度:%1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <source>[waiting]</source>
+ <translation>[等待中]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>体感/触摸设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="20"/>
+ <source>Motion</source>
+ <translation>体感</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="28"/>
+ <source>Motion Provider:</source>
+ <translation>体感设备:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="42"/>
+ <source>Sensitivity:</source>
+ <translation>灵敏度:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="76"/>
+ <source>Touch</source>
+ <translation>触摸</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="84"/>
+ <source>Touch Provider:</source>
+ <translation>触摸设备:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="98"/>
+ <source>Calibration:</source>
+ <translation>触摸校准:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="105"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="121"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="154"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="227"/>
+ <source>Configure</source>
+ <translation>设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="138"/>
+ <source>Use button mapping:</source>
+ <translation>使用按键映射:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="166"/>
+ <source>CemuhookUDP Config</source>
+ <translation>CemuhookUDP 设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="172"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>您可以使用任何与 Cemuhook 兼容的 UDP 输入源来提供体感和触摸输入。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="187"/>
+ <source>Server:</source>
+ <translation>服务器:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="208"/>
+ <source>Port:</source>
+ <translation>端口:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="229"/>
+ <source>Pad:</source>
+ <translation>Pad:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="237"/>
+ <source>Pad 1</source>
+ <translation>Pad 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="242"/>
+ <source>Pad 2</source>
+ <translation>Pad 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Pad 3</source>
+ <translation>Pad 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="252"/>
+ <source>Pad 4</source>
+ <translation>Pad 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="264"/>
+ <source>Learn More</source>
+ <translation>了解更多</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="277"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="250"/>
+ <source>Test</source>
+ <translation>测试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="78"/>
+ <source>Mouse (Right Click)</source>
+ <translation>鼠标 (右键)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="84"/>
+ <source>CemuhookUDP</source>
+ <translation>CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="83"/>
+ <source>Emulator Window</source>
+ <translation>模拟器窗口</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="101"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;了解更多&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="192"/>
+ <source>Testing</source>
+ <translation>测试中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="209"/>
+ <source>Configuring</source>
+ <translation>配置中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="241"/>
+ <source>Test Successful</source>
+ <translation>测试成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="242"/>
+ <source>Successfully received data from the server.</source>
+ <translation>已成功地从服务器获取数据。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="244"/>
+ <source>Test Failed</source>
+ <translation>测试失败</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>无法从服务器获取数据。&lt;br&gt;请验证服务器是否正在运行,以及地址和端口是否配置正确。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="272"/>
+ <source>Citra</source>
+ <translation>Citra</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="273"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>UDP 测试或触摸校准正在进行中。&lt;br&gt;请耐心等待。</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMouseAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="14"/>
+ <source>Configure Mouse</source>
+ <translation>鼠标设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="25"/>
+ <source>Mouse Buttons</source>
+ <translation>鼠标按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="35"/>
+ <source>Forward:</source>
+ <translation>前:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="75"/>
+ <source>Back:</source>
+ <translation>后:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="103"/>
+ <source>Left:</source>
+ <translation>左:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="131"/>
+ <source>Middle:</source>
+ <translation>中:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="197"/>
+ <source>Right:</source>
+ <translation>右:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="270"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="109"/>
+ <source>Clear</source>
+ <translation>清除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.ui" line="289"/>
+ <source>Defaults</source>
+ <translation>系统默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="111"/>
+ <source>[not set]</source>
+ <translation>[未设置]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="113"/>
+ <source>Restore Default</source>
+ <translation>恢复默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="200"/>
+ <source>[press key]</source>
+ <translation>[请按一个键]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>对话框</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="28"/>
+ <source>Info</source>
+ <translation>信息</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="87"/>
+ <source>Name</source>
+ <translation>名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="94"/>
+ <source>Title ID</source>
+ <translation>游戏 ID</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="131"/>
+ <source>Filename</source>
+ <translation>文件名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="158"/>
+ <source>Format</source>
+ <translation>格式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="165"/>
+ <source>Version</source>
+ <translation>版本</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="172"/>
+ <source>Size</source>
+ <translation>大小</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="179"/>
+ <source>Developer</source>
+ <translation>开发商</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="225"/>
+ <source>Add-Ons</source>
+ <translation>附加项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="230"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="235"/>
+ <source>System</source>
+ <translation>系统</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="240"/>
+ <source>Graphics</source>
+ <translation>图形</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="245"/>
+ <source>Adv. Graphics</source>
+ <translation>高级图形</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="250"/>
+ <source>Audio</source>
+ <translation>声音</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="38"/>
+ <source>Properties</source>
+ <translation>属性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="131"/>
+ <source>Use global configuration (%1)</source>
+ <translation>使用全局设置 (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+ <source>Patch Name</source>
+ <translation>补丁名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+ <source>Version</source>
+ <translation>版本</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="22"/>
+ <source>Profile Manager</source>
+ <translation>用户配置管理</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="39"/>
+ <source>Current User</source>
+ <translation>当前用户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="77"/>
+ <source>Username</source>
+ <translation>用户名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="107"/>
+ <source>Set Image</source>
+ <translation>设置图像</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="127"/>
+ <source>Add</source>
+ <translation>添加</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="137"/>
+ <source>Rename</source>
+ <translation>重命名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="147"/>
+ <source>Remove</source>
+ <translation>移除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="159"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>只有当游戏不在运行时,才能进行用户配置的管理。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="54"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="72"/>
+ <source>Enter Username</source>
+ <translation>输入用户名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="135"/>
+ <source>Users</source>
+ <translation>用户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="199"/>
+ <source>Enter a username for the new user:</source>
+ <translation>输入新用户的用户名:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="219"/>
+ <source>Enter a new username:</source>
+ <translation>输入新的用户名:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+ <source>Confirm Delete</source>
+ <translation>确认删除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="245"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>您确定要删除用户名为 “%1” 的用户吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>Select User Image</source>
+ <translation>选择用户图像</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="270"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>JPEG 图像 (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error deleting image</source>
+ <translation>删除图像时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="280"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>尝试覆盖该用户的现有图像时出错: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Error deleting file</source>
+ <translation>删除文件时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="289"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>无法删除文件: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Error creating user image directory</source>
+ <translation>创建用户图像目录时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="297"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>无法创建存储用户图像的目录 %1 。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Error copying user image</source>
+ <translation>复制用户图像时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="303"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>无法将图像从 %1 复制到 %2</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureService</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="22"/>
+ <source>BCAT</source>
+ <translation>BCAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="34"/>
+ <source>BCAT is Nintendo&apos;s way of sending data to games to engage its community and unlock additional content.</source>
+ <translation>BCAT 是任天堂向游戏发送数据的一种方式,以此吸引游戏玩家,并可能解锁额外的内容。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="50"/>
+ <source>BCAT Backend</source>
+ <translation>BCAT 后端</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;了解关于 BCAT、Boxcat 和当前事件的更多信息&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="81"/>
+ <source>The boxcat service is offline or you are not connected to the internet.</source>
+ <translation>Boxcat 服务处于离线状态,或者您没有连接到互联网。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="84"/>
+ <source>There was an error while processing the boxcat event data. Contact the yuzu developers.</source>
+ <translation>处理 Boxcat 事件数据时出错。请联系 yuzu 开发者。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="88"/>
+ <source>The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu.</source>
+ <translation>您使用的 yuzu 版本过新或过旧。尝试更新到官方的最新版本。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="94"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>There are currently no events on boxcat.</source>
+ <translation>当前 Boxcat 上没有事件。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="109"/>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="111"/>
+ <source>Current Boxcat Events</source>
+ <translation>当前 Boxcat 事件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_service.cpp" line="121"/>
+ <source>Yuzu is retrieving the latest boxcat status...</source>
+ <translation>Yuzu 正在检索 Boxcat 的最新状态...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="22"/>
+ <source>System Settings</source>
+ <translation>系统设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="30"/>
+ <source>Region:</source>
+ <translation>地区:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="38"/>
+ <source>Auto</source>
+ <translation>自动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="43"/>
+ <source>Default</source>
+ <translation>系统默认</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="48"/>
+ <source>CET</source>
+ <translation>欧洲中部时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="53"/>
+ <source>CST6CDT</source>
+ <translation>古巴标准时间&amp;古巴夏令时</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="58"/>
+ <source>Cuba</source>
+ <translation>古巴</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="63"/>
+ <source>EET</source>
+ <translation>东欧时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="68"/>
+ <source>Egypt</source>
+ <translation>埃及</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="73"/>
+ <source>Eire</source>
+ <translation>爱尔兰</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="78"/>
+ <source>EST</source>
+ <translation>东部标准时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="83"/>
+ <source>EST5EDT</source>
+ <translation>东部标准时间&amp;东部夏令时</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="88"/>
+ <source>GB</source>
+ <translation>国家标准时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="93"/>
+ <source>GB-Eire</source>
+ <translation>爱尔兰国家标准时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="98"/>
+ <source>GMT</source>
+ <translation>格林威治标准时间 (GMT)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="103"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="108"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="113"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="118"/>
+ <source>Greenwich</source>
+ <translation>格林威治</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="123"/>
+ <source>Hongkong</source>
+ <translation>中国香港</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="128"/>
+ <source>HST</source>
+ <translation>美国夏威夷时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="133"/>
+ <source>Iceland</source>
+ <translation>冰岛</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="138"/>
+ <source>Iran</source>
+ <translation>伊朗</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="143"/>
+ <source>Israel</source>
+ <translation>以色列</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="148"/>
+ <source>Jamaica</source>
+ <translation>牙买加</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="153"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="272"/>
+ <source>Japan</source>
+ <translation>日本</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="158"/>
+ <source>Kwajalein</source>
+ <translation>夸贾林环礁</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="163"/>
+ <source>Libya</source>
+ <translation>利比亚</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="168"/>
+ <source>MET</source>
+ <translation>中欧时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="173"/>
+ <source>MST</source>
+ <translation>山区标准时间 (北美)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="178"/>
+ <source>MST7MDT</source>
+ <translation>山区标准时间&amp;山区夏令时 (北美)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="183"/>
+ <source>Navajo</source>
+ <translation>纳瓦霍</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="188"/>
+ <source>NZ</source>
+ <translation>新西兰时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="193"/>
+ <source>NZ-CHAT</source>
+ <translation>新西兰-查塔姆群岛</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="198"/>
+ <source>Poland</source>
+ <translation>波兰</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="203"/>
+ <source>Portugal</source>
+ <translation>葡萄牙</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="208"/>
+ <source>PRC</source>
+ <translation>中国标准时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="213"/>
+ <source>PST8PDT</source>
+ <translation>太平洋标准时间&amp;太平洋夏令时</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="218"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="223"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="228"/>
+ <source>Singapore</source>
+ <translation>新加坡</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="233"/>
+ <source>Turkey</source>
+ <translation>土耳其</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="238"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="243"/>
+ <source>Universal</source>
+ <translation>世界时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="248"/>
+ <source>UTC</source>
+ <translation>协调世界时</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="253"/>
+ <source>W-SU</source>
+ <translation>欧洲-莫斯科时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="258"/>
+ <source>WET</source>
+ <translation>西欧时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="263"/>
+ <source>Zulu</source>
+ <translation>祖鲁</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="277"/>
+ <source>USA</source>
+ <translation>美国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="282"/>
+ <source>Europe</source>
+ <translation>欧洲</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="287"/>
+ <source>Australia</source>
+ <translation>澳大利亚</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="292"/>
+ <source>China</source>
+ <translation>中国</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="297"/>
+ <source>Korea</source>
+ <translation>朝鲜</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="302"/>
+ <source>Taiwan</source>
+ <translation>中国台湾</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="310"/>
+ <source>Time Zone:</source>
+ <translation>时区:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="317"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>注意:当“地区”设置是“自动选择”时,此设置可能会被覆盖。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="321"/>
+ <source>Japanese (日本語)</source>
+ <translation>日语 (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="326"/>
+ <source>English</source>
+ <translation>英语</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="331"/>
+ <source>French (français)</source>
+ <translation>法语 (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="336"/>
+ <source>German (Deutsch)</source>
+ <translation>德语 (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="341"/>
+ <source>Italian (italiano)</source>
+ <translation>意大利语 (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="346"/>
+ <source>Spanish (español)</source>
+ <translation>西班牙语 (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="351"/>
+ <source>Chinese</source>
+ <translation>中文</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="356"/>
+ <source>Korean (한국어)</source>
+ <translation>韩语 (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="361"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>荷兰语 (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="366"/>
+ <source>Portuguese (português)</source>
+ <translation>葡萄牙语 (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="371"/>
+ <source>Russian (Русский)</source>
+ <translation>俄语 (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="376"/>
+ <source>Taiwanese</source>
+ <translation>台湾中文</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="381"/>
+ <source>British English</source>
+ <translation>英式英语</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="386"/>
+ <source>Canadian French</source>
+ <translation>加拿大法语</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="391"/>
+ <source>Latin American Spanish</source>
+ <translation>拉美西班牙语</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="396"/>
+ <source>Simplified Chinese</source>
+ <translation>简体中文</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="401"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>繁体中文 (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Custom RTC</source>
+ <translation>自定义系统时间</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="416"/>
+ <source>Language</source>
+ <translation>语言</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="423"/>
+ <source>RNG Seed</source>
+ <translation>随机数生成器种子</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>Mono</source>
+ <translation>单声道</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="436"/>
+ <source>Stereo</source>
+ <translation>立体声</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="441"/>
+ <source>Surround</source>
+ <translation>环绕声</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Console ID:</source>
+ <translation>设备 ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="456"/>
+ <source>Sound output mode</source>
+ <translation>声音输出模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="470"/>
+ <source>d MMM yyyy h:mm:ss AP</source>
+ <translation>d MMM yyyy h:mm:ss AP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="507"/>
+ <source>Regenerate</source>
+ <translation>重置 ID</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="532"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>只有当游戏不在运行时,系统设置才可用。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="197"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>这将使用一个新的虚拟 Switch 取代你当前的虚拟 Switch。您当前的虚拟 Switch 将无法恢复。在部分游戏中可能会出现意外效果。如果你使用一个过时的配置存档这可能会失败。确定要继续吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="201"/>
+ <source>Warning</source>
+ <translation>警告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="209"/>
+ <source>Console ID: 0x%1</source>
+ <translation>设备 ID: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>配置触摸屏映射</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>映射:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>新建</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>删除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>重命名</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>单击屏幕底部区域添加点位,然后按下按键进行绑定。
+拖动点位以改变位置,或双击列表项更改绑定。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>删除点位</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Button</source>
+ <translation>按键</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>New Profile</source>
+ <translation>保存自定义设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="200"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>为新的自定义设置命名。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete Profile</source>
+ <translation>删除自定义设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="211"/>
+ <source>Delete profile %1?</source>
+ <translation>真的要删除自定义设置 %1 吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>Rename Profile</source>
+ <translation>重命名自定义设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="224"/>
+ <source>New name:</source>
+ <translation>新名称:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="233"/>
+ <source>[press key]</source>
+ <translation>[请按一个键]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>触摸屏设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>警告:此页面中的设置会影响 yuzu 的模拟触摸屏的内部工作方式。改变它们可能造成不良后果,例如触摸屏完全或部分失效。如果您不知道自己在做什么,请不要使用此页面。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>触摸参数</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>触摸直径 Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="78"/>
+ <source>Finger</source>
+ <translation>手指</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Touch Diameter X</source>
+ <translation>触摸直径 X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="115"/>
+ <source>Rotational Angle</source>
+ <translation>旋转角度</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="149"/>
+ <source>Restore Defaults</source>
+ <translation>恢复默认</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="20"/>
+ <source>General</source>
+ <translation>通用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="28"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>注意: 切换语言将应用您的配置。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="40"/>
+ <source>Interface language:</source>
+ <translation>界面语言:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="54"/>
+ <source>Theme:</source>
+ <translation>主题:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="71"/>
+ <source>Game List</source>
+ <translation>游戏列表</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="79"/>
+ <source>Show Add-Ons Column</source>
+ <translation>显示“附加项”列</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="88"/>
+ <source>Icon Size:</source>
+ <translation>图标大小:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="102"/>
+ <source>Row 1 Text:</source>
+ <translation>第一行:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="116"/>
+ <source>Row 2 Text:</source>
+ <translation>第二行:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+ <source>Screenshots</source>
+ <translation>捕获截图</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="141"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>询问保存截图的位置 (仅限 Windows )</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+ <source>Screenshots Path: </source>
+ <translation>截图保存位置:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="160"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="64"/>
+ <source>Select Screenshots Path...</source>
+ <translation>选择截图保存位置...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="131"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="132"/>
+ <source>English</source>
+ <translation>英语</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Form</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="22"/>
+ <source>yuzu Web Service</source>
+ <translation>yuzu 网络服务</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="28"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>提供您的用户名和令牌意味着您同意让 yuzu 收集额外的使用数据,其中可能包括用户识别信息。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+ <source>Verify</source>
+ <translation>验证</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="53"/>
+ <source>Sign up</source>
+ <translation>注册</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="63"/>
+ <source>Token: </source>
+ <translation>令牌:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="74"/>
+ <source>Username: </source>
+ <translation>用户名:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="91"/>
+ <source>What is my token?</source>
+ <translation>我的令牌是?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="116"/>
+ <source>Telemetry</source>
+ <translation>使用数据共享</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="122"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>与 yuzu 团队共享匿名使用数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="129"/>
+ <source>Learn more</source>
+ <translation>了解更多</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="138"/>
+ <source>Telemetry ID:</source>
+ <translation>数据 ID:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="154"/>
+ <source>Regenerate</source>
+ <translation>重新生成</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="168"/>
+ <source>Discord Presence</source>
+ <translation>Discord 状态</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="174"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>在您的 Discord 状态中显示当前游戏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;了解更多&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;注册&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;我的令牌是?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>数据 ID: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+ <source>Unspecified</source>
+ <translation>未指定</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token not verified</source>
+ <translation>令牌未被验证</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>令牌未被验证。您对用户名和令牌的更改尚未保存。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+ <source>Verifying...</source>
+ <translation>验证中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+ <source>Verification failed</source>
+ <translation>验证失败</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>验证失败。请检查您输入的令牌是否正确,并且确保您的互联网连接正常。</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="165"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;我们收集匿名数据&lt;/a&gt;来帮助改进 yuzu 。&lt;br/&gt;&lt;br/&gt;您愿意和我们分享您的使用数据吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="168"/>
+ <source>Telemetry</source>
+ <translation>使用数据共享</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="326"/>
+ <source>Text Check Failed</source>
+ <translation>文本检查失败</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="339"/>
+ <source>Loading Web Applet...</source>
+ <translation>正在加载 Web 应用程序...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="387"/>
+ <source>Exit Web Applet</source>
+ <translation>退出 Web 应用程序</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Exit</source>
+ <translation>退出</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>To exit the web application, use the game provided controls to select exit, select the &apos;Exit Web Applet&apos; option in the menu bar, or press the &apos;Enter&apos; key.</source>
+ <translation>若要退出 Web 应用程序,请使用游戏内提供的方式退出,在菜单栏中选择“退出 Web 应用程序”选项,或者按 &quot;Enter&quot; 键。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="458"/>
+ <source>Web Applet</source>
+ <translation>Web 应用程序</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="459"/>
+ <source>This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested.</source>
+ <translation>此版本的 yuzu 没有 QtWebEngine 的支持。这意味着 yuzu 无法正确显示所请求的游戏手册或网页。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="507"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>当前正在构建的着色器的数量</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="510"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>当前的模拟速度。高于或低于 100% 的值表示模拟正在运行得比实际 Switch 更快或更慢。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="513"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>游戏当前运行的帧率。这将因游戏和场景的不同而有所变化。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="517"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>在不计算速度限制和垂直同步的情况下,模拟一个 Switch 帧的实际时间。若要进行全速模拟,这个数值不应超过 16.67 毫秒。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="537"/>
+ <source>DOCK</source>
+ <translation>DOCK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="556"/>
+ <source>ASYNC</source>
+ <translation>异步</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="576"/>
+ <source>MULTICORE</source>
+ <translation>多核</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="588"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="647"/>
+ <source>Clear Recent Files</source>
+ <translation>清除最近文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="990"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>过时游戏格式警告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="991"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>目前使用的游戏为解体的 ROM 目录格式,这是一种过时的格式,已被其他格式替代,如 NCA,NAX,XCI 或 NSP。解体的 ROM 目录缺少图标、元数据和更新支持。&lt;br&gt;&lt;br&gt;有关 yuzu 支持的各种 Switch 格式的说明,&lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;请查看我们的 wiki&lt;/a&gt;。此消息将不会再次出现。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1003"/>
+ <location filename="../../src/yuzu/main.cpp" line="1036"/>
+ <source>Error while loading ROM!</source>
+ <translation>加载 ROM 时出错!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1004"/>
+ <source>The ROM format is not supported.</source>
+ <translation>该 ROM 格式不受支持。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1008"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>在初始化视频核心时发生错误。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1009"/>
+ <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+ <translation>yuzu 在运行视频核心时遇到错误,请查看日志了解更多详细信息。有关查看日志的更多信息,请参阅以下页面:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;。请确保您的 GPU 安装了最新的图形驱动程序。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1028"/>
+ <source>Error while loading ROM! </source>
+ <translation>加载 ROM 时出错!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1037"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>发生了未知错误。请查看日志了解详情。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1169"/>
+ <source>Start</source>
+ <translation>开始</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1274"/>
+ <source>Save Data</source>
+ <translation>保存数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1318"/>
+ <source>Mod Data</source>
+ <translation>Mod 数据</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1330"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>打开 %1 文件夹时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1331"/>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Folder does not exist!</source>
+ <translation>文件夹不存在!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1349"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>打开可转移着色器缓存时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1350"/>
+ <location filename="../../src/yuzu/main.cpp" line="1544"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>这个游戏的着色器缓存不存在。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1414"/>
+ <source>Contents</source>
+ <translation>目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1416"/>
+ <source>Update</source>
+ <translation>游戏更新</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1418"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Entry</source>
+ <translation>删除项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1425"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>删除已安装的游戏 %1 ?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1455"/>
+ <location filename="../../src/yuzu/main.cpp" line="1471"/>
+ <location filename="../../src/yuzu/main.cpp" line="1502"/>
+ <location filename="../../src/yuzu/main.cpp" line="1549"/>
+ <location filename="../../src/yuzu/main.cpp" line="1570"/>
+ <source>Successfully Removed</source>
+ <translation>删除成功</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1456"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>成功删除已安装的游戏。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1459"/>
+ <location filename="../../src/yuzu/main.cpp" line="1474"/>
+ <location filename="../../src/yuzu/main.cpp" line="1497"/>
+ <source>Error Removing %1</source>
+ <translation>删除 %1 时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1460"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>该游戏未安装于 NAND 中,无法删除。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1472"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>成功删除已安装的游戏更新。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1475"/>
+ <source>There is no update installed for this title.</source>
+ <translation>这个游戏没有任何已安装的更新。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1498"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>这个游戏没有任何已安装的 DLC 。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1503"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>成功删除游戏 %1 安装的 DLC 。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1510"/>
+ <source>Delete Transferable Shader Cache?</source>
+ <translation>删除着色器缓存?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1512"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>移除自定义游戏设置?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1518"/>
+ <source>Remove File</source>
+ <translation>删除文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1543"/>
+ <location filename="../../src/yuzu/main.cpp" line="1552"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>删除着色器缓存时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1550"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>成功删除着色器缓存。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1553"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>删除着色器缓存失败。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1564"/>
+ <location filename="../../src/yuzu/main.cpp" line="1573"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>移除自定义游戏设置时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1565"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>这个游戏的自定义设置不存在。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1571"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>成功移除自定义游戏设置。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1574"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>移除自定义游戏设置失败。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>RomFS 提取失败!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1581"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>复制 RomFS 文件时出错,或用户取消了操作。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Full</source>
+ <translation>完整</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1633"/>
+ <source>Skeleton</source>
+ <translation>框架</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1635"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>选择 RomFS 转储模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1636"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>请选择希望 RomFS 转储的方式。&lt;br&gt;“Full” 会将所有文件复制到新目录中,而&lt;br&gt;“Skeleton” 只会创建目录结构。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <source>Extracting RomFS...</source>
+ <translation>正在提取 RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1649"/>
+ <location filename="../../src/yuzu/main.cpp" line="1822"/>
+ <source>Cancel</source>
+ <translation>取消</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1656"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>RomFS 提取成功!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1657"/>
+ <source>The operation completed successfully.</source>
+ <translation>操作成功完成。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1698"/>
+ <source>Error Opening %1</source>
+ <translation>打开 %1 时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1705"/>
+ <source>Select Directory</source>
+ <translation>选择目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1731"/>
+ <source>Properties</source>
+ <translation>属性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1732"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>无法加载该游戏的属性信息。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1744"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Switch 可执行文件 (%1);;所有文件 (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1748"/>
+ <source>Load File</source>
+ <translation>加载文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1760"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>打开提取的 ROM 目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1771"/>
+ <source>Invalid Directory Selected</source>
+ <translation>选择的目录无效</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1772"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>选择的目录不包含 “main” 文件。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1782"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>可安装的 Switch 文件 (*.nca *.nsp *.xci);;任天堂内容档案 (*.nca);;任天堂应用包 (*.nsp);;NX 卡带镜像 (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1787"/>
+ <source>Install Files</source>
+ <translation>安装文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1832"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>正在安装文件 &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1880"/>
+ <source>Install Results</source>
+ <translation>安装结果</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1977"/>
+ <source>System Application</source>
+ <translation>系统应用</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1978"/>
+ <source>System Archive</source>
+ <translation>系统档案</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1979"/>
+ <source>System Application Update</source>
+ <translation>系统应用更新</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1980"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>固件包 (A型)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1981"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>固件包 (B型)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1982"/>
+ <source>Game</source>
+ <translation>游戏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1983"/>
+ <source>Game Update</source>
+ <translation>游戏更新</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1984"/>
+ <source>Game DLC</source>
+ <translation>游戏 DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1985"/>
+ <source>Delta Title</source>
+ <translation>差量程序</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1988"/>
+ <source>Select NCA Install Type...</source>
+ <translation>选择 NCA 安装类型...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1989"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>请选择此 NCA 的程序类型:
+(在大多数情况下,选择默认的“游戏”即可。)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1995"/>
+ <source>Failed to Install</source>
+ <translation>安装失败</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1996"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>选择的 NCA 程序类型无效。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2037"/>
+ <source>File not found</source>
+ <translation>找不到文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2038"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>文件 &quot;%1&quot; 未找到</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2060"/>
+ <location filename="../../src/yuzu/main.cpp" line="2867"/>
+ <source>Continue</source>
+ <translation>继续</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2101"/>
+ <source>Error Display</source>
+ <translation>错误显示</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2111"/>
+ <source>Missing yuzu Account</source>
+ <translation>未设置 yuzu 账户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2112"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>要提交游戏兼容性测试用例,您必须设置您的 yuzu 帐户。&lt;br&gt;&lt;br/&gt;要设置您的 yuzu 帐户,请转到模拟 &amp;gt; 设置 &amp;gt; 网络。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Error opening URL</source>
+ <translation>打开 URL 时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2123"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>无法打开 URL : &quot;%1&quot; 。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Amiibo 文件 (%1);; 全部文件 (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Load Amiibo</source>
+ <translation>加载 Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2307"/>
+ <source>Error opening Amiibo data file</source>
+ <translation>打开 Amiibo 数据文件时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2308"/>
+ <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+ <translation>无法打开 Amiibo 文件 %1。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2316"/>
+ <source>Error reading Amiibo data file</source>
+ <translation>读取 Amiibo 数据文件时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2317"/>
+ <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+ <translation>无法完全读取 Amiibo 数据。应读取 %1 个字节,但实际仅能读取 %2 个字节。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2325"/>
+ <source>Error loading Amiibo data</source>
+ <translation>加载 Amiibo 数据时出错</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2326"/>
+ <source>Unable to load Amiibo data.</source>
+ <translation>无法加载 Amiibo 数据。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2364"/>
+ <source>Capture Screenshot</source>
+ <translation>捕获截图</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>PNG Image (*.png)</source>
+ <translation>PNG 图像 (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2418"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>速度: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2422"/>
+ <source>Speed: %1%</source>
+ <translation>速度: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2424"/>
+ <source>Game: %1 FPS</source>
+ <translation>FPS: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2425"/>
+ <source>Frame: %1 ms</source>
+ <translation>帧延迟:%1 毫秒</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2473"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>您正尝试启动的游戏需要从 Switch 转储的其他文件。&lt;br/&gt;&lt;br/&gt;有关转储这些文件的更多信息,请参阅以下 wiki 页面:&lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;您要退出并返回至游戏列表吗?继续模拟可能会导致崩溃,存档损坏或其他错误。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2488"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>Yuzu 找不到 Switch 系统档案 %1 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2490"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>Yuzu 找不到 Switch 系统档案: %1, %2 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2494"/>
+ <source>System Archive Not Found</source>
+ <translation>未找到系统档案</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2496"/>
+ <source>System Archive Missing</source>
+ <translation>系统档案缺失</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2502"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>Yuzu 找不到 Swtich 共享字体 %1 </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2503"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>未找到共享字体</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2505"/>
+ <source>Shared Font Missing</source>
+ <translation>共享字体文件缺失</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2511"/>
+ <source>Fatal Error</source>
+ <translation>致命错误</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2512"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu 遇到了致命错误,请查看日志了解详情。有关查找日志的更多信息,请参阅以下页面:&lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;。&lt;br/&gt;&lt;br/&gt;您要退出并返回至游戏列表吗?继续模拟可能会导致崩溃,存档损坏或其他错误。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2521"/>
+ <source>Fatal Error encountered</source>
+ <translation>发生致命错误</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2544"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>确认重新生成密钥</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2545"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>即将强制重新生成您的全部密钥。
+如果您不清楚这意味着什么,或您在做什么,
+这可能具有破坏性后果。
+请确保您希望这样做,并且做好备份。
+
+这将删除您自动生成的密钥文件并重新运行密钥生成模块。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2578"/>
+ <source>Missing fuses</source>
+ <translation>项目丢失</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2581"/>
+ <source> - Missing BOOT0</source>
+ <translation>- 丢失 BOOT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2584"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation> - 丢失 BCPKG2-1-Normal-Main</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2587"/>
+ <source> - Missing PRODINFO</source>
+ <translation>- 丢失 PRODINFO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2591"/>
+ <source>Derivation Components Missing</source>
+ <translation>组件丢失</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2592"/>
+ <source>Components are missing that may hinder key derivation from completing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>缺少组件可能会影响密钥的使用。&lt;br&gt;请查看&lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;yuzu 快速导航&lt;/a&gt;以获得你的密钥和游戏。&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2601"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>正在生成密钥...
+这可能需要最多一分钟,具体取决于
+您的系统性能。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2603"/>
+ <source>Deriving Keys</source>
+ <translation>生成密钥</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2648"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>选择 RomFS 转储目标</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2649"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>请选择希望转储的 RomFS。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <location filename="../../src/yuzu/main.cpp" line="2756"/>
+ <location filename="../../src/yuzu/main.cpp" line="2767"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2665"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>您确定要关闭 yuzu 吗?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2757"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>您确定要停止模拟吗?未保存的进度将会丢失。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2768"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>当前运行的应用程序请求 yuzu 不要退出。
+
+您希望忽略并退出吗?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="600"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL 模式不可用!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="601"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzu 没有使用 OpenGL 进行编译。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="615"/>
+ <source>Vulkan not available!</source>
+ <translation>Vulkan 模式不可用!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="616"/>
+ <source>yuzu has not been compiled with Vulkan support.</source>
+ <translation>yuzu 没有使用 Vulkan 进行编译。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="625"/>
+ <source>Error while initializing OpenGL 4.3!</source>
+ <translation>初始化 OpenGL 4.3 时出错!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="626"/>
+ <source>Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver.</source>
+ <translation>您的 GPU 可能不支持 OpenGL 4.3 ,或者您没有安装最新的显卡驱动。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="634"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>初始化 OpenGL 时出错!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="635"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;</source>
+ <translation>您的 GPU 可能不支持某些必需的 OpenGL 扩展。请确保您已经安装最新的显卡驱动。&lt;br&gt;&lt;br&gt;不支持的扩展:&lt;br&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="307"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>Name</source>
+ <translation>名称</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="308"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Compatibility</source>
+ <translation>兼容性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="311"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="655"/>
+ <source>Add-ons</source>
+ <translation>附加项</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="312"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="315"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="656"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="659"/>
+ <source>File type</source>
+ <translation>文件类型</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="313"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="316"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="657"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="660"/>
+ <source>Size</source>
+ <translation>大小</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="476"/>
+ <source>Open Save Data Location</source>
+ <translation>打开存档位置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="477"/>
+ <source>Open Mod Data Location</source>
+ <translation>打开 MOD 数据位置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="479"/>
+ <source>Open Transferable Shader Cache</source>
+ <translation>打开可转移着色器缓存</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="481"/>
+ <source>Remove</source>
+ <translation>删除</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="482"/>
+ <source>Remove Installed Update</source>
+ <translation>删除已安装的游戏更新</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="483"/>
+ <source>Remove All Installed DLC</source>
+ <translation>删除所有已安装 DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="484"/>
+ <source>Remove Shader Cache</source>
+ <translation>删除着色器缓存</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="485"/>
+ <source>Remove Custom Configuration</source>
+ <translation>删除自定义设置</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="487"/>
+ <source>Remove All Installed Contents</source>
+ <translation>删除所有安装的项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="488"/>
+ <source>Dump RomFS</source>
+ <translation>转储 RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="489"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>复制游戏 ID 到剪贴板</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="490"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>查看兼容性报告</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="492"/>
+ <source>Properties</source>
+ <translation>属性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="542"/>
+ <source>Scan Subfolders</source>
+ <translation>扫描子文件夹</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove Game Directory</source>
+ <translation>移除游戏目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="562"/>
+ <source>▲ Move Up</source>
+ <translation>▲ 向上移动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="563"/>
+ <source>▼ Move Down</source>
+ <translation>▼ 向下移动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="564"/>
+ <source>Open Directory Location</source>
+ <translation>打开目录位置</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Perfect</source>
+ <translation>完美</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="146"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>游戏功能完美,没有音频或图形问题,所有测试功能按预期工作,无需需要任何解决办法。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Great</source>
+ <translation>良好</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="147"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>游戏运行时会有非常轻微的图像或音频问题,但是能从头玩到尾。可能需要一些技巧才能完成游戏。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Okay</source>
+ <translation>一般</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="148"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>游戏运行时会有很多图像或音频错误,但是在使用一些特殊技巧之后能完整地完成游戏。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Bad</source>
+ <translation>较差</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>游戏能运行,但是会有大量图像或音频错误。即使使用一些技巧也无法通过游戏的某些区域。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Intro/Menu</source>
+ <translation>开场 / 菜单</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>游戏完全没法玩,图像或音频有重大错误。通过开场菜单后无法继续。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Won&apos;t Boot</source>
+ <translation>无法打开</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>在启动游戏时直接崩溃了。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Not Tested</source>
+ <translation>未测试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>The game has not yet been tested.</source>
+ <translation>游戏尚未经过测试。</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="722"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>双击以添加新的游戏文件夹</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="120"/>
+ <source>Filter:</source>
+ <translation>搜索:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="123"/>
+ <source>Enter pattern to filter</source>
+ <translation>搜索游戏</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>请确认这些您想要安装的文件。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>安装游戏更新或 DLC 时,会覆盖以前安装的内容。</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+ <source>Install</source>
+ <translation>安装</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+ <source>Install Files to NAND</source>
+ <translation>安装文件到 NAND</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>正在加载着色器: 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>正在加载着色器: %v / %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>所需时间: 5 分 4 秒</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+ <source>Loading...</source>
+ <translation>加载中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>正在加载着色器: %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+ <source>Launching...</source>
+ <translation>启动中...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+ <source>Estimated Time %1</source>
+ <translation>所需时间: %1</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="53"/>
+ <source>&amp;File</source>
+ <translation>文件 (&amp;F)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="57"/>
+ <source>Recent Files</source>
+ <translation>最近文件</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="76"/>
+ <source>&amp;Emulation</source>
+ <translation>模拟 (&amp;E)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="88"/>
+ <source>&amp;View</source>
+ <translation>视图 (&amp;V)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Debugging</source>
+ <translation>调试</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="106"/>
+ <source>Tools</source>
+ <translation>工具</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="114"/>
+ <source>&amp;Help</source>
+ <translation>帮助 (&amp;H)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="134"/>
+ <source>Install Files to NAND...</source>
+ <translation>安装文件到 NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>Load File...</source>
+ <translation>加载文件...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="144"/>
+ <source>Load Folder...</source>
+ <translation>加载文件夹...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="149"/>
+ <source>E&amp;xit</source>
+ <translation>退出 (&amp;X)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="157"/>
+ <source>&amp;Start</source>
+ <translation>开始 (&amp;S)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="165"/>
+ <source>&amp;Pause</source>
+ <translation>暂停 (&amp;P)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="173"/>
+ <source>&amp;Stop</source>
+ <translation>停止 (&amp;S)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="178"/>
+ <source>Reinitialize keys...</source>
+ <translation>重新生成密钥...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="183"/>
+ <source>About yuzu</source>
+ <translation>关于 yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="191"/>
+ <source>Single Window Mode</source>
+ <translation>单窗口模式</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="196"/>
+ <source>Configure...</source>
+ <translation>设置...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="204"/>
+ <source>Display Dock Widget Headers</source>
+ <translation>显示停靠小部件的标题</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="212"/>
+ <source>Show Filter Bar</source>
+ <translation>显示搜索栏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>Show Status Bar</source>
+ <translation>显示状态栏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="225"/>
+ <source>Reset Window Size</source>
+ <translation>重置窗口大小</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Fullscreen</source>
+ <translation>全屏</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Restart</source>
+ <translation>重新启动</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Load Amiibo...</source>
+ <translation>加载 Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Report Compatibility</source>
+ <translation>报告兼容性</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="265"/>
+ <source>Open Mods Page</source>
+ <translation>打开 Mod 页面</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="270"/>
+ <source>Open Quickstart Guide</source>
+ <translation>查看快速导航</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="275"/>
+ <source>FAQ</source>
+ <translation>FAQ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="280"/>
+ <source>Open yuzu Folder</source>
+ <translation>打开 yuzu 文件夹</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="288"/>
+ <source>Capture Screenshot</source>
+ <translation>捕获截图</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="296"/>
+ <source>Configure Current Game..</source>
+ <translation>配置当前游戏...</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+ <source>MicroProfile</source>
+ <translation>MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="238"/>
+ <source>Installed SD Titles</source>
+ <translation>SD 卡中安装的项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="246"/>
+ <source>Installed NAND Titles</source>
+ <translation>NAND 中安装的项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="254"/>
+ <source>System Titles</source>
+ <translation>系统项目</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="296"/>
+ <source>Add New Game Directory</source>
+ <translation>添加游戏目录</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="36"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+ <source>[not set]</source>
+ <translation>[未设置]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="157"/>
+ <source>Hat %1 %2</source>
+ <translation>方向键 %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+ <source>Axis %1%2</source>
+ <translation>轴 %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>Button %1</source>
+ <translation>按键 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="176"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+ <source>[unknown]</source>
+ <translation>[未知]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="22"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>Click 0</source>
+ <translation>点击 0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="24"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Click 1</source>
+ <translation>点击 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="26"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Click 2</source>
+ <translation>点击 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Click 3</source>
+ <translation>点击 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_mouse_advanced.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Click 4</source>
+ <translation>点击 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="143"/>
+ <source>GC Axis %1%2</source>
+ <translation>GC 轴 %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="147"/>
+ <source>GC Button %1</source>
+ <translation>GC 按键 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="210"/>
+ <source>[unused]</source>
+ <translation>[未使用]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <source>Axis %1</source>
+ <translation>轴 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="222"/>
+ <source>GC Axis %1</source>
+ <translation>GC 轴 %1</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="22"/>
+ <source>An error has occured.
+Please try again or contact the developer of the software.
+
+Error Code: %1-%2 (0x%3)</source>
+ <translation>发生了一个错误。
+请再试一次或联系开发者。
+
+错误代码: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="35"/>
+ <source>An error occured on %1 at %2.
+Please try again or contact the developer of the software.
+
+Error Code: %3-%4 (0x%5)</source>
+ <translation>在 %2 处的 %1 上发生了一个错误。
+请再试一次或联系开发者。
+
+错误代码: %3-%4 (0x%5)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/error.cpp" line="49"/>
+ <source>An error has occured.
+Error Code: %1-%2 (0x%3)
+
+%4
+
+%5</source>
+ <translation>发生了一个错误。
+错误代码: %1-%2 (0x%3)
+
+%4
+
+%5</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="22"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="51"/>
+ <source>Select a user:</source>
+ <translation>选择一个用户:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="80"/>
+ <source>Users</source>
+ <translation>用户</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/profile_select.cpp" line="111"/>
+ <source>Profile Selector</source>
+ <translation>选择用户</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="59"/>
+ <source>Enter text:</source>
+ <translation>输入文本:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/software_keyboard.cpp" line="101"/>
+ <source>Software Keyboard</source>
+ <translation>软件键盘</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+ <source>Enter a hotkey</source>
+ <translation>键入热键</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>调用栈</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="127"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation>正在等待互斥锁 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="134"/>
+ <source>has waiters: %1</source>
+ <translation>等待中: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+ <source>owner handle: 0x%1</source>
+ <translation>所有者句柄: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="223"/>
+ <source>waiting for all objects</source>
+ <translation>正在等待所有对象</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="224"/>
+ <source>waiting for one of the following objects</source>
+ <translation>正在等待下列对象中的一个</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1]%2 %3</source>
+ <translation>[%1]%2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="208"/>
+ <source>waited by no thread</source>
+ <translation>没有等待的线程</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="243"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="248"/>
+ <source>running</source>
+ <translation>运行中</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>ready</source>
+ <translation>就绪</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="257"/>
+ <source>paused</source>
+ <translation>暂停</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="260"/>
+ <source>waiting for HLE return</source>
+ <translation>等待 HLE 返回</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="263"/>
+ <source>sleeping</source>
+ <translation>睡眠</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="266"/>
+ <source>waiting for IPC reply</source>
+ <translation>等待 IPC 响应</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="269"/>
+ <source>waiting for objects</source>
+ <translation>等待对象</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="272"/>
+ <source>waiting for mutex</source>
+ <translation>等待互斥锁</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="275"/>
+ <source>waiting for condition variable</source>
+ <translation>等待条件变量</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="278"/>
+ <source>waiting for address arbiter</source>
+ <translation>等待 address arbiter</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>dormant</source>
+ <translation>休眠</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>dead</source>
+ <translation>死亡</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="289"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation>PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation>ideal</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+ <source>core %1</source>
+ <translation>核心 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>Unknown processor %1</source>
+ <translation>未知的处理器 %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>processor = %1</source>
+ <translation>处理器 = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+ <source>ideal core = %1</source>
+ <translation>理想核心 = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>affinity mask = %1</source>
+ <translation>关联掩码 = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="360"/>
+ <source>thread id = %1</source>
+ <translation>线程 ID = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="361"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation>优先级 = %1 (实时) / %2 (正常)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="365"/>
+ <source>last running ticks = %1</source>
+ <translation>最后运行频率 = %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="372"/>
+ <source>not waiting for mutex</source>
+ <translation>未等待互斥锁</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+ <source>waited by thread</source>
+ <translation>等待中的线程</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="466"/>
+ <source>Wait Tree</source>
+ <translation>等待树</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/dist/qt_themes/colorful_dark/icons/index.theme b/dist/qt_themes/colorful_dark/icons/index.theme
index 94d5ae8aa..19dc0369a 100644
--- a/dist/qt_themes/colorful_dark/icons/index.theme
+++ b/dist/qt_themes/colorful_dark/icons/index.theme
@@ -1,7 +1,7 @@
[Icon Theme]
Name=colorful_dark
Comment=Colorful theme (Dark style)
-Inherits=default
+Inherits=colorful
Directories=16x16
[16x16]
diff --git a/dist/qt_themes/colorful_midnight_blue/icons/index.theme b/dist/qt_themes/colorful_midnight_blue/icons/index.theme
index e23bfe6f9..dcb2c50d6 100644
--- a/dist/qt_themes/colorful_midnight_blue/icons/index.theme
+++ b/dist/qt_themes/colorful_midnight_blue/icons/index.theme
@@ -1,7 +1,7 @@
[Icon Theme]
Name=colorful_midnight_blue
Comment=Colorful theme (Midnight Blue style)
-Inherits=default
+Inherits=colorful
Directories=16x16
[16x16]
diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss
index 5da56520b..836dd25ca 100644
--- a/dist/qt_themes/default/style.qss
+++ b/dist/qt_themes/default/style.qss
@@ -1,3 +1,7 @@
+QAbstractSpinBox {
+ min-height: 19px;
+}
+
QPushButton#TogglableStatusBarButton {
color: #959595;
border: 1px solid transparent;
@@ -35,10 +39,103 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
- min-width: 20px;
- min-height: 20px;
- max-width: 20px;
- max-height: 20px;
+ min-width: 21px;
+ min-height: 21px;
+ max-width: 21px;
+ max-height: 21px;
+}
+
+QWidget#bottomPerGameInput,
+QWidget#topControllerApplet,
+QWidget#bottomControllerApplet,
+QGroupBox#groupPlayer1Connected:checked,
+QGroupBox#groupPlayer2Connected:checked,
+QGroupBox#groupPlayer3Connected:checked,
+QGroupBox#groupPlayer4Connected:checked,
+QGroupBox#groupPlayer5Connected:checked,
+QGroupBox#groupPlayer6Connected:checked,
+QGroupBox#groupPlayer7Connected:checked,
+QGroupBox#groupPlayer8Connected:checked {
+ background-color: #f5f5f5;
+}
+
+QWidget#topControllerApplet {
+ border-bottom: 1px solid #828790
+}
+
+QWidget#bottomPerGameInput,
+QWidget#bottomControllerApplet {
+ border-top: 1px solid #828790
+}
+
+QWidget#topPerGameInput,
+QWidget#middleControllerApplet {
+ background-color: #fff;
+}
+
+QWidget#topPerGameInput QComboBox,
+QWidget#middleControllerApplet QComboBox {
+ width: 120px;
+}
+
+QWidget#connectedControllers {
+ background: transparent;
+}
+
+QWidget#playersSupported,
+QWidget#controllersSupported,
+QWidget#controllerSupported1,
+QWidget#controllerSupported2,
+QWidget#controllerSupported3,
+QWidget#controllerSupported4,
+QWidget#controllerSupported5,
+QWidget#controllerSupported6 {
+ border: none;
+ background: transparent;
+}
+
+QGroupBox#groupPlayer1Connected,
+QGroupBox#groupPlayer2Connected,
+QGroupBox#groupPlayer3Connected,
+QGroupBox#groupPlayer4Connected,
+QGroupBox#groupPlayer5Connected,
+QGroupBox#groupPlayer6Connected,
+QGroupBox#groupPlayer7Connected,
+QGroupBox#groupPlayer8Connected {
+ border: 1px solid #828790;
+ border-radius: 3px;
+ padding: 0px;
+ min-height: 98px;
+ max-height: 98px;
+}
+
+QGroupBox#groupPlayer1Connected:unchecked,
+QGroupBox#groupPlayer2Connected:unchecked,
+QGroupBox#groupPlayer3Connected:unchecked,
+QGroupBox#groupPlayer4Connected:unchecked,
+QGroupBox#groupPlayer5Connected:unchecked,
+QGroupBox#groupPlayer6Connected:unchecked,
+QGroupBox#groupPlayer7Connected:unchecked,
+QGroupBox#groupPlayer8Connected:unchecked {
+ border: 1px solid #d9d9d9;
+}
+
+QGroupBox#groupPlayer1Connected::title,
+QGroupBox#groupPlayer2Connected::title,
+QGroupBox#groupPlayer3Connected::title,
+QGroupBox#groupPlayer4Connected::title,
+QGroupBox#groupPlayer5Connected::title,
+QGroupBox#groupPlayer6Connected::title,
+QGroupBox#groupPlayer7Connected::title,
+QGroupBox#groupPlayer8Connected::title {
+ subcontrol-origin: margin;
+ subcontrol-position: top left;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 1px;
+ margin-left: 0px;
+ margin-right: -4px;
+ margin-bottom: 4px;
}
QCheckBox#checkboxPlayer1Connected,
@@ -52,6 +149,42 @@ QCheckBox#checkboxPlayer8Connected {
spacing: 0px;
}
+QWidget#Player1LEDs QCheckBox,
+QWidget#Player2LEDs QCheckBox,
+QWidget#Player3LEDs QCheckBox,
+QWidget#Player4LEDs QCheckBox,
+QWidget#Player5LEDs QCheckBox,
+QWidget#Player6LEDs QCheckBox,
+QWidget#Player7LEDs QCheckBox,
+QWidget#Player8LEDs QCheckBox {
+ spacing: 0px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator,
+QWidget#Player2LEDs QCheckBox::indicator,
+QWidget#Player3LEDs QCheckBox::indicator,
+QWidget#Player4LEDs QCheckBox::indicator,
+QWidget#Player5LEDs QCheckBox::indicator,
+QWidget#Player6LEDs QCheckBox::indicator,
+QWidget#Player7LEDs QCheckBox::indicator,
+QWidget#Player8LEDs QCheckBox::indicator {
+ width: 6px;
+ height: 6px;
+ margin-left: 0px;
+}
+
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 12px;
+ height: 12px;
+}
+
QCheckBox#checkboxPlayer1Connected::indicator,
QCheckBox#checkboxPlayer2Connected::indicator,
QCheckBox#checkboxPlayer3Connected::indicator,
@@ -64,6 +197,34 @@ QCheckBox#checkboxPlayer8Connected::indicator {
height: 14px;
}
+QGroupBox#groupPlayer1Connected::indicator,
+QGroupBox#groupPlayer2Connected::indicator,
+QGroupBox#groupPlayer3Connected::indicator,
+QGroupBox#groupPlayer4Connected::indicator,
+QGroupBox#groupPlayer5Connected::indicator,
+QGroupBox#groupPlayer6Connected::indicator,
+QGroupBox#groupPlayer7Connected::indicator,
+QGroupBox#groupPlayer8Connected::indicator {
+ width: 16px;
+ height: 16px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator:checked,
+QWidget#Player2LEDs QCheckBox::indicator:checked,
+QWidget#Player3LEDs QCheckBox::indicator:checked,
+QWidget#Player4LEDs QCheckBox::indicator:checked,
+QWidget#Player5LEDs QCheckBox::indicator:checked,
+QWidget#Player6LEDs QCheckBox::indicator:checked,
+QWidget#Player7LEDs QCheckBox::indicator:checked,
+QWidget#Player8LEDs QCheckBox::indicator:checked,
+QGroupBox#groupPlayer1Connected::indicator:checked,
+QGroupBox#groupPlayer2Connected::indicator:checked,
+QGroupBox#groupPlayer3Connected::indicator:checked,
+QGroupBox#groupPlayer4Connected::indicator:checked,
+QGroupBox#groupPlayer5Connected::indicator:checked,
+QGroupBox#groupPlayer6Connected::indicator:checked,
+QGroupBox#groupPlayer7Connected::indicator:checked,
+QGroupBox#groupPlayer8Connected::indicator:checked,
QCheckBox#checkboxPlayer1Connected::indicator:checked,
QCheckBox#checkboxPlayer2Connected::indicator:checked,
QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -74,11 +235,27 @@ QCheckBox#checkboxPlayer7Connected::indicator:checked,
QCheckBox#checkboxPlayer8Connected::indicator:checked,
QGroupBox#groupConnectedController::indicator:checked {
border-radius: 2px;
- border: 1px solid black;
+ border: 1px solid #929192;
background: #39ff14;
image: none;
}
+QWidget#Player1LEDs QCheckBox::indicator:unchecked,
+QWidget#Player2LEDs QCheckBox::indicator:unchecked,
+QWidget#Player3LEDs QCheckBox::indicator:unchecked,
+QWidget#Player4LEDs QCheckBox::indicator:unchecked,
+QWidget#Player5LEDs QCheckBox::indicator:unchecked,
+QWidget#Player6LEDs QCheckBox::indicator:unchecked,
+QWidget#Player7LEDs QCheckBox::indicator:unchecked,
+QWidget#Player8LEDs QCheckBox::indicator:unchecked,
+QGroupBox#groupPlayer1Connected::indicator:unchecked,
+QGroupBox#groupPlayer2Connected::indicator:unchecked,
+QGroupBox#groupPlayer3Connected::indicator:unchecked,
+QGroupBox#groupPlayer4Connected::indicator:unchecked,
+QGroupBox#groupPlayer5Connected::indicator:unchecked,
+QGroupBox#groupPlayer6Connected::indicator:unchecked,
+QGroupBox#groupPlayer7Connected::indicator:unchecked,
+QGroupBox#groupPlayer8Connected::indicator:unchecked,
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -89,7 +266,18 @@ QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
QGroupBox#groupConnectedController::indicator:unchecked {
border-radius: 2px;
- border: 1px solid black;
+ border: 1px solid #929192;
background: transparent;
image: none;
}
+
+QWidget#controllerPlayer1,
+QWidget#controllerPlayer2,
+QWidget#controllerPlayer3,
+QWidget#controllerPlayer4,
+QWidget#controllerPlayer5,
+QWidget#controllerPlayer6,
+QWidget#controllerPlayer7,
+QWidget#controllerPlayer8 {
+ background: transparent;
+}
diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc
index ec07ba160..2b91204f3 100644
--- a/dist/qt_themes/qdarkstyle/style.qrc
+++ b/dist/qt_themes/qdarkstyle/style.qrc
@@ -52,6 +52,6 @@
<file>rc/radio_unchecked.png</file>
</qresource>
<qresource prefix="qdarkstyle">
- <file>style.qss</file>
+ <file>style.qss</file>
</qresource>
</RCC>
diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss
index 7755426f8..2a1e8ddeb 100644
--- a/dist/qt_themes/qdarkstyle/style.qss
+++ b/dist/qt_themes/qdarkstyle/style.qss
@@ -99,12 +99,19 @@ QGroupBox::indicator:unchecked:disabled {
}
QRadioButton {
- spacing: 5px;
- outline: none;
color: #eff0f1;
+ spacing: 3px;
+ padding: 0px;
+ border: none;
+ outline: none;
margin-bottom: 2px;
}
+QGroupBox QRadioButton {
+ padding-left: 0px;
+ padding-right: 7px;
+}
+
QRadioButton:disabled {
color: #76797C;
}
@@ -522,13 +529,12 @@ QToolButton#qt_toolbar_ext_button {
QPushButton {
color: #eff0f1;
- border-width: 1px;
- border-color: #54575B;
- border-style: solid;
- padding: 6px 4px;
+ border: 1px solid #54575B;
border-radius: 2px;
+ padding: 5px 0px 5px 0px;
outline: none;
min-width: 100px;
+ min-height: 13px;
background-color: #232629;
}
@@ -553,8 +559,9 @@ QComboBox {
selection-background-color: #3daee9;
border: 1px solid #54575B;
border-radius: 2px;
- padding: 4px 6px;
- min-width: 75px;
+ padding: 0px 4px 0px 4px;
+ min-width: 60px;
+ min-height: 23px;
background-color: #232629;
}
@@ -608,26 +615,26 @@ QComboBox::down-arrow:focus {
}
QAbstractSpinBox {
- padding: 4px 6px;
border: 1px solid #54575B;
background-color: #232629;
color: #eff0f1;
border-radius: 2px;
- min-width: 75px;
+ min-width: 52px;
+ min-height: 23px;
}
QAbstractSpinBox:up-button {
background-color: transparent;
subcontrol-origin: border;
subcontrol-position: center right;
- left: -6px;
+ left: -2px;
}
QAbstractSpinBox:down-button {
background-color: transparent;
subcontrol-origin: border;
subcontrol-position: center left;
- right: -6px;
+ right: -2px;
}
QAbstractSpinBox::up-arrow,
@@ -1277,13 +1284,125 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
- min-width: 24px;
- min-height: 24px;
- max-width: 24px;
- max-height: 24px;
+ min-width: 23px;
+ min-height: 23px;
+ max-width: 23px;
+ max-height: 23px;
padding: 0px 0px;
}
+QSpinBox#spinboxLStickRange,
+QSpinBox#spinboxRStickRange,
+QSpinBox#vibrationSpinPlayer1,
+QSpinBox#vibrationSpinPlayer2,
+QSpinBox#vibrationSpinPlayer3,
+QSpinBox#vibrationSpinPlayer4,
+QSpinBox#vibrationSpinPlayer5,
+QSpinBox#vibrationSpinPlayer6,
+QSpinBox#vibrationSpinPlayer7,
+QSpinBox#vibrationSpinPlayer8 {
+ min-width: 68px;
+}
+
+QDialog#ConfigureVibration QGroupBox::indicator,
+QGroupBox#motionGroup::indicator,
+QGroupBox#vibrationGroup::indicator {
+ margin-left: 0px;
+}
+
+QDialog#ConfigureVibration QGroupBox::title,
+QGroupBox#motionGroup::title,
+QGroupBox#vibrationGroup::title {
+ spacing: 2px;
+ padding-left: 1px;
+ padding-right: 1px;
+}
+
+QWidget#bottomPerGameInput,
+QWidget#topControllerApplet,
+QWidget#bottomControllerApplet,
+QGroupBox#groupPlayer1Connected:checked,
+QGroupBox#groupPlayer2Connected:checked,
+QGroupBox#groupPlayer3Connected:checked,
+QGroupBox#groupPlayer4Connected:checked,
+QGroupBox#groupPlayer5Connected:checked,
+QGroupBox#groupPlayer6Connected:checked,
+QGroupBox#groupPlayer7Connected:checked,
+QGroupBox#groupPlayer8Connected:checked {
+ background-color: #232629;
+}
+
+QWidget#topPerGameInput,
+QWidget#middleControllerApplet {
+ background-color: #31363b;
+}
+
+QWidget#topPerGameInput QComboBox,
+QWidget#middleControllerApplet QComboBox {
+ width: 120px;
+}
+
+QWidget#connectedControllers {
+ background: transparent;
+}
+
+QWidget#playersSupported,
+QWidget#controllersSupported,
+QWidget#controllerSupported1,
+QWidget#controllerSupported2,
+QWidget#controllerSupported3,
+QWidget#controllerSupported4,
+QWidget#controllerSupported5,
+QWidget#controllerSupported6 {
+ border: none;
+ background: transparent;
+}
+
+QGroupBox#groupPlayer1Connected,
+QGroupBox#groupPlayer2Connected,
+QGroupBox#groupPlayer3Connected,
+QGroupBox#groupPlayer4Connected,
+QGroupBox#groupPlayer5Connected,
+QGroupBox#groupPlayer6Connected,
+QGroupBox#groupPlayer7Connected,
+QGroupBox#groupPlayer8Connected {
+ border: 1px solid #76797c;
+ border-radius: 3px;
+ padding: 0px;
+ min-height: 98px;
+ max-height: 98px;
+ margin-top: 0px;
+}
+
+QGroupBox#groupPlayer1Connected:unchecked,
+QGroupBox#groupPlayer2Connected:unchecked,
+QGroupBox#groupPlayer3Connected:unchecked,
+QGroupBox#groupPlayer4Connected:unchecked,
+QGroupBox#groupPlayer5Connected:unchecked,
+QGroupBox#groupPlayer6Connected:unchecked,
+QGroupBox#groupPlayer7Connected:unchecked,
+QGroupBox#groupPlayer8Connected:unchecked {
+ border: 1px solid #54575b;
+}
+
+QGroupBox#groupPlayer1Connected::title,
+QGroupBox#groupPlayer2Connected::title,
+QGroupBox#groupPlayer3Connected::title,
+QGroupBox#groupPlayer4Connected::title,
+QGroupBox#groupPlayer5Connected::title,
+QGroupBox#groupPlayer6Connected::title,
+QGroupBox#groupPlayer7Connected::title,
+QGroupBox#groupPlayer8Connected::title {
+ subcontrol-origin: margin;
+ subcontrol-position: top left;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 1px;
+ margin-left: -2px;
+ margin-right: -4px;
+ margin-bottom: 6px;
+}
+
QCheckBox#checkboxPlayer1Connected,
QCheckBox#checkboxPlayer2Connected,
QCheckBox#checkboxPlayer3Connected,
@@ -1295,6 +1414,55 @@ QCheckBox#checkboxPlayer8Connected {
spacing: 0px;
}
+QWidget#Player1LEDs,
+QWidget#Player2LEDs,
+QWidget#Player3LEDs,
+QWidget#Player4LEDs,
+QWidget#Player5LEDs,
+QWidget#Player6LEDs,
+QWidget#Player7LEDs,
+QWidget#Player8LEDs {
+ background: transparent;
+}
+
+QWidget#Player1LEDs QCheckBox,
+QWidget#Player2LEDs QCheckBox,
+QWidget#Player3LEDs QCheckBox,
+QWidget#Player4LEDs QCheckBox,
+QWidget#Player5LEDs QCheckBox,
+QWidget#Player6LEDs QCheckBox,
+QWidget#Player7LEDs QCheckBox,
+QWidget#Player8LEDs QCheckBox {
+ spacing: 0px;
+ margin-bottom: 0px;
+ margin-right: 0px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator,
+QWidget#Player2LEDs QCheckBox::indicator,
+QWidget#Player3LEDs QCheckBox::indicator,
+QWidget#Player4LEDs QCheckBox::indicator,
+QWidget#Player5LEDs QCheckBox::indicator,
+QWidget#Player6LEDs QCheckBox::indicator,
+QWidget#Player7LEDs QCheckBox::indicator,
+QWidget#Player8LEDs QCheckBox::indicator {
+ width: 6px;
+ height: 6px;
+ margin-left: 0px;
+}
+
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 12px;
+ height: 12px;
+}
+
QCheckBox#checkboxPlayer1Connected::indicator,
QCheckBox#checkboxPlayer2Connected::indicator,
QCheckBox#checkboxPlayer3Connected::indicator,
@@ -1307,6 +1475,34 @@ QCheckBox#checkboxPlayer8Connected::indicator {
height: 14px;
}
+QGroupBox#groupPlayer1Connected::indicator,
+QGroupBox#groupPlayer2Connected::indicator,
+QGroupBox#groupPlayer3Connected::indicator,
+QGroupBox#groupPlayer4Connected::indicator,
+QGroupBox#groupPlayer5Connected::indicator,
+QGroupBox#groupPlayer6Connected::indicator,
+QGroupBox#groupPlayer7Connected::indicator,
+QGroupBox#groupPlayer8Connected::indicator {
+ width: 16px;
+ height: 16px;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator:checked,
+QWidget#Player2LEDs QCheckBox::indicator:checked,
+QWidget#Player3LEDs QCheckBox::indicator:checked,
+QWidget#Player4LEDs QCheckBox::indicator:checked,
+QWidget#Player5LEDs QCheckBox::indicator:checked,
+QWidget#Player6LEDs QCheckBox::indicator:checked,
+QWidget#Player7LEDs QCheckBox::indicator:checked,
+QWidget#Player8LEDs QCheckBox::indicator:checked,
+QGroupBox#groupPlayer1Connected::indicator:checked,
+QGroupBox#groupPlayer2Connected::indicator:checked,
+QGroupBox#groupPlayer3Connected::indicator:checked,
+QGroupBox#groupPlayer4Connected::indicator:checked,
+QGroupBox#groupPlayer5Connected::indicator:checked,
+QGroupBox#groupPlayer6Connected::indicator:checked,
+QGroupBox#groupPlayer7Connected::indicator:checked,
+QGroupBox#groupPlayer8Connected::indicator:checked,
QCheckBox#checkboxPlayer1Connected::indicator:checked,
QCheckBox#checkboxPlayer2Connected::indicator:checked,
QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -1322,6 +1518,22 @@ QGroupBox#groupConnectedController::indicator:checked {
image: none;
}
+QWidget#Player1LEDs QCheckBox::indicator:unchecked,
+QWidget#Player2LEDs QCheckBox::indicator:unchecked,
+QWidget#Player3LEDs QCheckBox::indicator:unchecked,
+QWidget#Player4LEDs QCheckBox::indicator:unchecked,
+QWidget#Player5LEDs QCheckBox::indicator:unchecked,
+QWidget#Player6LEDs QCheckBox::indicator:unchecked,
+QWidget#Player7LEDs QCheckBox::indicator:unchecked,
+QWidget#Player8LEDs QCheckBox::indicator:unchecked,
+QGroupBox#groupPlayer1Connected::indicator:unchecked,
+QGroupBox#groupPlayer2Connected::indicator:unchecked,
+QGroupBox#groupPlayer3Connected::indicator:unchecked,
+QGroupBox#groupPlayer4Connected::indicator:unchecked,
+QGroupBox#groupPlayer5Connected::indicator:unchecked,
+QGroupBox#groupPlayer6Connected::indicator:unchecked,
+QGroupBox#groupPlayer7Connected::indicator:unchecked,
+QGroupBox#groupPlayer8Connected::indicator:unchecked,
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -1337,37 +1549,18 @@ QGroupBox#groupConnectedController::indicator:unchecked {
image: none;
}
-QSpinBox#spinboxLStickRange,
-QSpinBox#spinboxRStickRange {
- padding: 4px 0px 5px 0px;
- min-width: 63px;
-}
-
-QSpinBox#vibrationSpin {
- padding: 4px 0px 5px 0px;
- min-width: 63px;
-}
-
-QSpinBox#spinboxLStickRange:up-button,
-QSpinBox#spinboxRStickRange:up-button,
-QSpinBox#vibrationSpin:up-button {
- left: -2px;
-}
-
-QSpinBox#spinboxLStickRange:down-button,
-QSpinBox#spinboxRStickRange:down-button,
-QSpinBox#vibrationSpin:down-button {
- right: -1px;
-}
-
-QGroupBox#motionGroup::indicator,
-QGroupBox#vibrationGroup::indicator {
- margin-left: 0px;
+QWidget#controllerPlayer1,
+QWidget#controllerPlayer2,
+QWidget#controllerPlayer3,
+QWidget#controllerPlayer4,
+QWidget#controllerPlayer5,
+QWidget#controllerPlayer6,
+QWidget#controllerPlayer7,
+QWidget#controllerPlayer8 {
+ background: transparent;
}
-QGroupBox#motionGroup::title,
-QGroupBox#vibrationGroup::title {
- spacing: 2px;
- padding-left: 1px;
- padding-right: 1px;
+/* touchscreen mapping widget */
+TouchScreenPreview {
+ qproperty-dotHighlightColor: #3daee9;
}
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
index 616aace73..579e73ece 100644
--- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
@@ -221,6 +221,6 @@
<file>rc/window_undock_pressed@2x.png</file>
</qresource>
<qresource prefix="qdarkstyle_midnight_blue">
- <file>style.qss</file>
+ <file>style.qss</file>
</qresource>
</RCC>
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
index a714e1475..a64037455 100644
--- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
@@ -172,8 +172,8 @@ QCheckBox {
color: #F0F0F0;
spacing: 4px;
outline: none;
- padding-top: 4px;
- padding-bottom: 4px;
+ padding-top: 2px;
+ padding-bottom: 2px;
}
QCheckBox:focus {
@@ -235,19 +235,19 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qgroupbox
--------------------------------------------------------------------------- */
QGroupBox {
- font-weight: bold;
- border: 1px solid #32414B;
- border-radius: 4px;
- margin-top: 12px;
- padding: 4px;
+ font-weight: bold;
+ border: 1px solid #32414B;
+ border-radius: 4px;
+ margin-top: 12px;
+ padding: 2px;
}
QGroupBox::title {
- subcontrol-origin: margin;
- subcontrol-position: top left;
- padding-left: 3px;
- padding-right: 5px;
- padding-top: 4px;
+ subcontrol-origin: margin;
+ subcontrol-position: top left;
+ padding-left: 3px;
+ padding-right: 5px;
+ padding-top: 2px;
}
QGroupBox::indicator {
@@ -298,6 +298,11 @@ QRadioButton {
outline: none;
}
+QGroupBox QRadioButton {
+ padding-left: 0px;
+ padding-right: 7px;
+}
+
QRadioButton:focus {
border: none;
}
@@ -321,7 +326,6 @@ QRadioButton QWidget {
QRadioButton::indicator {
border: none;
outline: none;
- margin-left: 4px;
height: 16px;
width: 16px;
}
@@ -785,14 +789,8 @@ QAbstractSpinBox {
background-color: #19232D;
border: 1px solid #32414B;
color: #F0F0F0;
- /* This fixes 103, 111 */
- padding-top: 2px;
- /* This fixes 103, 111 */
- padding-bottom: 2px;
- padding-left: 4px;
- padding-right: 4px;
border-radius: 4px;
- /* min-width: 5px; removed to fix 109 */
+ min-height: 19px;
}
QAbstractSpinBox:up-button {
@@ -997,10 +995,11 @@ QPushButton {
border: 1px solid #32414B;
color: #F0F0F0;
border-radius: 4px;
- padding: 3px;
+ padding: 3px 0px 3px 0px;
outline: none;
/* Issue #194 - Special case of QPushButton inside dialogs, for better UI */
min-width: 80px;
+ min-height: 13px;
}
QPushButton:disabled {
@@ -1008,14 +1007,14 @@ QPushButton:disabled {
border: 1px solid #32414B;
color: #787878;
border-radius: 4px;
- padding: 3px;
+ padding: 3px 0px 3px 0px;
}
QPushButton:checked {
background-color: #32414B;
border: 1px solid #32414B;
border-radius: 4px;
- padding: 3px;
+ padding: 3px 0px 3px 0px;
outline: none;
}
@@ -1024,7 +1023,7 @@ QPushButton:checked:disabled {
border: 1px solid #32414B;
color: #787878;
border-radius: 4px;
- padding: 3px;
+ padding: 3px 0px 3px 0px;
outline: none;
}
@@ -1197,15 +1196,9 @@ QComboBox {
border: 1px solid #32414B;
border-radius: 4px;
selection-background-color: #1464A0;
- padding-left: 4px;
- padding-right: 36px;
- /* 4 + 16*2 See scrollbar size */
- /* Fixes #103, #111 */
- min-height: 1.5em;
- /* padding-top: 2px; removed to fix #132 */
- /* padding-bottom: 2px; removed to fix #132 */
- /* min-width: 75px; removed to fix #109 */
- /* Needed to remove indicator - fix #132 */
+ padding: 0px 4px 0px 4px;
+ min-width: 60px;
+ min-height: 19px;
}
QComboBox QAbstractItemView {
@@ -1264,10 +1257,6 @@ QComboBox::item:alternate {
background: #19232D;
}
-QComboBox::item:checked {
- font-weight: bold;
-}
-
QComboBox::item:selected {
border: 0px solid transparent;
}
@@ -2198,13 +2187,143 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
- min-width: 20px;
- min-height: 20px;
- max-width: 20px;
- max-height: 20px;
+ min-width: 19px;
+ min-height: 19px;
+ max-width: 19px;
+ max-height: 19px;
padding: 0px 0px;
}
+QSpinBox#spinboxLStickRange,
+QSpinBox#spinboxRStickRange,
+QSpinBox#vibrationSpinPlayer1,
+QSpinBox#vibrationSpinPlayer2,
+QSpinBox#vibrationSpinPlayer3,
+QSpinBox#vibrationSpinPlayer4,
+QSpinBox#vibrationSpinPlayer5,
+QSpinBox#vibrationSpinPlayer6,
+QSpinBox#vibrationSpinPlayer7,
+QSpinBox#vibrationSpinPlayer8 {
+ min-width: 68px;
+}
+
+QDialog#ConfigureVibration QGroupBox::indicator,
+QGroupBox#motionGroup::indicator,
+QGroupBox#vibrationGroup::indicator {
+ margin-left: 0px;
+}
+
+QDialog#ConfigureVibration QGroupBox,
+QWidget#bottomPerGameInput QGroupBox#motionGroup,
+QWidget#bottomPerGameInput QGroupBox#vibrationGroup,
+QWidget#bottomPerGameInput QGroupBox#inputConfigGroup {
+ padding: 0px;
+}
+
+QDialog#ConfigureVibration QGroupBox::title,
+QGroupBox#motionGroup::title,
+QGroupBox#vibrationGroup::title {
+ spacing: 2px;
+ padding-left: 1px;
+ padding-right: 1px;
+}
+
+QListWidget#selectorList {
+ background-color: #0f1922;
+}
+
+QSpinBox,
+QLineEdit,
+QTreeView#hotkey_list,
+QScrollArea#scrollArea QTreeView {
+ background-color: #0f1922;
+}
+
+QWidget#bottomPerGameInput,
+QWidget#topControllerApplet,
+QWidget#bottomControllerApplet,
+QGroupBox#groupPlayer1Connected:checked,
+QGroupBox#groupPlayer2Connected:checked,
+QGroupBox#groupPlayer3Connected:checked,
+QGroupBox#groupPlayer4Connected:checked,
+QGroupBox#groupPlayer5Connected:checked,
+QGroupBox#groupPlayer6Connected:checked,
+QGroupBox#groupPlayer7Connected:checked,
+QGroupBox#groupPlayer8Connected:checked {
+ background-color: #0f1922;
+}
+
+QWidget#topPerGameInput,
+QWidget#middleControllerApplet {
+ background-color: #19232d;
+}
+
+QWidget#topPerGameInput QComboBox,
+QWidget#middleControllerApplet QComboBox {
+ width: 120px;
+}
+
+QWidget#connectedControllers {
+ background: transparent;
+}
+
+QWidget#playersSupported,
+QWidget#controllersSupported,
+QWidget#controllerSupported1,
+QWidget#controllerSupported2,
+QWidget#controllerSupported3,
+QWidget#controllerSupported4,
+QWidget#controllerSupported5,
+QWidget#controllerSupported6 {
+ border: none;
+ background: transparent;
+}
+
+QGroupBox#groupPlayer1Connected,
+QGroupBox#groupPlayer2Connected,
+QGroupBox#groupPlayer3Connected,
+QGroupBox#groupPlayer4Connected,
+QGroupBox#groupPlayer5Connected,
+QGroupBox#groupPlayer6Connected,
+QGroupBox#groupPlayer7Connected,
+QGroupBox#groupPlayer8Connected {
+ border: 1px solid #76797c;
+ border-radius: 3px;
+ padding: 0px;
+ min-height: 98px;
+ max-height: 98px;
+ margin-top: 0px;
+}
+
+
+QGroupBox#groupPlayer1Connected:unchecked,
+QGroupBox#groupPlayer2Connected:unchecked,
+QGroupBox#groupPlayer3Connected:unchecked,
+QGroupBox#groupPlayer4Connected:unchecked,
+QGroupBox#groupPlayer5Connected:unchecked,
+QGroupBox#groupPlayer6Connected:unchecked,
+QGroupBox#groupPlayer7Connected:unchecked,
+QGroupBox#groupPlayer8Connected:unchecked {
+ border: 1px solid #32414b;
+}
+
+QGroupBox#groupPlayer1Connected::title,
+QGroupBox#groupPlayer2Connected::title,
+QGroupBox#groupPlayer3Connected::title,
+QGroupBox#groupPlayer4Connected::title,
+QGroupBox#groupPlayer5Connected::title,
+QGroupBox#groupPlayer6Connected::title,
+QGroupBox#groupPlayer7Connected::title,
+QGroupBox#groupPlayer8Connected::title {
+ subcontrol-origin: margin;
+ subcontrol-position: top left;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 1px;
+ margin-left: -2px;
+ margin-right: -4px;
+ margin-bottom: 6px;
+}
QCheckBox#checkboxPlayer1Connected,
QCheckBox#checkboxPlayer2Connected,
@@ -2217,6 +2336,68 @@ QCheckBox#checkboxPlayer8Connected {
spacing: 0px;
}
+QWidget#connectedControllers QLabel {
+ padding: 0px;
+}
+
+QWidget#Player1LEDs,
+QWidget#Player2LEDs,
+QWidget#Player3LEDs,
+QWidget#Player4LEDs,
+QWidget#Player5LEDs,
+QWidget#Player6LEDs,
+QWidget#Player7LEDs,
+QWidget#Player8LEDs {
+ background: transparent;
+}
+
+QWidget#Player1LEDs QCheckBox,
+QWidget#Player2LEDs QCheckBox,
+QWidget#Player3LEDs QCheckBox,
+QWidget#Player4LEDs QCheckBox,
+QWidget#Player5LEDs QCheckBox,
+QWidget#Player6LEDs QCheckBox,
+QWidget#Player7LEDs QCheckBox,
+QWidget#Player8LEDs QCheckBox,
+QCheckBox#checkboxPlayer1Connected,
+QCheckBox#checkboxPlayer2Connected,
+QCheckBox#checkboxPlayer3Connected,
+QCheckBox#checkboxPlayer4Connected,
+QCheckBox#checkboxPlayer5Connected,
+QCheckBox#checkboxPlayer6Connected,
+QCheckBox#checkboxPlayer7Connected,
+QCheckBox#checkboxPlayer8Connected {
+ spacing: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ background: transparent;
+}
+
+QWidget#Player1LEDs QCheckBox::indicator,
+QWidget#Player2LEDs QCheckBox::indicator,
+QWidget#Player3LEDs QCheckBox::indicator,
+QWidget#Player4LEDs QCheckBox::indicator,
+QWidget#Player5LEDs QCheckBox::indicator,
+QWidget#Player6LEDs QCheckBox::indicator,
+QWidget#Player7LEDs QCheckBox::indicator,
+QWidget#Player8LEDs QCheckBox::indicator {
+ width: 6px;
+ height: 6px;
+ margin-left: 0px;
+}
+
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
+QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
+ width: 12px;
+ height: 12px;
+}
+
QCheckBox#checkboxPlayer1Connected::indicator,
QCheckBox#checkboxPlayer2Connected::indicator,
QCheckBox#checkboxPlayer3Connected::indicator,
@@ -2227,8 +2408,25 @@ QCheckBox#checkboxPlayer7Connected::indicator,
QCheckBox#checkboxPlayer8Connected::indicator {
width: 14px;
height: 14px;
+ margin-left: 0px;
}
+QWidget#Player1LEDs QCheckBox::indicator:checked,
+QWidget#Player2LEDs QCheckBox::indicator:checked,
+QWidget#Player3LEDs QCheckBox::indicator:checked,
+QWidget#Player4LEDs QCheckBox::indicator:checked,
+QWidget#Player5LEDs QCheckBox::indicator:checked,
+QWidget#Player6LEDs QCheckBox::indicator:checked,
+QWidget#Player7LEDs QCheckBox::indicator:checked,
+QWidget#Player8LEDs QCheckBox::indicator:checked,
+QGroupBox#groupPlayer1Connected::indicator:checked,
+QGroupBox#groupPlayer2Connected::indicator:checked,
+QGroupBox#groupPlayer3Connected::indicator:checked,
+QGroupBox#groupPlayer4Connected::indicator:checked,
+QGroupBox#groupPlayer5Connected::indicator:checked,
+QGroupBox#groupPlayer6Connected::indicator:checked,
+QGroupBox#groupPlayer7Connected::indicator:checked,
+QGroupBox#groupPlayer8Connected::indicator:checked,
QCheckBox#checkboxPlayer1Connected::indicator:checked,
QCheckBox#checkboxPlayer2Connected::indicator:checked,
QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -2244,6 +2442,22 @@ QGroupBox#groupConnectedController::indicator:checked {
image: none;
}
+QWidget#Player1LEDs QCheckBox::indicator:unchecked,
+QWidget#Player2LEDs QCheckBox::indicator:unchecked,
+QWidget#Player3LEDs QCheckBox::indicator:unchecked,
+QWidget#Player4LEDs QCheckBox::indicator:unchecked,
+QWidget#Player5LEDs QCheckBox::indicator:unchecked,
+QWidget#Player6LEDs QCheckBox::indicator:unchecked,
+QWidget#Player7LEDs QCheckBox::indicator:unchecked,
+QWidget#Player8LEDs QCheckBox::indicator:unchecked,
+QGroupBox#groupPlayer1Connected::indicator:unchecked,
+QGroupBox#groupPlayer2Connected::indicator:unchecked,
+QGroupBox#groupPlayer3Connected::indicator:unchecked,
+QGroupBox#groupPlayer4Connected::indicator:unchecked,
+QGroupBox#groupPlayer5Connected::indicator:unchecked,
+QGroupBox#groupPlayer6Connected::indicator:unchecked,
+QGroupBox#groupPlayer7Connected::indicator:unchecked,
+QGroupBox#groupPlayer8Connected::indicator:unchecked,
QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -2255,34 +2469,17 @@ QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
QGroupBox#groupConnectedController::indicator:unchecked {
border-radius: 2px;
border: 1px solid #929192;
- background: transparent;
+ background: #19232d;
image: none;
}
-QSpinBox#spinboxLStickRange,
-QSpinBox#spinboxRStickRange {
- min-width: 38px;
-}
-
-QGroupBox#motionGroup::indicator,
-QGroupBox#vibrationGroup::indicator {
- margin-left: 0px;
-}
-
-QGroupBox#motionGroup::title,
-QGroupBox#vibrationGroup::title {
-spacing: 2px;
- padding-left: 1px;
- padding-right: 1px;
-}
-
-QListWidget#selectorList {
- background-color: #0f1922;
+QWidget#controllerPlayer1,
+QWidget#controllerPlayer2,
+QWidget#controllerPlayer3,
+QWidget#controllerPlayer4,
+QWidget#controllerPlayer5,
+QWidget#controllerPlayer6,
+QWidget#controllerPlayer7,
+QWidget#controllerPlayer8 {
+ background: transparent;
}
-
-QSpinBox,
-QLineEdit,
-QTreeView#hotkey_list,
-QScrollArea#scrollArea QTreeView {
- background-color: #0f1922;
-} \ No newline at end of file
diff --git a/dist/yuzu.bmp b/dist/yuzu.bmp
new file mode 100644
index 000000000..66f2f696f
--- /dev/null
+++ b/dist/yuzu.bmp
Binary files differ
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index d1dcc403b..c629bbc5c 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -61,9 +61,7 @@ if (USE_DISCORD_PRESENCE)
endif()
# Sirit
-if (ENABLE_VULKAN)
- add_subdirectory(sirit)
-endif()
+add_subdirectory(sirit)
# libzip
find_package(Libzip 1.5)
@@ -73,23 +71,29 @@ if (NOT LIBZIP_FOUND)
endif()
if (ENABLE_WEB_SERVICE)
- # LibreSSL
- set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
- add_subdirectory(libressl EXCLUDE_FROM_ALL)
- target_include_directories(ssl INTERFACE ./libressl/include)
- target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
- get_directory_property(OPENSSL_LIBRARIES
- DIRECTORY libressl
- DEFINITION OPENSSL_LIBS)
-
- # lurlparser
- add_subdirectory(lurlparser EXCLUDE_FROM_ALL)
+ find_package(OpenSSL 1.1)
+ if (OPENSSL_FOUND)
+ set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
+ else()
+ # LibreSSL
+ set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
+ set(OPENSSLDIR "/etc/ssl/")
+ add_subdirectory(libressl EXCLUDE_FROM_ALL)
+ target_include_directories(ssl INTERFACE ./libressl/include)
+ target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
+ get_directory_property(OPENSSL_LIBRARIES
+ DIRECTORY libressl
+ DEFINITION OPENSSL_LIBS)
+ endif()
# httplib
add_library(httplib INTERFACE)
target_include_directories(httplib INTERFACE ./httplib)
target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
+ if (WIN32)
+ target_link_libraries(httplib INTERFACE crypt32 cryptui ws2_32)
+ endif()
endif()
# Opus
diff --git a/externals/cubeb b/externals/cubeb
-Subproject 616d773441b5355800ce64197a699e6cd6b3617
+Subproject 1d66483ad2b93f0e00e175f9480c771af90003a
diff --git a/externals/dynarmic b/externals/dynarmic
-Subproject 0e1112b7df77ae55a62a51622940d5c8f9e8c84
+Subproject 8c09da666aa3f0bb1000b0b6c5d5b0a1876f306
diff --git a/externals/ffmpeg b/externals/ffmpeg
new file mode 160000
+Subproject 6b6b9e593dd4d3aaf75f48d40a13ef03bdef9fd
diff --git a/externals/find-modules/FindFFmpeg.cmake b/externals/find-modules/FindFFmpeg.cmake
new file mode 100644
index 000000000..61b6dc8d2
--- /dev/null
+++ b/externals/find-modules/FindFFmpeg.cmake
@@ -0,0 +1,187 @@
+# FindFFmpeg
+# ----------
+#
+# Copyright 2019 Citra Emulator Project
+# Licensed under GPLv2 or any later version
+#
+# Find the native FFmpeg includes and libraries
+#
+# This module defines the following variables:
+#
+# FFmpeg_INCLUDE_<component>: where to find <component>.h
+# FFmpeg_LIBRARY_<component>: where to find the <component> library
+# FFmpeg_INCLUDE_DIR: aggregate all the include paths
+# FFmpeg_LIBRARIES: aggregate all the paths to the libraries
+# FFmpeg_FOUND: True if all components have been found
+#
+# This module defines the following targets, which are prefered over variables:
+#
+# FFmpeg::<component>: Target to use <component> directly, with include path,
+# library and dependencies set up. If you are using a static build, you are
+# responsible for adding any external dependencies (such as zlib, bzlib...).
+#
+# <component> can be one of:
+# avcodec
+# avdevice
+# avfilter
+# avformat
+# avutil
+# postproc
+# swresample
+# swscale
+#
+
+set(_FFmpeg_ALL_COMPONENTS
+ avcodec
+ avdevice
+ avfilter
+ avformat
+ avutil
+ postproc
+ swresample
+ swscale
+)
+
+set(_FFmpeg_DEPS_avcodec avutil)
+set(_FFmpeg_DEPS_avdevice avcodec avformat avutil)
+set(_FFmpeg_DEPS_avfilter avutil)
+set(_FFmpeg_DEPS_avformat avcodec avutil)
+set(_FFmpeg_DEPS_postproc avutil)
+set(_FFmpeg_DEPS_swresample avutil)
+set(_FFmpeg_DEPS_swscale avutil)
+
+function(find_ffmpeg LIBNAME)
+ if(DEFINED ENV{FFMPEG_DIR})
+ set(FFMPEG_DIR $ENV{FFMPEG_DIR})
+ endif()
+
+ if(FFMPEG_DIR)
+ list(APPEND INCLUDE_PATHS
+ ${FFMPEG_DIR}
+ ${FFMPEG_DIR}/ffmpeg
+ ${FFMPEG_DIR}/lib${LIBNAME}
+ ${FFMPEG_DIR}/include/lib${LIBNAME}
+ ${FFMPEG_DIR}/include/ffmpeg
+ ${FFMPEG_DIR}/include
+ NO_DEFAULT_PATH
+ NO_CMAKE_FIND_ROOT_PATH
+ )
+ list(APPEND LIB_PATHS
+ ${FFMPEG_DIR}
+ ${FFMPEG_DIR}/lib
+ ${FFMPEG_DIR}/lib${LIBNAME}
+ NO_DEFAULT_PATH
+ NO_CMAKE_FIND_ROOT_PATH
+ )
+ else()
+ list(APPEND INCLUDE_PATHS
+ /usr/local/include/ffmpeg
+ /usr/local/include/lib${LIBNAME}
+ /usr/include/ffmpeg
+ /usr/include/lib${LIBNAME}
+ /usr/include/ffmpeg/lib${LIBNAME}
+ )
+
+ list(APPEND LIB_PATHS
+ /usr/local/lib
+ /usr/lib
+ )
+ endif()
+
+ find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h
+ HINTS ${INCLUDE_PATHS}
+ )
+
+ find_library(FFmpeg_LIBRARY_${LIBNAME} ${LIBNAME}
+ HINTS ${LIB_PATHS}
+ )
+
+ if(NOT FFMPEG_DIR AND (NOT FFmpeg_LIBRARY_${LIBNAME} OR NOT FFmpeg_INCLUDE_${LIBNAME}))
+ # Didn't find it in the usual paths, try pkg-config
+ find_package(PkgConfig QUIET)
+ pkg_check_modules(FFmpeg_PKGCONFIG_${LIBNAME} QUIET lib${LIBNAME})
+
+ find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h
+ ${FFmpeg_PKGCONFIG_${LIBNAME}_INCLUDE_DIRS}
+ )
+
+ find_library(FFmpeg_LIBRARY_${LIBNAME} ${LIBNAME}
+ ${FFmpeg_PKGCONFIG_${LIBNAME}_LIBRARY_DIRS}
+ )
+ endif()
+
+ if(FFmpeg_INCLUDE_${LIBNAME} AND FFmpeg_LIBRARY_${LIBNAME})
+ set(FFmpeg_INCLUDE_${LIBNAME} "${FFmpeg_INCLUDE_${LIBNAME}}" PARENT_SCOPE)
+ set(FFmpeg_LIBRARY_${LIBNAME} "${FFmpeg_LIBRARY_${LIBNAME}}" PARENT_SCOPE)
+
+ # Extract FFmpeg version from version.h
+ foreach(v MAJOR MINOR MICRO)
+ set(FFmpeg_${LIBNAME}_VERSION_${v} 0)
+ endforeach()
+ string(TOUPPER ${LIBNAME} LIBNAME_UPPER)
+ file(STRINGS "${FFmpeg_INCLUDE_${LIBNAME}}/lib${LIBNAME}/version.h" _FFmpeg_VERSION_H_CONTENTS REGEX "#define LIB${LIBNAME_UPPER}_VERSION_(MAJOR|MINOR|MICRO) ")
+ set(_FFmpeg_VERSION_REGEX "([0-9]+)")
+ foreach(v MAJOR MINOR MICRO)
+ if("${_FFmpeg_VERSION_H_CONTENTS}" MATCHES "#define LIB${LIBNAME_UPPER}_VERSION_${v}[\\t ]+${_FFmpeg_VERSION_REGEX}")
+ set(FFmpeg_${LIBNAME}_VERSION_${v} "${CMAKE_MATCH_1}")
+ endif()
+ endforeach()
+ set(FFmpeg_${LIBNAME}_VERSION "${FFmpeg_${LIBNAME}_VERSION_MAJOR}.${FFmpeg_${LIBNAME}_VERSION_MINOR}.${FFmpeg_${LIBNAME}_VERSION_MICRO}")
+ set(FFmpeg_${c}_VERSION "${FFmpeg_${LIBNAME}_VERSION}" PARENT_SCOPE)
+ unset(_FFmpeg_VERSION_REGEX)
+ unset(_FFmpeg_VERSION_H_CONTENTS)
+
+ set(FFmpeg_${c}_FOUND TRUE PARENT_SCOPE)
+ if(NOT FFmpeg_FIND_QUIETLY)
+ message("-- Found ${LIBNAME}: ${FFmpeg_INCLUDE_${LIBNAME}} ${FFmpeg_LIBRARY_${LIBNAME}} (version: ${FFmpeg_${LIBNAME}_VERSION})")
+ endif()
+ endif()
+endfunction()
+
+foreach(c ${_FFmpeg_ALL_COMPONENTS})
+ find_ffmpeg(${c})
+endforeach()
+
+foreach(c ${_FFmpeg_ALL_COMPONENTS})
+ if(FFmpeg_${c}_FOUND)
+ list(APPEND FFmpeg_INCLUDE_DIR ${FFmpeg_INCLUDE_${c}})
+ list(APPEND FFmpeg_LIBRARIES ${FFmpeg_LIBRARY_${c}})
+
+ add_library(FFmpeg::${c} IMPORTED UNKNOWN)
+ set_target_properties(FFmpeg::${c} PROPERTIES
+ IMPORTED_LOCATION ${FFmpeg_LIBRARY_${c}}
+ INTERFACE_INCLUDE_DIRECTORIES ${FFmpeg_INCLUDE_${c}}
+ )
+ if(_FFmpeg_DEPS_${c})
+ set(deps)
+ foreach(dep ${_FFmpeg_DEPS_${c}})
+ list(APPEND deps FFmpeg::${dep})
+ endforeach()
+
+ set_target_properties(FFmpeg::${c} PROPERTIES
+ INTERFACE_LINK_LIBRARIES "${deps}"
+ )
+ unset(deps)
+ endif()
+ endif()
+endforeach()
+
+if(FFmpeg_INCLUDE_DIR)
+ list(REMOVE_DUPLICATES FFmpeg_INCLUDE_DIR)
+endif()
+
+foreach(c ${FFmpeg_FIND_COMPONENTS})
+ list(APPEND _FFmpeg_REQUIRED_VARS FFmpeg_INCLUDE_${c} FFmpeg_LIBRARY_${c})
+endforeach()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FFmpeg
+ REQUIRED_VARS ${_FFmpeg_REQUIRED_VARS}
+ HANDLE_COMPONENTS
+)
+
+foreach(c ${_FFmpeg_ALL_COMPONENTS})
+ unset(_FFmpeg_DEPS_${c})
+endforeach()
+unset(_FFmpeg_ALL_COMPONENTS)
+unset(_FFmpeg_REQUIRED_VARS)
diff --git a/externals/httplib/README.md b/externals/httplib/README.md
index 73037d297..1940e446c 100644
--- a/externals/httplib/README.md
+++ b/externals/httplib/README.md
@@ -1,4 +1,4 @@
-From https://github.com/yhirose/cpp-httplib/tree/fce8e6fefdab4ad48bc5b25c98e5ebfda4f3cf53
+From https://github.com/yhirose/cpp-httplib/tree/ff5677ad197947177c158fe857caff4f0e242045 with https://github.com/yhirose/cpp-httplib/pull/701
MIT License
diff --git a/externals/httplib/httplib.h b/externals/httplib/httplib.h
index e03842e6d..8982054e2 100644
--- a/externals/httplib/httplib.h
+++ b/externals/httplib/httplib.h
@@ -16,14 +16,18 @@
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
#endif
-#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND
-#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0
-#endif
-
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
#endif
+#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
+#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
+#endif
+
+#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
+#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
+#endif
+
#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
#endif
@@ -32,6 +36,26 @@
#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
#endif
+#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
+#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
+#endif
+
+#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
+#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
+#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
+#endif
+
+#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
+#ifdef _WIN32
+#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000
+#else
+#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
+#endif
+#endif
+
#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
#endif
@@ -41,16 +65,26 @@
#endif
#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
-#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max())
+#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
+#endif
+
+#ifndef CPPHTTPLIB_TCP_NODELAY
+#define CPPHTTPLIB_TCP_NODELAY false
#endif
#ifndef CPPHTTPLIB_RECV_BUFSIZ
#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
#endif
+#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
+#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
+#endif
+
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
#define CPPHTTPLIB_THREAD_POOL_COUNT \
- (std::max(1u, std::thread::hardware_concurrency() - 1))
+ ((std::max)(8u, std::thread::hardware_concurrency() > 0 \
+ ? std::thread::hardware_concurrency() - 1 \
+ : 0))
#endif
/*
@@ -92,6 +126,8 @@ using ssize_t = int;
#include <io.h>
#include <winsock2.h>
+
+#include <wincrypt.h>
#include <ws2tcpip.h>
#ifndef WSA_FLAG_NO_HANDLE_INHERIT
@@ -100,6 +136,8 @@ using ssize_t = int;
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
+#pragma comment(lib, "crypt32.lib")
+#pragma comment(lib, "cryptui.lib")
#endif
#ifndef strcasecmp
@@ -118,6 +156,10 @@ using socket_t = SOCKET;
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
+#ifdef __linux__
+#include <resolv.h>
+#endif
+#include <netinet/tcp.h>
#ifdef CPPHTTPLIB_USE_POLL
#include <poll.h>
#endif
@@ -131,20 +173,25 @@ using socket_t = int;
#define INVALID_SOCKET (-1)
#endif //_WIN32
+#include <algorithm>
#include <array>
#include <atomic>
#include <cassert>
+#include <cctype>
+#include <climits>
#include <condition_variable>
#include <errno.h>
#include <fcntl.h>
#include <fstream>
#include <functional>
+#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <random>
#include <regex>
+#include <sstream>
#include <string>
#include <sys/stat.h>
#include <thread>
@@ -155,12 +202,17 @@ using socket_t = int;
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
+#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
+#include <openssl/applink.c>
+#endif
+
#include <iomanip>
+#include <iostream>
#include <sstream>
-// #if OPENSSL_VERSION_NUMBER < 0x1010100fL
-// #error Sorry, OpenSSL versions prior to 1.1.1 are not supported
-// #endif
+#if OPENSSL_VERSION_NUMBER < 0x1010100fL
+#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
+#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#include <openssl/crypto.h>
@@ -174,6 +226,11 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
#include <zlib.h>
#endif
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+#include <brotli/decode.h>
+#include <brotli/encode.h>
+#endif
+
/*
* Declaration
*/
@@ -181,6 +238,27 @@ namespace httplib {
namespace detail {
+/*
+ * Backport std::make_unique from C++14.
+ *
+ * NOTE: This code came up with the following stackoverflow post:
+ * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
+ *
+ */
+
+template <class T, class... Args>
+typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(Args &&... args) {
+ return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+template <class T>
+typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(std::size_t n) {
+ typedef typename std::remove_extent<T>::type RT;
+ return std::unique_ptr<T>(new RT[n]);
+}
+
struct ci {
bool operator()(const std::string &s1, const std::string &s2) const {
return std::lexicographical_compare(
@@ -212,7 +290,8 @@ using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
class DataSink {
public:
- DataSink() = default;
+ DataSink() : os(&sb_), sb_(*this) {}
+
DataSink(const DataSink &) = delete;
DataSink &operator=(const DataSink &) = delete;
DataSink(DataSink &&) = delete;
@@ -221,10 +300,35 @@ public:
std::function<void(const char *data, size_t data_len)> write;
std::function<void()> done;
std::function<bool()> is_writable;
+ std::ostream os;
+
+private:
+ class data_sink_streambuf : public std::streambuf {
+ public:
+ explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
+
+ protected:
+ std::streamsize xsputn(const char *s, std::streamsize n) {
+ sink_.write(s, static_cast<size_t>(n));
+ return n;
+ }
+
+ private:
+ DataSink &sink_;
+ };
+
+ data_sink_streambuf sb_;
};
using ContentProvider =
- std::function<void(size_t offset, size_t length, DataSink &sink)>;
+ std::function<bool(size_t offset, size_t length, DataSink &sink)>;
+
+using ContentProviderWithoutLength =
+ std::function<bool(size_t offset, DataSink &sink)>;
+
+using ContentReceiverWithProgress =
+ std::function<bool(const char *data, size_t data_length, uint64_t offset,
+ uint64_t total_length)>;
using ContentReceiver =
std::function<bool(const char *data, size_t data_length)>;
@@ -238,18 +342,21 @@ public:
using MultipartReader = std::function<bool(MultipartContentHeader header,
ContentReceiver receiver)>;
- ContentReader(Reader reader, MultipartReader muitlpart_reader)
- : reader_(reader), muitlpart_reader_(muitlpart_reader) {}
+ ContentReader(Reader reader, MultipartReader multipart_reader)
+ : reader_(std::move(reader)),
+ multipart_reader_(std::move(multipart_reader)) {}
bool operator()(MultipartContentHeader header,
ContentReceiver receiver) const {
- return muitlpart_reader_(header, receiver);
+ return multipart_reader_(std::move(header), std::move(receiver));
}
- bool operator()(ContentReceiver receiver) const { return reader_(receiver); }
+ bool operator()(ContentReceiver receiver) const {
+ return reader_(std::move(receiver));
+ }
Reader reader_;
- MultipartReader muitlpart_reader_;
+ MultipartReader multipart_reader_;
};
using Range = std::pair<ssize_t, ssize_t>;
@@ -261,6 +368,9 @@ struct Request {
Headers headers;
std::string body;
+ std::string remote_addr;
+ int remote_port = -1;
+
// for server
std::string version;
std::string target;
@@ -272,7 +382,9 @@ struct Request {
// for client
size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
ResponseHandler response_handler;
- ContentReceiver content_receiver;
+ ContentReceiverWithProgress content_receiver;
+ size_t content_length = 0;
+ ContentProvider content_provider;
Progress progress;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
@@ -281,6 +393,8 @@ struct Request {
bool has_header(const char *key) const;
std::string get_header_value(const char *key, size_t id = 0) const;
+ template <typename T>
+ T get_header_value(const char *key, size_t id = 0) const;
size_t get_header_value_count(const char *key) const;
void set_header(const char *key, const char *val);
void set_header(const char *key, const std::string &val);
@@ -295,35 +409,40 @@ struct Request {
MultipartFormData get_file_value(const char *key) const;
// private members...
- size_t content_length;
- ContentProvider content_provider;
+ size_t authorization_count_ = 0;
};
struct Response {
std::string version;
int status = -1;
+ std::string reason;
Headers headers;
std::string body;
bool has_header(const char *key) const;
std::string get_header_value(const char *key, size_t id = 0) const;
+ template <typename T>
+ T get_header_value(const char *key, size_t id = 0) const;
size_t get_header_value_count(const char *key) const;
void set_header(const char *key, const char *val);
void set_header(const char *key, const std::string &val);
- void set_redirect(const char *url);
+ void set_redirect(const char *url, int status = 302);
+ void set_redirect(const std::string &url, int status = 302);
void set_content(const char *s, size_t n, const char *content_type);
- void set_content(const std::string &s, const char *content_type);
+ void set_content(std::string s, const char *content_type);
void set_content_provider(
- size_t length,
- std::function<void(size_t offset, size_t length, DataSink &sink)>
- provider,
- std::function<void()> resource_releaser = [] {});
+ size_t length, const char *content_type, ContentProvider provider,
+ const std::function<void()> &resource_releaser = nullptr);
+
+ void set_content_provider(
+ const char *content_type, ContentProviderWithoutLength provider,
+ const std::function<void()> &resource_releaser = nullptr);
void set_chunked_content_provider(
- std::function<void(size_t offset, DataSink &sink)> provider,
- std::function<void()> resource_releaser = [] {});
+ const char *content_type, ContentProviderWithoutLength provider,
+ const std::function<void()> &resource_releaser = nullptr);
Response() = default;
Response(const Response &) = default;
@@ -331,15 +450,16 @@ struct Response {
Response(Response &&) = default;
Response &operator=(Response &&) = default;
~Response() {
- if (content_provider_resource_releaser) {
- content_provider_resource_releaser();
+ if (content_provider_resource_releaser_) {
+ content_provider_resource_releaser_();
}
}
// private members...
- size_t content_length = 0;
- ContentProvider content_provider;
- std::function<void()> content_provider_resource_releaser;
+ size_t content_length_ = 0;
+ ContentProvider content_provider_;
+ std::function<void()> content_provider_resource_releaser_;
+ bool is_chunked_content_provider = false;
};
class Stream {
@@ -349,22 +469,25 @@ public:
virtual bool is_readable() const = 0;
virtual bool is_writable() const = 0;
- virtual int read(char *ptr, size_t size) = 0;
- virtual int write(const char *ptr, size_t size) = 0;
- virtual std::string get_remote_addr() const = 0;
+ virtual ssize_t read(char *ptr, size_t size) = 0;
+ virtual ssize_t write(const char *ptr, size_t size) = 0;
+ virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
template <typename... Args>
- int write_format(const char *fmt, const Args &... args);
- int write(const char *ptr);
- int write(const std::string &s);
+ ssize_t write_format(const char *fmt, const Args &... args);
+ ssize_t write(const char *ptr);
+ ssize_t write(const std::string &s);
};
class TaskQueue {
public:
TaskQueue() = default;
virtual ~TaskQueue() = default;
+
virtual void enqueue(std::function<void()> fn) = 0;
virtual void shutdown() = 0;
+
+ virtual void on_idle(){};
};
class ThreadPool : public TaskQueue {
@@ -381,7 +504,7 @@ public:
void enqueue(std::function<void()> fn) override {
std::unique_lock<std::mutex> lock(mutex_);
- jobs_.push_back(fn);
+ jobs_.push_back(std::move(fn));
cond_.notify_one();
}
@@ -439,6 +562,26 @@ private:
using Logger = std::function<void(const Request &, const Response &)>;
+using SocketOptions = std::function<void(socket_t sock)>;
+
+inline void default_socket_options(socket_t sock) {
+ int yes = 1;
+#ifdef _WIN32
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
+ sizeof(yes));
+ setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+ reinterpret_cast<char *>(&yes), sizeof(yes));
+#else
+#ifdef SO_REUSEPORT
+ setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes),
+ sizeof(yes));
+#else
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes),
+ sizeof(yes));
+#endif
+#endif
+}
+
class Server {
public:
using Handler = std::function<void(const Request &, Response &)>;
@@ -461,23 +604,30 @@ public:
Server &Patch(const char *pattern, Handler handler);
Server &Patch(const char *pattern, HandlerWithContentReader handler);
Server &Delete(const char *pattern, Handler handler);
+ Server &Delete(const char *pattern, HandlerWithContentReader handler);
Server &Options(const char *pattern, Handler handler);
- [[deprecated]] bool set_base_dir(const char *dir,
- const char *mount_point = nullptr);
- bool set_mount_point(const char *mount_point, const char *dir);
+ bool set_base_dir(const char *dir, const char *mount_point = nullptr);
+ bool set_mount_point(const char *mount_point, const char *dir,
+ Headers headers = Headers());
bool remove_mount_point(const char *mount_point);
void set_file_extension_and_mimetype_mapping(const char *ext,
const char *mime);
void set_file_request_handler(Handler handler);
void set_error_handler(Handler handler);
+ void set_expect_100_continue_handler(Expect100ContinueHandler handler);
void set_logger(Logger logger);
- void set_expect_100_continue_handler(Expect100ContinueHandler handler);
+ void set_tcp_nodelay(bool on);
+ void set_socket_options(SocketOptions socket_options);
void set_keep_alive_max_count(size_t count);
- void set_read_timeout(time_t sec, time_t usec);
+ void set_keep_alive_timeout(time_t sec);
+ void set_read_timeout(time_t sec, time_t usec = 0);
+ void set_write_timeout(time_t sec, time_t usec = 0);
+ void set_idle_interval(time_t sec, time_t usec = 0);
+
void set_payload_max_length(size_t length);
bool bind_to_port(const char *host, int port, int socket_flags = 0);
@@ -492,54 +642,66 @@ public:
std::function<TaskQueue *(void)> new_task_queue;
protected:
- bool process_request(Stream &strm, bool last_connection,
- bool &connection_close,
+ bool process_request(Stream &strm, bool close_connection,
+ bool &connection_closed,
const std::function<void(Request &)> &setup_request);
- size_t keep_alive_max_count_;
- time_t read_timeout_sec_;
- time_t read_timeout_usec_;
- size_t payload_max_length_;
+ std::atomic<socket_t> svr_sock_;
+ size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
+ time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
+ time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
+ time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
+ time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
+ time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+ time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
+ time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
+ size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
private:
using Handlers = std::vector<std::pair<std::regex, Handler>>;
using HandlersForContentReader =
std::vector<std::pair<std::regex, HandlerWithContentReader>>;
- socket_t create_server_socket(const char *host, int port,
- int socket_flags) const;
+ socket_t create_server_socket(const char *host, int port, int socket_flags,
+ SocketOptions socket_options) const;
int bind_internal(const char *host, int port, int socket_flags);
bool listen_internal();
- bool routing(Request &req, Response &res, Stream &strm, bool last_connection);
+ bool routing(Request &req, Response &res, Stream &strm);
bool handle_file_request(Request &req, Response &res, bool head = false);
- bool dispatch_request(Request &req, Response &res, Handlers &handlers);
- bool dispatch_request_for_content_reader(Request &req, Response &res,
- ContentReader content_reader,
- HandlersForContentReader &handlers);
+ bool dispatch_request(Request &req, Response &res, const Handlers &handlers);
+ bool
+ dispatch_request_for_content_reader(Request &req, Response &res,
+ ContentReader content_reader,
+ const HandlersForContentReader &handlers);
bool parse_request_line(const char *s, Request &req);
- bool write_response(Stream &strm, bool last_connection, const Request &req,
+ bool write_response(Stream &strm, bool close_connection, const Request &req,
Response &res);
bool write_content_with_provider(Stream &strm, const Request &req,
Response &res, const std::string &boundary,
const std::string &content_type);
- bool read_content(Stream &strm, bool last_connection, Request &req,
- Response &res);
- bool read_content_with_content_receiver(
- Stream &strm, bool last_connection, Request &req, Response &res,
- ContentReceiver receiver, MultipartContentHeader multipart_header,
- ContentReceiver multipart_receiver);
- bool read_content_core(Stream &strm, bool last_connection, Request &req,
- Response &res, ContentReceiver receiver,
+ bool read_content(Stream &strm, Request &req, Response &res);
+ bool
+ read_content_with_content_receiver(Stream &strm, Request &req, Response &res,
+ ContentReceiver receiver,
+ MultipartContentHeader multipart_header,
+ ContentReceiver multipart_receiver);
+ bool read_content_core(Stream &strm, Request &req, Response &res,
+ ContentReceiver receiver,
MultipartContentHeader mulitpart_header,
ContentReceiver multipart_receiver);
virtual bool process_and_close_socket(socket_t sock);
+ struct MountPointEntry {
+ std::string mount_point;
+ std::string base_dir;
+ Headers headers;
+ };
+ std::vector<MountPointEntry> base_dirs_;
+
std::atomic<bool> is_running_;
- std::atomic<socket_t> svr_sock_;
- std::vector<std::pair<std::string, std::string>> base_dirs_;
std::map<std::string, std::string> file_extension_and_mimetype_map_;
Handler file_request_handler_;
Handlers get_handlers_;
@@ -550,293 +712,454 @@ private:
Handlers patch_handlers_;
HandlersForContentReader patch_handlers_for_content_reader_;
Handlers delete_handlers_;
+ HandlersForContentReader delete_handlers_for_content_reader_;
Handlers options_handlers_;
Handler error_handler_;
Logger logger_;
Expect100ContinueHandler expect_100_continue_handler_;
-};
-
-class Client {
-public:
- explicit Client(const std::string &host, int port = 80,
- const std::string &client_cert_path = std::string(),
- const std::string &client_key_path = std::string());
- virtual ~Client();
-
- virtual bool is_valid() const;
-
- std::shared_ptr<Response> Get(const char *path);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers);
-
- std::shared_ptr<Response> Get(const char *path, Progress progress);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- Progress progress);
-
- std::shared_ptr<Response> Get(const char *path,
- ContentReceiver content_receiver);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- ContentReceiver content_receiver);
-
- std::shared_ptr<Response>
- Get(const char *path, ContentReceiver content_receiver, Progress progress);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- ContentReceiver content_receiver,
- Progress progress);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- ResponseHandler response_handler,
- ContentReceiver content_receiver);
-
- std::shared_ptr<Response> Get(const char *path, const Headers &headers,
- ResponseHandler response_handler,
- ContentReceiver content_receiver,
- Progress progress);
-
- std::shared_ptr<Response> Head(const char *path);
-
- std::shared_ptr<Response> Head(const char *path, const Headers &headers);
-
- std::shared_ptr<Response> Post(const char *path, const std::string &body,
- const char *content_type);
-
- std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- const std::string &body,
- const char *content_type);
-
- std::shared_ptr<Response> Post(const char *path, size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
-
- std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
-
- std::shared_ptr<Response> Post(const char *path, const Params &params);
-
- std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- const Params &params);
-
- std::shared_ptr<Response> Post(const char *path,
- const MultipartFormDataItems &items);
-
- std::shared_ptr<Response> Post(const char *path, const Headers &headers,
- const MultipartFormDataItems &items);
-
- std::shared_ptr<Response> Put(const char *path, const std::string &body,
- const char *content_type);
-
- std::shared_ptr<Response> Put(const char *path, const Headers &headers,
- const std::string &body,
- const char *content_type);
-
- std::shared_ptr<Response> Put(const char *path, size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
-
- std::shared_ptr<Response> Put(const char *path, const Headers &headers,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
-
- std::shared_ptr<Response> Put(const char *path, const Params &params);
-
- std::shared_ptr<Response> Put(const char *path, const Headers &headers,
- const Params &params);
-
- std::shared_ptr<Response> Patch(const char *path, const std::string &body,
- const char *content_type);
+ bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+ SocketOptions socket_options_ = default_socket_options;
+};
- std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
- const std::string &body,
- const char *content_type);
+enum Error {
+ Success = 0,
+ Unknown,
+ Connection,
+ BindIPAddress,
+ Read,
+ Write,
+ ExceedRedirectCount,
+ Canceled,
+ SSLConnection,
+ SSLLoadingCerts,
+ SSLServerVerification,
+ UnsupportedMultipartBoundaryChars
+};
- std::shared_ptr<Response> Patch(const char *path, size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
+class Result {
+public:
+ Result(std::unique_ptr<Response> res, Error err)
+ : res_(std::move(res)), err_(err) {}
+ operator bool() const { return res_ != nullptr; }
+ bool operator==(std::nullptr_t) const { return res_ == nullptr; }
+ bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
+ const Response &value() const { return *res_; }
+ Response &value() { return *res_; }
+ const Response &operator*() const { return *res_; }
+ Response &operator*() { return *res_; }
+ const Response *operator->() const { return res_.get(); }
+ Response *operator->() { return res_.get(); }
+ Error error() const { return err_; }
- std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type);
+private:
+ std::unique_ptr<Response> res_;
+ Error err_;
+};
- std::shared_ptr<Response> Delete(const char *path);
+class ClientImpl {
+public:
+ explicit ClientImpl(const std::string &host);
- std::shared_ptr<Response> Delete(const char *path, const std::string &body,
- const char *content_type);
+ explicit ClientImpl(const std::string &host, int port);
- std::shared_ptr<Response> Delete(const char *path, const Headers &headers);
+ explicit ClientImpl(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
- std::shared_ptr<Response> Delete(const char *path, const Headers &headers,
- const std::string &body,
- const char *content_type);
+ virtual ~ClientImpl();
- std::shared_ptr<Response> Options(const char *path);
+ virtual bool is_valid() const;
- std::shared_ptr<Response> Options(const char *path, const Headers &headers);
+ Result Get(const char *path);
+ Result Get(const char *path, const Headers &headers);
+ Result Get(const char *path, Progress progress);
+ Result Get(const char *path, const Headers &headers, Progress progress);
+ Result Get(const char *path, ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler, ContentReceiver content_receiver,
+ Progress progress);
+
+ Result Head(const char *path);
+ Result Head(const char *path, const Headers &headers);
+
+ Result Post(const char *path);
+ Result Post(const char *path, const std::string &body,
+ const char *content_type);
+ Result Post(const char *path, const Headers &headers, const std::string &body,
+ const char *content_type);
+ Result Post(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Post(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Post(const char *path, const Params &params);
+ Result Post(const char *path, const Headers &headers, const Params &params);
+ Result Post(const char *path, const MultipartFormDataItems &items);
+ Result Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items);
+ Result Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items, const std::string &boundary);
+
+ Result Put(const char *path);
+ Result Put(const char *path, const std::string &body,
+ const char *content_type);
+ Result Put(const char *path, const Headers &headers, const std::string &body,
+ const char *content_type);
+ Result Put(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Put(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Put(const char *path, const Params &params);
+ Result Put(const char *path, const Headers &headers, const Params &params);
+
+ Result Patch(const char *path, const std::string &body,
+ const char *content_type);
+ Result Patch(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type);
+ Result Patch(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Patch(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+
+ Result Delete(const char *path);
+ Result Delete(const char *path, const std::string &body,
+ const char *content_type);
+ Result Delete(const char *path, const Headers &headers);
+ Result Delete(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type);
+
+ Result Options(const char *path);
+ Result Options(const char *path, const Headers &headers);
bool send(const Request &req, Response &res);
- bool send(const std::vector<Request> &requests,
- std::vector<Response> &responses);
+ size_t is_socket_open() const;
- void set_timeout_sec(time_t timeout_sec);
+ void stop();
- void set_read_timeout(time_t sec, time_t usec);
+ void set_default_headers(Headers headers);
- void set_keep_alive_max_count(size_t count);
+ void set_tcp_nodelay(bool on);
+ void set_socket_options(SocketOptions socket_options);
- void set_basic_auth(const char *username, const char *password);
+ void set_connection_timeout(time_t sec, time_t usec = 0);
+ void set_read_timeout(time_t sec, time_t usec = 0);
+ void set_write_timeout(time_t sec, time_t usec = 0);
+ void set_basic_auth(const char *username, const char *password);
+ void set_bearer_token_auth(const char *token);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void set_digest_auth(const char *username, const char *password);
#endif
+ void set_keep_alive(bool on);
void set_follow_location(bool on);
void set_compress(bool on);
+ void set_decompress(bool on);
+
void set_interface(const char *intf);
void set_proxy(const char *host, int port);
-
void set_proxy_basic_auth(const char *username, const char *password);
-
+ void set_proxy_bearer_token_auth(const char *token);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
void set_proxy_digest_auth(const char *username, const char *password);
#endif
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void enable_server_certificate_verification(bool enabled);
+#endif
+
void set_logger(Logger logger);
protected:
+ struct Socket {
+ socket_t sock = INVALID_SOCKET;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ SSL *ssl = nullptr;
+#endif
+
+ bool is_open() const { return sock != INVALID_SOCKET; }
+ };
+
+ virtual bool create_and_connect_socket(Socket &socket);
+
+ // All of:
+ // shutdown_ssl
+ // shutdown_socket
+ // close_socket
+ // should ONLY be called when socket_mutex_ is locked.
+ // Also, shutdown_ssl and close_socket should also NOT be called concurrently
+ // with a DIFFERENT thread sending requests using that socket.
+ virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
+ void shutdown_socket(Socket &socket);
+ void close_socket(Socket &socket);
+
+ // Similar to shutdown_ssl and close_socket, this should NOT be called
+ // concurrently with a DIFFERENT thread sending requests from the socket
+ void lock_socket_and_shutdown_and_close();
+
bool process_request(Stream &strm, const Request &req, Response &res,
- bool last_connection, bool &connection_close);
+ bool close_connection);
+
+ Error get_last_error() const;
+
+ void copy_settings(const ClientImpl &rhs);
+ // Error state
+ mutable std::atomic<Error> error_;
+
+ // Socket endoint information
const std::string host_;
const int port_;
const std::string host_and_port_;
+ // Current open socket
+ Socket socket_;
+ mutable std::mutex socket_mutex_;
+ std::recursive_mutex request_mutex_;
+
+ // These are all protected under socket_mutex
+ int socket_requests_in_flight_ = 0;
+ std::thread::id socket_requests_are_from_thread_ = std::thread::id();
+ bool socket_should_be_closed_when_request_is_done_ = false;
+
+ // Default headers
+ Headers default_headers_;
+
// Settings
std::string client_cert_path_;
std::string client_key_path_;
- time_t timeout_sec_ = 300;
+ time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
+ time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
-
- size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
+ time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
+ time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
std::string basic_auth_username_;
std::string basic_auth_password_;
+ std::string bearer_token_auth_token_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
std::string digest_auth_username_;
std::string digest_auth_password_;
#endif
+ bool keep_alive_ = false;
bool follow_location_ = false;
+ bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+ SocketOptions socket_options_ = nullptr;
+
bool compress_ = false;
+ bool decompress_ = true;
std::string interface_;
std::string proxy_host_;
- int proxy_port_;
+ int proxy_port_ = -1;
std::string proxy_basic_auth_username_;
std::string proxy_basic_auth_password_;
+ std::string proxy_bearer_token_auth_token_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
std::string proxy_digest_auth_username_;
std::string proxy_digest_auth_password_;
#endif
- Logger logger_;
-
- void copy_settings(const Client &rhs) {
- client_cert_path_ = rhs.client_cert_path_;
- client_key_path_ = rhs.client_key_path_;
- timeout_sec_ = rhs.timeout_sec_;
- read_timeout_sec_ = rhs.read_timeout_sec_;
- read_timeout_usec_ = rhs.read_timeout_usec_;
- keep_alive_max_count_ = rhs.keep_alive_max_count_;
- basic_auth_username_ = rhs.basic_auth_username_;
- basic_auth_password_ = rhs.basic_auth_password_;
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- digest_auth_username_ = rhs.digest_auth_username_;
- digest_auth_password_ = rhs.digest_auth_password_;
-#endif
- follow_location_ = rhs.follow_location_;
- compress_ = rhs.compress_;
- interface_ = rhs.interface_;
- proxy_host_ = rhs.proxy_host_;
- proxy_port_ = rhs.proxy_port_;
- proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
- proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
- proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
+ bool server_certificate_verification_ = true;
#endif
- logger_ = rhs.logger_;
- }
+
+ Logger logger_;
private:
socket_t create_client_socket() const;
bool read_response_line(Stream &strm, Response &res);
- bool write_request(Stream &strm, const Request &req, bool last_connection);
+ bool write_request(Stream &strm, const Request &req, bool close_connection);
bool redirect(const Request &req, Response &res);
bool handle_request(Stream &strm, const Request &req, Response &res,
- bool last_connection, bool &connection_close);
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- bool connect(socket_t sock, Response &res, bool &error);
-#endif
-
- std::shared_ptr<Response> send_with_content_provider(
+ bool close_connection);
+ std::unique_ptr<Response> send_with_content_provider(
const char *method, const char *path, const Headers &headers,
const std::string &body, size_t content_length,
ContentProvider content_provider, const char *content_type);
- virtual bool process_and_close_socket(
- socket_t sock, size_t request_count,
- std::function<bool(Stream &strm, bool last_connection,
- bool &connection_close)>
- callback);
-
+ // socket is const because this function is called when socket_mutex_ is not locked
+ virtual bool process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback);
virtual bool is_ssl() const;
};
-inline void Get(std::vector<Request> &requests, const char *path,
- const Headers &headers) {
- Request req;
- req.method = "GET";
- req.path = path;
- req.headers = headers;
- requests.emplace_back(std::move(req));
-}
+class Client {
+public:
+ // Universal interface
+ explicit Client(const char *scheme_host_port);
+
+ explicit Client(const char *scheme_host_port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ // HTTP only interface
+ explicit Client(const std::string &host, int port);
+
+ explicit Client(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ ~Client();
+
+ bool is_valid() const;
+
+ Result Get(const char *path);
+ Result Get(const char *path, const Headers &headers);
+ Result Get(const char *path, Progress progress);
+ Result Get(const char *path, const Headers &headers, Progress progress);
+ Result Get(const char *path, ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress);
+ Result Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver);
+ Result Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler, ContentReceiver content_receiver,
+ Progress progress);
+ Result Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress);
+
+ Result Head(const char *path);
+ Result Head(const char *path, const Headers &headers);
+
+ Result Post(const char *path);
+ Result Post(const char *path, const std::string &body,
+ const char *content_type);
+ Result Post(const char *path, const Headers &headers, const std::string &body,
+ const char *content_type);
+ Result Post(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Post(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Post(const char *path, const Params &params);
+ Result Post(const char *path, const Headers &headers, const Params &params);
+ Result Post(const char *path, const MultipartFormDataItems &items);
+ Result Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items);
+ Result Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items, const std::string &boundary);
+ Result Put(const char *path);
+ Result Put(const char *path, const std::string &body,
+ const char *content_type);
+ Result Put(const char *path, const Headers &headers, const std::string &body,
+ const char *content_type);
+ Result Put(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Put(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Put(const char *path, const Params &params);
+ Result Put(const char *path, const Headers &headers, const Params &params);
+ Result Patch(const char *path, const std::string &body,
+ const char *content_type);
+ Result Patch(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type);
+ Result Patch(const char *path, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+ Result Patch(const char *path, const Headers &headers, size_t content_length,
+ ContentProvider content_provider, const char *content_type);
+
+ Result Delete(const char *path);
+ Result Delete(const char *path, const std::string &body,
+ const char *content_type);
+ Result Delete(const char *path, const Headers &headers);
+ Result Delete(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type);
+
+ Result Options(const char *path);
+ Result Options(const char *path, const Headers &headers);
-inline void Get(std::vector<Request> &requests, const char *path) {
- Get(requests, path, Headers());
-}
+ bool send(const Request &req, Response &res);
-inline void Post(std::vector<Request> &requests, const char *path,
- const Headers &headers, const std::string &body,
- const char *content_type) {
- Request req;
- req.method = "POST";
- req.path = path;
- req.headers = headers;
- req.headers.emplace("Content-Type", content_type);
- req.body = body;
- requests.emplace_back(std::move(req));
-}
+ size_t is_socket_open() const;
-inline void Post(std::vector<Request> &requests, const char *path,
- const std::string &body, const char *content_type) {
- Post(requests, path, Headers(), body, content_type);
-}
+ void stop();
+
+ void set_default_headers(Headers headers);
+
+ void set_tcp_nodelay(bool on);
+ void set_socket_options(SocketOptions socket_options);
+
+ void set_connection_timeout(time_t sec, time_t usec = 0);
+ void set_read_timeout(time_t sec, time_t usec = 0);
+ void set_write_timeout(time_t sec, time_t usec = 0);
+
+ void set_basic_auth(const char *username, const char *password);
+ void set_bearer_token_auth(const char *token);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_digest_auth(const char *username, const char *password);
+#endif
+
+ void set_keep_alive(bool on);
+ void set_follow_location(bool on);
+
+ void set_compress(bool on);
+
+ void set_decompress(bool on);
+
+ void set_interface(const char *intf);
+
+ void set_proxy(const char *host, int port);
+ void set_proxy_basic_auth(const char *username, const char *password);
+ void set_proxy_bearer_token_auth(const char *token);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_proxy_digest_auth(const char *username, const char *password);
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void enable_server_certificate_verification(bool enabled);
+#endif
+
+ void set_logger(Logger logger);
+
+ // SSL
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ void set_ca_cert_path(const char *ca_cert_file_path,
+ const char *ca_cert_dir_path = nullptr);
+
+ void set_ca_cert_store(X509_STORE *ca_cert_store);
+
+ long get_openssl_verify_result() const;
+
+ SSL_CTX *ssl_context() const;
+#endif
+
+private:
+ std::unique_ptr<ClientImpl> cli_;
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ bool is_ssl_ = false;
+#endif
+}; // namespace httplib
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
class SSLServer : public Server {
@@ -845,43 +1168,58 @@ public:
const char *client_ca_cert_file_path = nullptr,
const char *client_ca_cert_dir_path = nullptr);
- virtual ~SSLServer();
+ SSLServer(X509 *cert, EVP_PKEY *private_key,
+ X509_STORE *client_ca_cert_store = nullptr);
- virtual bool is_valid() const;
+ ~SSLServer() override;
+
+ bool is_valid() const override;
private:
- virtual bool process_and_close_socket(socket_t sock);
+ bool process_and_close_socket(socket_t sock) override;
SSL_CTX *ctx_;
std::mutex ctx_mutex_;
};
-class SSLClient : public Client {
+class SSLClient : public ClientImpl {
public:
- SSLClient(const std::string &host, int port = 443,
- const std::string &client_cert_path = std::string(),
- const std::string &client_key_path = std::string());
+ explicit SSLClient(const std::string &host);
- virtual ~SSLClient();
+ explicit SSLClient(const std::string &host, int port);
- virtual bool is_valid() const;
+ explicit SSLClient(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path);
+
+ explicit SSLClient(const std::string &host, int port, X509 *client_cert,
+ EVP_PKEY *client_key);
+
+ ~SSLClient() override;
- void set_ca_cert_path(const char *ca_ceert_file_path,
+ bool is_valid() const override;
+
+ void set_ca_cert_path(const char *ca_cert_file_path,
const char *ca_cert_dir_path = nullptr);
- void enable_server_certificate_verification(bool enabled);
+ void set_ca_cert_store(X509_STORE *ca_cert_store);
long get_openssl_verify_result() const;
- SSL_CTX *ssl_context() const noexcept;
+ SSL_CTX *ssl_context() const;
private:
- virtual bool process_and_close_socket(
- socket_t sock, size_t request_count,
- std::function<bool(Stream &strm, bool last_connection,
- bool &connection_close)>
- callback);
- virtual bool is_ssl() const;
+ bool create_and_connect_socket(Socket &socket) override;
+ void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
+
+ bool process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) override;
+ bool is_ssl() const override;
+
+ bool connect_with_proxy(Socket &sock, Response &res, bool &success);
+ bool initialize_ssl(Socket &socket);
+
+ bool load_certs();
bool verify_host(X509 *server_cert) const;
bool verify_host_with_subject_alt_name(X509 *server_cert) const;
@@ -890,12 +1228,15 @@ private:
SSL_CTX *ctx_;
std::mutex ctx_mutex_;
+ std::once_flag initialize_cert_;
+
std::vector<std::string> host_components_;
std::string ca_cert_file_path_;
std::string ca_cert_dir_path_;
- bool server_certificate_verification_ = false;
long verify_result_ = 0;
+
+ friend class ClientImpl;
};
#endif
@@ -948,31 +1289,39 @@ inline std::string from_i_to_hex(size_t n) {
return ret;
}
+inline bool start_with(const std::string &a, const std::string &b) {
+ if (a.size() < b.size()) { return false; }
+ for (size_t i = 0; i < b.size(); i++) {
+ if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
+ }
+ return true;
+}
+
inline size_t to_utf8(int code, char *buff) {
if (code < 0x0080) {
buff[0] = (code & 0x7F);
return 1;
} else if (code < 0x0800) {
- buff[0] = (0xC0 | ((code >> 6) & 0x1F));
- buff[1] = (0x80 | (code & 0x3F));
+ buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
+ buff[1] = static_cast<char>(0x80 | (code & 0x3F));
return 2;
} else if (code < 0xD800) {
- buff[0] = (0xE0 | ((code >> 12) & 0xF));
- buff[1] = (0x80 | ((code >> 6) & 0x3F));
- buff[2] = (0x80 | (code & 0x3F));
+ buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
+ buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | (code & 0x3F));
return 3;
} else if (code < 0xE000) { // D800 - DFFF is invalid...
return 0;
} else if (code < 0x10000) {
- buff[0] = (0xE0 | ((code >> 12) & 0xF));
- buff[1] = (0x80 | ((code >> 6) & 0x3F));
- buff[2] = (0x80 | (code & 0x3F));
+ buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
+ buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | (code & 0x3F));
return 3;
} else if (code < 0x110000) {
- buff[0] = (0xF0 | ((code >> 18) & 0x7));
- buff[1] = (0x80 | ((code >> 12) & 0x3F));
- buff[2] = (0x80 | ((code >> 6) & 0x3F));
- buff[3] = (0x80 | (code & 0x3F));
+ buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
+ buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
+ buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+ buff[3] = static_cast<char>(0x80 | (code & 0x3F));
return 4;
}
@@ -992,8 +1341,8 @@ inline std::string base64_encode(const std::string &in) {
int val = 0;
int valb = -6;
- for (uint8_t c : in) {
- val = (val << 8) + c;
+ for (auto c : in) {
+ val = (val << 8) + static_cast<uint8_t>(c);
valb += 8;
while (valb >= 0) {
out.push_back(lookup[(val >> valb) & 0x3F]);
@@ -1057,13 +1406,81 @@ inline bool is_valid_path(const std::string &path) {
return true;
}
+inline std::string encode_url(const std::string &s) {
+ std::string result;
+
+ for (size_t i = 0; s[i]; i++) {
+ switch (s[i]) {
+ case ' ': result += "%20"; break;
+ case '+': result += "%2B"; break;
+ case '\r': result += "%0D"; break;
+ case '\n': result += "%0A"; break;
+ case '\'': result += "%27"; break;
+ case ',': result += "%2C"; break;
+ // case ':': result += "%3A"; break; // ok? probably...
+ case ';': result += "%3B"; break;
+ default:
+ auto c = static_cast<uint8_t>(s[i]);
+ if (c >= 0x80) {
+ result += '%';
+ char hex[4];
+ auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
+ assert(len == 2);
+ result.append(hex, static_cast<size_t>(len));
+ } else {
+ result += s[i];
+ }
+ break;
+ }
+ }
+
+ return result;
+}
+
+inline std::string decode_url(const std::string &s,
+ bool convert_plus_to_space) {
+ std::string result;
+
+ for (size_t i = 0; i < s.size(); i++) {
+ if (s[i] == '%' && i + 1 < s.size()) {
+ if (s[i + 1] == 'u') {
+ int val = 0;
+ if (from_hex_to_i(s, i + 2, 4, val)) {
+ // 4 digits Unicode codes
+ char buff[4];
+ size_t len = to_utf8(val, buff);
+ if (len > 0) { result.append(buff, len); }
+ i += 5; // 'u0000'
+ } else {
+ result += s[i];
+ }
+ } else {
+ int val = 0;
+ if (from_hex_to_i(s, i + 1, 2, val)) {
+ // 2 digits hex codes
+ result += static_cast<char>(val);
+ i += 2; // '00'
+ } else {
+ result += s[i];
+ }
+ }
+ } else if (convert_plus_to_space && s[i] == '+') {
+ result += ' ';
+ } else {
+ result += s[i];
+ }
+ }
+
+ return result;
+}
+
inline void read_file(const std::string &path, std::string &out) {
std::ifstream fs(path, std::ios_base::binary);
fs.seekg(0, std::ios_base::end);
auto size = fs.tellg();
fs.seekg(0);
out.resize(static_cast<size_t>(size));
- fs.read(&out[0], size);
+ fs.read(&out[0], static_cast<std::streamsize>(size));
}
inline std::string file_extension(const std::string &path) {
@@ -1073,19 +1490,41 @@ inline std::string file_extension(const std::string &path) {
return std::string();
}
+inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
+
+inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
+ size_t right) {
+ while (b + left < e && is_space_or_tab(b[left])) {
+ left++;
+ }
+ while (right > 0 && is_space_or_tab(b[right - 1])) {
+ right--;
+ }
+ return std::make_pair(left, right);
+}
+
+inline std::string trim_copy(const std::string &s) {
+ auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
+ return s.substr(r.first, r.second - r.first);
+}
+
template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
- int i = 0;
- int beg = 0;
+ size_t i = 0;
+ size_t beg = 0;
- while (e ? (b + i != e) : (b[i] != '\0')) {
+ while (e ? (b + i < e) : (b[i] != '\0')) {
if (b[i] == d) {
- fn(&b[beg], &b[i]);
+ auto r = trim(b, e, beg, i);
+ if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
beg = i + 1;
}
i++;
}
- if (i) { fn(&b[beg], &b[i]); }
+ if (i) {
+ auto r = trim(b, e, beg, i);
+ if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
+ }
}
// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
@@ -1172,7 +1611,17 @@ inline int close_socket(socket_t sock) {
#endif
}
-inline int select_read(socket_t sock, time_t sec, time_t usec) {
+template <typename T> inline ssize_t handle_EINTR(T fn) {
+ ssize_t res = false;
+ while (true) {
+ res = fn();
+ if (res < 0 && errno == EINTR) { continue; }
+ break;
+ }
+ return res;
+}
+
+inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
#ifdef CPPHTTPLIB_USE_POLL
struct pollfd pfd_read;
pfd_read.fd = sock;
@@ -1180,7 +1629,7 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- return poll(&pfd_read, 1, timeout);
+ return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
fd_set fds;
FD_ZERO(&fds);
@@ -1188,13 +1637,15 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
timeval tv;
tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<long>(usec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
- return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+ return handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+ });
#endif
}
-inline int select_write(socket_t sock, time_t sec, time_t usec) {
+inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
#ifdef CPPHTTPLIB_USE_POLL
struct pollfd pfd_read;
pfd_read.fd = sock;
@@ -1202,7 +1653,7 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- return poll(&pfd_read, 1, timeout);
+ return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
#else
fd_set fds;
FD_ZERO(&fds);
@@ -1210,9 +1661,11 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
timeval tv;
tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<long>(usec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
- return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
+ return handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
+ });
#endif
}
@@ -1224,13 +1677,14 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
- if (poll(&pfd_read, 1, timeout) > 0 &&
- pfd_read.revents & (POLLIN | POLLOUT)) {
+ auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+
+ if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
int error = 0;
socklen_t len = sizeof(error);
- return getsockopt(sock, SOL_SOCKET, SO_ERROR,
- reinterpret_cast<char *>(&error), &len) >= 0 &&
- !error;
+ auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
+ reinterpret_cast<char *>(&error), &len);
+ return res >= 0 && !error;
}
return false;
#else
@@ -1243,10 +1697,13 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
timeval tv;
tv.tv_sec = static_cast<long>(sec);
- tv.tv_usec = static_cast<long>(usec);
+ tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+
+ auto ret = handle_EINTR([&]() {
+ return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
+ });
- if (select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv) > 0 &&
- (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
+ if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
int error = 0;
socklen_t len = sizeof(error);
return getsockopt(sock, SOL_SOCKET, SO_ERROR,
@@ -1259,40 +1716,45 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
class SocketStream : public Stream {
public:
- SocketStream(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec);
+ SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec);
~SocketStream() override;
bool is_readable() const override;
bool is_writable() const override;
- int read(char *ptr, size_t size) override;
- int write(const char *ptr, size_t size) override;
- std::string get_remote_addr() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
private:
socket_t sock_;
time_t read_timeout_sec_;
time_t read_timeout_usec_;
+ time_t write_timeout_sec_;
+ time_t write_timeout_usec_;
};
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
class SSLSocketStream : public Stream {
public:
SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
- time_t read_timeout_usec);
- virtual ~SSLSocketStream();
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec);
+ ~SSLSocketStream() override;
bool is_readable() const override;
bool is_writable() const override;
- int read(char *ptr, size_t size) override;
- int write(const char *ptr, size_t size) override;
- std::string get_remote_addr() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
private:
socket_t sock_;
SSL *ssl_;
time_t read_timeout_sec_;
time_t read_timeout_usec_;
+ time_t write_timeout_sec_;
+ time_t write_timeout_usec_;
};
#endif
@@ -1303,58 +1765,76 @@ public:
bool is_readable() const override;
bool is_writable() const override;
- int read(char *ptr, size_t size) override;
- int write(const char *ptr, size_t size) override;
- std::string get_remote_addr() const override;
+ ssize_t read(char *ptr, size_t size) override;
+ ssize_t write(const char *ptr, size_t size) override;
+ void get_remote_ip_and_port(std::string &ip, int &port) const override;
const std::string &get_buffer() const;
private:
std::string buffer;
- int position = 0;
+ size_t position = 0;
};
+inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
+ using namespace std::chrono;
+ auto start = steady_clock::now();
+ while (true) {
+ auto val = select_read(sock, 0, 10000);
+ if (val < 0) {
+ return false;
+ } else if (val == 0) {
+ auto current = steady_clock::now();
+ auto duration = duration_cast<milliseconds>(current - start);
+ auto timeout = keep_alive_timeout_sec * 1000;
+ if (duration.count() > timeout) { return false; }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ } else {
+ return true;
+ }
+ }
+}
+
template <typename T>
-inline bool process_socket(bool is_client_request, socket_t sock,
- size_t keep_alive_max_count, time_t read_timeout_sec,
- time_t read_timeout_usec, T callback) {
+inline bool
+process_server_socket_core(socket_t sock, size_t keep_alive_max_count,
+ time_t keep_alive_timeout_sec, T callback) {
assert(keep_alive_max_count > 0);
-
auto ret = false;
-
- if (keep_alive_max_count > 1) {
- auto count = keep_alive_max_count;
- while (count > 0 &&
- (is_client_request ||
- select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
- CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
- SocketStream strm(sock, read_timeout_sec, read_timeout_usec);
- auto last_connection = count == 1;
- auto connection_close = false;
-
- ret = callback(strm, last_connection, connection_close);
- if (!ret || connection_close) { break; }
-
- count--;
- }
- } else { // keep_alive_max_count is 0 or 1
- SocketStream strm(sock, read_timeout_sec, read_timeout_usec);
- auto dummy_connection_close = false;
- ret = callback(strm, true, dummy_connection_close);
+ auto count = keep_alive_max_count;
+ while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) {
+ auto close_connection = count == 1;
+ auto connection_closed = false;
+ ret = callback(close_connection, connection_closed);
+ if (!ret || connection_closed) { break; }
+ count--;
}
-
return ret;
}
template <typename T>
-inline bool process_and_close_socket(bool is_client_request, socket_t sock,
- size_t keep_alive_max_count,
- time_t read_timeout_sec,
- time_t read_timeout_usec, T callback) {
- auto ret = process_socket(is_client_request, sock, keep_alive_max_count,
- read_timeout_sec, read_timeout_usec, callback);
- close_socket(sock);
- return ret;
+inline bool
+process_server_socket(socket_t sock, size_t keep_alive_max_count,
+ time_t keep_alive_timeout_sec, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ return process_server_socket_core(
+ sock, keep_alive_max_count, keep_alive_timeout_sec,
+ [&](bool close_connection, bool &connection_closed) {
+ SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm, close_connection, connection_closed);
+ });
+}
+
+template <typename T>
+inline bool process_client_socket(socket_t sock, time_t read_timeout_sec,
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm);
}
inline int shutdown_socket(socket_t sock) {
@@ -1365,18 +1845,10 @@ inline int shutdown_socket(socket_t sock) {
#endif
}
-template <typename Fn>
-socket_t create_socket(const char *host, int port, Fn fn,
- int socket_flags = 0) {
-#ifdef _WIN32
-#define SO_SYNCHRONOUS_NONALERT 0x20
-#define SO_OPENTYPE 0x7008
-
- int opt = SO_SYNCHRONOUS_NONALERT;
- setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&opt,
- sizeof(opt));
-#endif
-
+template <typename BindOrConnect>
+socket_t create_socket(const char *host, int port, int socket_flags,
+ bool tcp_nodelay, SocketOptions socket_options,
+ BindOrConnect bind_or_connect) {
// Get address info
struct addrinfo hints;
struct addrinfo *result;
@@ -1390,6 +1862,9 @@ socket_t create_socket(const char *host, int port, Fn fn,
auto service = std::to_string(port);
if (getaddrinfo(host, service.c_str(), &hints, &result)) {
+#ifdef __linux__
+ res_init();
+#endif
return INVALID_SOCKET;
}
@@ -1424,17 +1899,22 @@ socket_t create_socket(const char *host, int port, Fn fn,
if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; }
#endif
- // Make 'reuse address' option available
- int yes = 1;
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
- sizeof(yes));
-#ifdef SO_REUSEPORT
- setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char *>(&yes),
- sizeof(yes));
-#endif
+ if (tcp_nodelay) {
+ int yes = 1;
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes),
+ sizeof(yes));
+ }
+
+ if (socket_options) { socket_options(sock); }
+
+ if (rp->ai_family == AF_INET6) {
+ int no = 0;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
+ sizeof(no));
+ }
// bind or connect
- if (fn(sock, *rp)) {
+ if (bind_or_connect(sock, *rp)) {
freeaddrinfo(result);
return sock;
}
@@ -1479,7 +1959,7 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
auto ret = false;
for (auto rp = result; rp; rp = rp->ai_next) {
const auto &ai = *rp;
- if (!::bind(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen))) {
+ if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
ret = true;
break;
}
@@ -1489,8 +1969,12 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
return ret;
}
+#if !defined _WIN32 && !defined ANDROID
+#define USE_IF2IP
+#endif
+
+#ifdef USE_IF2IP
inline std::string if2ip(const std::string &ifn) {
-#ifndef _WIN32
struct ifaddrs *ifap;
getifaddrs(&ifap);
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
@@ -1506,51 +1990,83 @@ inline std::string if2ip(const std::string &ifn) {
}
}
freeifaddrs(ifap);
-#endif
return std::string();
}
+#endif
inline socket_t create_client_socket(const char *host, int port,
- time_t timeout_sec,
- const std::string &intf) {
- return create_socket(
- host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
+ bool tcp_nodelay,
+ SocketOptions socket_options,
+ time_t timeout_sec, time_t timeout_usec,
+ const std::string &intf, std::atomic<Error> &error) {
+ auto sock = create_socket(
+ host, port, 0, tcp_nodelay, std::move(socket_options),
+ [&](socket_t sock, struct addrinfo &ai) -> bool {
if (!intf.empty()) {
+#ifdef USE_IF2IP
auto ip = if2ip(intf);
if (ip.empty()) { ip = intf; }
- if (!bind_ip_address(sock, ip.c_str())) { return false; }
+ if (!bind_ip_address(sock, ip.c_str())) {
+ error = Error::BindIPAddress;
+ return false;
+ }
+#endif
}
set_nonblocking(sock, true);
- auto ret = ::connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen));
+ auto ret =
+ ::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
+
if (ret < 0) {
if (is_connection_error() ||
- !wait_until_socket_is_ready(sock, timeout_sec, 0)) {
+ !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) {
close_socket(sock);
+ error = Error::Connection;
return false;
}
}
set_nonblocking(sock, false);
+ error = Error::Success;
return true;
});
-}
-inline std::string get_remote_addr(socket_t sock) {
- struct sockaddr_storage addr;
- socklen_t len = sizeof(addr);
+ if (sock != INVALID_SOCKET) {
+ error = Error::Success;
+ } else {
+ if (error == Error::Success) { error = Error::Connection; }
+ }
- if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
- std::array<char, NI_MAXHOST> ipstr{};
+ return sock;
+}
- if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
- ipstr.data(), static_cast<unsigned int>(ipstr.size()), nullptr, 0, NI_NUMERICHOST)) {
- return ipstr.data();
- }
+inline void get_remote_ip_and_port(const struct sockaddr_storage &addr,
+ socklen_t addr_len, std::string &ip,
+ int &port) {
+ if (addr.ss_family == AF_INET) {
+ port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
+ } else if (addr.ss_family == AF_INET6) {
+ port =
+ ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
}
- return std::string();
+ std::array<char, NI_MAXHOST> ipstr{};
+ if (!getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
+ ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
+ 0, NI_NUMERICHOST)) {
+ ip = ipstr.data();
+ }
+}
+
+inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+
+ if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
+ &addr_len)) {
+ get_remote_ip_and_port(addr, addr_len, ip, port);
+ }
}
inline const char *
@@ -1596,121 +2112,336 @@ find_content_type(const std::string &path,
inline const char *status_message(int status) {
switch (status) {
case 100: return "Continue";
+ case 101: return "Switching Protocol";
+ case 102: return "Processing";
+ case 103: return "Early Hints";
case 200: return "OK";
+ case 201: return "Created";
case 202: return "Accepted";
+ case 203: return "Non-Authoritative Information";
case 204: return "No Content";
+ case 205: return "Reset Content";
case 206: return "Partial Content";
+ case 207: return "Multi-Status";
+ case 208: return "Already Reported";
+ case 226: return "IM Used";
+ case 300: return "Multiple Choice";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
+ case 305: return "Use Proxy";
+ case 306: return "unused";
+ case 307: return "Temporary Redirect";
+ case 308: return "Permanent Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
+ case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
+ case 405: return "Method Not Allowed";
+ case 406: return "Not Acceptable";
+ case 407: return "Proxy Authentication Required";
+ case 408: return "Request Timeout";
+ case 409: return "Conflict";
+ case 410: return "Gone";
+ case 411: return "Length Required";
+ case 412: return "Precondition Failed";
case 413: return "Payload Too Large";
- case 414: return "Request-URI Too Long";
+ case 414: return "URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Range Not Satisfiable";
case 417: return "Expectation Failed";
+ case 418: return "I'm a teapot";
+ case 421: return "Misdirected Request";
+ case 422: return "Unprocessable Entity";
+ case 423: return "Locked";
+ case 424: return "Failed Dependency";
+ case 425: return "Too Early";
+ case 426: return "Upgrade Required";
+ case 428: return "Precondition Required";
+ case 429: return "Too Many Requests";
+ case 431: return "Request Header Fields Too Large";
+ case 451: return "Unavailable For Legal Reasons";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
+ case 504: return "Gateway Timeout";
+ case 505: return "HTTP Version Not Supported";
+ case 506: return "Variant Also Negotiates";
+ case 507: return "Insufficient Storage";
+ case 508: return "Loop Detected";
+ case 510: return "Not Extended";
+ case 511: return "Network Authentication Required";
default:
case 500: return "Internal Server Error";
}
}
-#ifdef CPPHTTPLIB_ZLIB_SUPPORT
-inline bool can_compress(const std::string &content_type) {
- return !content_type.find("text/") || content_type == "image/svg+xml" ||
+inline bool can_compress_content_type(const std::string &content_type) {
+ return (!content_type.find("text/") && content_type != "text/event-stream") ||
+ content_type == "image/svg+xml" ||
content_type == "application/javascript" ||
content_type == "application/json" ||
content_type == "application/xml" ||
content_type == "application/xhtml+xml";
}
-inline bool compress(std::string &content) {
- z_stream strm;
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
+enum class EncodingType { None = 0, Gzip, Brotli };
- auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
- Z_DEFAULT_STRATEGY);
- if (ret != Z_OK) { return false; }
+inline EncodingType encoding_type(const Request &req, const Response &res) {
+ auto ret =
+ detail::can_compress_content_type(res.get_header_value("Content-Type"));
+ if (!ret) { return EncodingType::None; }
- strm.avail_in = content.size();
- strm.next_in =
- const_cast<Bytef *>(reinterpret_cast<const Bytef *>(content.data()));
+ const auto &s = req.get_header_value("Accept-Encoding");
+ (void)(s);
- std::string compressed;
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ // TODO: 'Accept-Encoding' has br, not br;q=0
+ ret = s.find("br") != std::string::npos;
+ if (ret) { return EncodingType::Brotli; }
+#endif
- std::array<char, 16384> buff{};
- do {
- strm.avail_out = buff.size();
- strm.next_out = reinterpret_cast<Bytef *>(buff.data());
- ret = deflate(&strm, Z_FINISH);
- assert(ret != Z_STREAM_ERROR);
- compressed.append(buff.data(), buff.size() - strm.avail_out);
- } while (strm.avail_out == 0);
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
+ ret = s.find("gzip") != std::string::npos;
+ if (ret) { return EncodingType::Gzip; }
+#endif
- assert(ret == Z_STREAM_END);
- assert(strm.avail_in == 0);
+ return EncodingType::None;
+}
- content.swap(compressed);
+class compressor {
+public:
+ virtual ~compressor(){};
- deflateEnd(&strm);
- return true;
-}
+ typedef std::function<bool(const char *data, size_t data_len)> Callback;
+ virtual bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) = 0;
+};
class decompressor {
public:
- decompressor() {
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
+ virtual ~decompressor() {}
+
+ virtual bool is_valid() const = 0;
+
+ typedef std::function<bool(const char *data, size_t data_len)> Callback;
+ virtual bool decompress(const char *data, size_t data_length,
+ Callback callback) = 0;
+};
+
+class nocompressor : public compressor {
+public:
+ ~nocompressor(){};
+
+ bool compress(const char *data, size_t data_length, bool /*last*/,
+ Callback callback) override {
+ if (!data_length) { return true; }
+ return callback(data, data_length);
+ }
+};
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+class gzip_compressor : public compressor {
+public:
+ gzip_compressor() {
+ std::memset(&strm_, 0, sizeof(strm_));
+ strm_.zalloc = Z_NULL;
+ strm_.zfree = Z_NULL;
+ strm_.opaque = Z_NULL;
+
+ is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
+ Z_DEFAULT_STRATEGY) == Z_OK;
+ }
+
+ ~gzip_compressor() { deflateEnd(&strm_); }
+
+ bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) override {
+ assert(is_valid_);
+
+ auto flush = last ? Z_FINISH : Z_NO_FLUSH;
+
+ strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
+ strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
+
+ int ret = Z_OK;
+
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ do {
+ strm_.avail_out = buff.size();
+ strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
+
+ ret = deflate(&strm_, flush);
+ assert(ret != Z_STREAM_ERROR);
+
+ if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
+ return false;
+ }
+ } while (strm_.avail_out == 0);
+
+ assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK));
+ assert(strm_.avail_in == 0);
+ return true;
+ }
+
+private:
+ bool is_valid_ = false;
+ z_stream strm_;
+};
+
+class gzip_decompressor : public decompressor {
+public:
+ gzip_decompressor() {
+ std::memset(&strm_, 0, sizeof(strm_));
+ strm_.zalloc = Z_NULL;
+ strm_.zfree = Z_NULL;
+ strm_.opaque = Z_NULL;
// 15 is the value of wbits, which should be at the maximum possible value
- // to ensure that any gzip stream can be decoded. The offset of 16 specifies
- // that the stream to decompress will be formatted with a gzip wrapper.
- is_valid_ = inflateInit2(&strm, 16 + 15) == Z_OK;
+ // to ensure that any gzip stream can be decoded. The offset of 32 specifies
+ // that the stream type should be automatically detected either gzip or
+ // deflate.
+ is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
}
- ~decompressor() { inflateEnd(&strm); }
+ ~gzip_decompressor() { inflateEnd(&strm_); }
- bool is_valid() const { return is_valid_; }
+ bool is_valid() const override { return is_valid_; }
+
+ bool decompress(const char *data, size_t data_length,
+ Callback callback) override {
+ assert(is_valid_);
- template <typename T>
- bool decompress(const char *data, size_t data_length, T callback) {
int ret = Z_OK;
- strm.avail_in = data_length;
- strm.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
+ strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
+ strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
- std::array<char, 16384> buff{};
- do {
- strm.avail_out = buff.size();
- strm.next_out = reinterpret_cast<Bytef *>(buff.data());
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ while (strm_.avail_in > 0) {
+ strm_.avail_out = buff.size();
+ strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
- ret = inflate(&strm, Z_NO_FLUSH);
+ ret = inflate(&strm_, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
- case Z_MEM_ERROR: inflateEnd(&strm); return false;
+ case Z_MEM_ERROR: inflateEnd(&strm_); return false;
}
- if (!callback(buff.data(), buff.size() - strm.avail_out)) {
+ if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
return false;
}
- } while (strm.avail_out == 0);
+ }
return ret == Z_OK || ret == Z_STREAM_END;
}
private:
- bool is_valid_;
- z_stream strm;
+ bool is_valid_ = false;
+ z_stream strm_;
+};
+#endif
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+class brotli_compressor : public compressor {
+public:
+ brotli_compressor() {
+ state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
+ }
+
+ ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); }
+
+ bool compress(const char *data, size_t data_length, bool last,
+ Callback callback) override {
+ std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+
+ auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
+ auto available_in = data_length;
+ auto next_in = reinterpret_cast<const uint8_t *>(data);
+
+ for (;;) {
+ if (last) {
+ if (BrotliEncoderIsFinished(state_)) { break; }
+ } else {
+ if (!available_in) { break; }
+ }
+
+ auto available_out = buff.size();
+ auto next_out = buff.data();
+
+ if (!BrotliEncoderCompressStream(state_, operation, &available_in,
+ &next_in, &available_out, &next_out,
+ nullptr)) {
+ return false;
+ }
+
+ auto output_bytes = buff.size() - available_out;
+ if (output_bytes) {
+ callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
+ }
+ }
+
+ return true;
+ }
+
+private:
+ BrotliEncoderState *state_ = nullptr;
+};
+
+class brotli_decompressor : public decompressor {
+public:
+ brotli_decompressor() {
+ decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
+ decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
+ : BROTLI_DECODER_RESULT_ERROR;
+ }
+
+ ~brotli_decompressor() {
+ if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
+ }
+
+ bool is_valid() const override { return decoder_s; }
+
+ bool decompress(const char *data, size_t data_length,
+ Callback callback) override {
+ if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+ decoder_r == BROTLI_DECODER_RESULT_ERROR) {
+ return 0;
+ }
+
+ const uint8_t *next_in = (const uint8_t *)data;
+ size_t avail_in = data_length;
+ size_t total_out;
+
+ decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
+
+ std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+ while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
+ char *next_out = buff.data();
+ size_t avail_out = buff.size();
+
+ decoder_r = BrotliDecoderDecompressStream(
+ decoder_s, &avail_in, &next_in, &avail_out,
+ reinterpret_cast<uint8_t **>(&next_out), &total_out);
+
+ if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
+
+ if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
+ }
+
+ return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+ decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
+ }
+
+private:
+ BrotliDecoderResult decoder_r;
+ BrotliDecoderState *decoder_s = nullptr;
};
#endif
@@ -1720,21 +2451,60 @@ inline bool has_header(const Headers &headers, const char *key) {
inline const char *get_header_value(const Headers &headers, const char *key,
size_t id = 0, const char *def = nullptr) {
- auto it = headers.find(key);
- std::advance(it, id);
- if (it != headers.end()) { return it->second.c_str(); }
+ auto rng = headers.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) { return it->second.c_str(); }
return def;
}
-inline uint64_t get_header_value_uint64(const Headers &headers, const char *key,
- int def = 0) {
- auto it = headers.find(key);
- if (it != headers.end()) {
+template <typename T>
+inline T get_header_value(const Headers & /*headers*/, const char * /*key*/,
+ size_t /*id*/ = 0, uint64_t /*def*/ = 0) {}
+
+template <>
+inline uint64_t get_header_value<uint64_t>(const Headers &headers,
+ const char *key, size_t id,
+ uint64_t def) {
+ auto rng = headers.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) {
return std::strtoull(it->second.data(), nullptr, 10);
}
return def;
}
+template <typename T>
+inline bool parse_header(const char *beg, const char *end, T fn) {
+ // Skip trailing spaces and tabs.
+ while (beg < end && is_space_or_tab(end[-1])) {
+ end--;
+ }
+
+ auto p = beg;
+ while (p < end && *p != ':') {
+ p++;
+ }
+
+ if (p == end) { return false; }
+
+ auto key_end = p;
+
+ if (*p++ != ':') { return false; }
+
+ while (p < end && is_space_or_tab(*p)) {
+ p++;
+ }
+
+ if (p < end) {
+ fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
+ return true;
+ }
+
+ return false;
+}
+
inline bool read_headers(Stream &strm, Headers &headers) {
const auto bufsiz = 2048;
char buf[bufsiz];
@@ -1751,42 +2521,31 @@ inline bool read_headers(Stream &strm, Headers &headers) {
continue; // Skip invalid line.
}
- // Skip trailing spaces and tabs.
+ // Exclude CRLF
auto end = line_reader.ptr() + line_reader.size() - 2;
- while (line_reader.ptr() < end && (end[-1] == ' ' || end[-1] == '\t')) {
- end--;
- }
- // Horizontal tab and ' ' are considered whitespace and are ignored when on
- // the left or right side of the header value:
- // - https://stackoverflow.com/questions/50179659/
- // - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
- static const std::regex re(R"((.+?):[\t ]*(.+))");
-
- std::cmatch m;
- if (std::regex_match(line_reader.ptr(), end, m, re)) {
- auto key = std::string(m[1]);
- auto val = std::string(m[2]);
- headers.emplace(key, val);
- }
+ parse_header(line_reader.ptr(), end,
+ [&](std::string &&key, std::string &&val) {
+ headers.emplace(std::move(key), std::move(val));
+ });
}
return true;
}
inline bool read_content_with_length(Stream &strm, uint64_t len,
- Progress progress, ContentReceiver out) {
+ Progress progress,
+ ContentReceiverWithProgress out) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
uint64_t r = 0;
while (r < len) {
auto read_len = static_cast<size_t>(len - r);
- auto n = strm.read(buf, std::min(read_len, CPPHTTPLIB_RECV_BUFSIZ));
+ auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
if (n <= 0) { return false; }
- if (!out(buf, n)) { return false; }
-
- r += n;
+ if (!out(buf, static_cast<size_t>(n), r, len)) { return false; }
+ r += static_cast<uint64_t>(n);
if (progress) {
if (!progress(r, len)) { return false; }
@@ -1801,14 +2560,16 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) {
uint64_t r = 0;
while (r < len) {
auto read_len = static_cast<size_t>(len - r);
- auto n = strm.read(buf, std::min(read_len, CPPHTTPLIB_RECV_BUFSIZ));
+ auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
if (n <= 0) { return; }
- r += n;
+ r += static_cast<uint64_t>(n);
}
}
-inline bool read_content_without_length(Stream &strm, ContentReceiver out) {
+inline bool read_content_without_length(Stream &strm,
+ ContentReceiverWithProgress out) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
+ uint64_t r = 0;
for (;;) {
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
if (n < 0) {
@@ -1816,13 +2577,16 @@ inline bool read_content_without_length(Stream &strm, ContentReceiver out) {
} else if (n == 0) {
return true;
}
- if (!out(buf, n)) { return false; }
+
+ if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
+ r += static_cast<uint64_t>(n);
}
return true;
}
-inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
+inline bool read_content_chunked(Stream &strm,
+ ContentReceiverWithProgress out) {
const auto bufsiz = 16;
char buf[bufsiz];
@@ -1830,9 +2594,17 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
if (!line_reader.getline()) { return false; }
- auto chunk_len = std::stoi(line_reader.ptr(), 0, 16);
+ unsigned long chunk_len;
+ while (true) {
+ char *end_ptr;
+
+ chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
+
+ if (end_ptr == line_reader.ptr()) { return false; }
+ if (chunk_len == ULONG_MAX) { return false; }
+
+ if (chunk_len == 0) { break; }
- while (chunk_len > 0) {
if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
return false;
}
@@ -1842,8 +2614,6 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
if (strcmp(line_reader.ptr(), "\r\n")) { break; }
if (!line_reader.getline()) { return false; }
-
- chunk_len = std::stoi(line_reader.ptr(), 0, 16);
}
if (chunk_len == 0) {
@@ -1860,62 +2630,91 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) {
"chunked");
}
-template <typename T>
-bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
- Progress progress, ContentReceiver receiver) {
-
- ContentReceiver out = [&](const char *buf, size_t n) {
- return receiver(buf, n);
- };
+template <typename T, typename U>
+bool prepare_content_receiver(T &x, int &status,
+ ContentReceiverWithProgress receiver,
+ bool decompress, U callback) {
+ if (decompress) {
+ std::string encoding = x.get_header_value("Content-Encoding");
+ std::unique_ptr<decompressor> decompressor;
+ if (encoding.find("gzip") != std::string::npos ||
+ encoding.find("deflate") != std::string::npos) {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
- decompressor decompressor;
-
- if (!decompressor.is_valid()) {
- status = 500;
- return false;
- }
-
- if (x.get_header_value("Content-Encoding") == "gzip") {
- out = [&](const char *buf, size_t n) {
- return decompressor.decompress(
- buf, n, [&](const char *buf, size_t n) { return receiver(buf, n); });
- };
- }
+ decompressor = detail::make_unique<gzip_decompressor>();
#else
- if (x.get_header_value("Content-Encoding") == "gzip") {
- status = 415;
- return false;
- }
+ status = 415;
+ return false;
#endif
+ } else if (encoding.find("br") != std::string::npos) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ decompressor = detail::make_unique<brotli_decompressor>();
+#else
+ status = 415;
+ return false;
+#endif
+ }
- auto ret = true;
- auto exceed_payload_max_length = false;
-
- if (is_chunked_transfer_encoding(x.headers)) {
- ret = read_content_chunked(strm, out);
- } else if (!has_header(x.headers, "Content-Length")) {
- ret = read_content_without_length(strm, out);
- } else {
- auto len = get_header_value_uint64(x.headers, "Content-Length", 0);
- if (len > payload_max_length) {
- exceed_payload_max_length = true;
- skip_content_with_length(strm, len);
- ret = false;
- } else if (len > 0) {
- ret = read_content_with_length(strm, len, progress, out);
+ if (decompressor) {
+ if (decompressor->is_valid()) {
+ ContentReceiverWithProgress out = [&](const char *buf, size_t n,
+ uint64_t off, uint64_t len) {
+ return decompressor->decompress(buf, n,
+ [&](const char *buf, size_t n) {
+ return receiver(buf, n, off, len);
+ });
+ };
+ return callback(std::move(out));
+ } else {
+ status = 500;
+ return false;
+ }
}
}
- if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+ ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off,
+ uint64_t len) {
+ return receiver(buf, n, off, len);
+ };
+ return callback(std::move(out));
+}
- return ret;
+template <typename T>
+bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
+ Progress progress, ContentReceiverWithProgress receiver,
+ bool decompress) {
+ return prepare_content_receiver(
+ x, status, std::move(receiver), decompress,
+ [&](const ContentReceiverWithProgress &out) {
+ auto ret = true;
+ auto exceed_payload_max_length = false;
+
+ if (is_chunked_transfer_encoding(x.headers)) {
+ ret = read_content_chunked(strm, out);
+ } else if (!has_header(x.headers, "Content-Length")) {
+ ret = read_content_without_length(strm, out);
+ } else {
+ auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
+ if (len > payload_max_length) {
+ exceed_payload_max_length = true;
+ skip_content_with_length(strm, len);
+ ret = false;
+ } else if (len > 0) {
+ ret = read_content_with_length(strm, len, std::move(progress), out);
+ }
+ }
+
+ if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+ return ret;
+ });
}
template <typename T>
-inline int write_headers(Stream &strm, const T &info, const Headers &headers) {
- auto write_len = 0;
+inline ssize_t write_headers(Stream &strm, const T &info,
+ const Headers &headers) {
+ ssize_t write_len = 0;
for (const auto &x : info.headers) {
+ if (x.first == "EXCEPTION_WHAT") { continue; }
auto len =
strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
if (len < 0) { return len; }
@@ -1933,57 +2732,150 @@ inline int write_headers(Stream &strm, const T &info, const Headers &headers) {
return write_len;
}
+inline bool write_data(Stream &strm, const char *d, size_t l) {
+ size_t offset = 0;
+ while (offset < l) {
+ auto length = strm.write(d + offset, l - offset);
+ if (length < 0) { return false; }
+ offset += static_cast<size_t>(length);
+ }
+ return true;
+}
+
+template <typename T>
inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
- size_t offset, size_t length) {
+ size_t offset, size_t length, T is_shutting_down) {
size_t begin_offset = offset;
size_t end_offset = offset + length;
- while (offset < end_offset) {
- ssize_t written_length = 0;
+ auto ok = true;
+ DataSink data_sink;
- DataSink data_sink;
- data_sink.write = [&](const char *d, size_t l) {
+ data_sink.write = [&](const char *d, size_t l) {
+ if (ok) {
offset += l;
- written_length = strm.write(d, l);
- };
- data_sink.done = [&](void) { written_length = -1; };
- data_sink.is_writable = [&](void) { return strm.is_writable(); };
+ if (!write_data(strm, d, l)) { ok = false; }
+ }
+ };
+
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
- content_provider(offset, end_offset - offset, data_sink);
- if (written_length < 0) { return written_length; }
+ while (offset < end_offset && !is_shutting_down()) {
+ if (!content_provider(offset, end_offset - offset, data_sink)) {
+ return -1;
+ }
+ if (!ok) { return -1; }
}
+
return static_cast<ssize_t>(offset - begin_offset);
}
template <typename T>
+inline ssize_t write_content_without_length(Stream &strm,
+ ContentProvider content_provider,
+ T is_shutting_down) {
+ size_t offset = 0;
+ auto data_available = true;
+ auto ok = true;
+ DataSink data_sink;
+
+ data_sink.write = [&](const char *d, size_t l) {
+ if (ok) {
+ offset += l;
+ if (!write_data(strm, d, l)) { ok = false; }
+ }
+ };
+
+ data_sink.done = [&](void) { data_available = false; };
+
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+
+ while (data_available && !is_shutting_down()) {
+ if (!content_provider(offset, 0, data_sink)) { return -1; }
+ if (!ok) { return -1; }
+ }
+
+ return static_cast<ssize_t>(offset);
+}
+
+template <typename T, typename U>
inline ssize_t write_content_chunked(Stream &strm,
ContentProvider content_provider,
- T is_shutting_down) {
+ T is_shutting_down, U &compressor) {
size_t offset = 0;
auto data_available = true;
ssize_t total_written_length = 0;
- while (data_available && !is_shutting_down()) {
- ssize_t written_length = 0;
+ auto ok = true;
+ DataSink data_sink;
+
+ data_sink.write = [&](const char *d, size_t l) {
+ if (!ok) { return; }
+
+ data_available = l > 0;
+ offset += l;
+
+ std::string payload;
+ if (!compressor.compress(d, l, false,
+ [&](const char *data, size_t data_len) {
+ payload.append(data, data_len);
+ return true;
+ })) {
+ ok = false;
+ return;
+ }
- DataSink data_sink;
- data_sink.write = [&](const char *d, size_t l) {
- data_available = l > 0;
- offset += l;
+ if (!payload.empty()) {
+ // Emit chunked response header and footer for each chunk
+ auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
+ if (write_data(strm, chunk.data(), chunk.size())) {
+ total_written_length += chunk.size();
+ } else {
+ ok = false;
+ return;
+ }
+ }
+ };
+
+ data_sink.done = [&](void) {
+ if (!ok) { return; }
+ data_available = false;
+
+ std::string payload;
+ if (!compressor.compress(nullptr, 0, true,
+ [&](const char *data, size_t data_len) {
+ payload.append(data, data_len);
+ return true;
+ })) {
+ ok = false;
+ return;
+ }
+
+ if (!payload.empty()) {
// Emit chunked response header and footer for each chunk
- auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
- written_length = strm.write(chunk);
- };
- data_sink.done = [&](void) {
- data_available = false;
- written_length = strm.write("0\r\n\r\n");
- };
- data_sink.is_writable = [&](void) { return strm.is_writable(); };
+ auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
+ if (write_data(strm, chunk.data(), chunk.size())) {
+ total_written_length += chunk.size();
+ } else {
+ ok = false;
+ return;
+ }
+ }
+
+ static const std::string done_marker("0\r\n\r\n");
+ if (write_data(strm, done_marker.data(), done_marker.size())) {
+ total_written_length += done_marker.size();
+ } else {
+ ok = false;
+ }
+ };
- content_provider(offset, 0, data_sink);
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
- if (written_length < 0) { return written_length; }
- total_written_length += written_length;
+ while (data_available && !is_shutting_down()) {
+ if (!content_provider(offset, 0, data_sink)) { return -1; }
+ if (!ok) { return -1; }
}
+
return total_written_length;
}
@@ -1994,6 +2886,12 @@ inline bool redirect(T &cli, const Request &req, Response &res,
new_req.path = path;
new_req.redirect_count -= 1;
+ if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
+ new_req.method = "GET";
+ new_req.body.clear();
+ new_req.headers.clear();
+ }
+
Response new_res;
auto ret = cli.send(new_req, new_res);
@@ -2001,75 +2899,20 @@ inline bool redirect(T &cli, const Request &req, Response &res,
return ret;
}
-inline std::string encode_url(const std::string &s) {
- std::string result;
-
- for (auto i = 0; s[i]; i++) {
- switch (s[i]) {
- case ' ': result += "%20"; break;
- case '+': result += "%2B"; break;
- case '\r': result += "%0D"; break;
- case '\n': result += "%0A"; break;
- case '\'': result += "%27"; break;
- case ',': result += "%2C"; break;
- // case ':': result += "%3A"; break; // ok? probably...
- case ';': result += "%3B"; break;
- default:
- auto c = static_cast<uint8_t>(s[i]);
- if (c >= 0x80) {
- result += '%';
- char hex[4];
- size_t len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
- assert(len == 2);
- result.append(hex, len);
- } else {
- result += s[i];
- }
- break;
- }
- }
-
- return result;
-}
-
-inline std::string decode_url(const std::string &s) {
- std::string result;
+inline std::string params_to_query_str(const Params &params) {
+ std::string query;
- for (size_t i = 0; i < s.size(); i++) {
- if (s[i] == '%' && i + 1 < s.size()) {
- if (s[i + 1] == 'u') {
- int val = 0;
- if (from_hex_to_i(s, i + 2, 4, val)) {
- // 4 digits Unicode codes
- char buff[4];
- size_t len = to_utf8(val, buff);
- if (len > 0) { result.append(buff, len); }
- i += 5; // 'u0000'
- } else {
- result += s[i];
- }
- } else {
- int val = 0;
- if (from_hex_to_i(s, i + 1, 2, val)) {
- // 2 digits hex codes
- result += static_cast<char>(val);
- i += 2; // '00'
- } else {
- result += s[i];
- }
- }
- } else if (s[i] == '+') {
- result += ' ';
- } else {
- result += s[i];
- }
+ for (auto it = params.begin(); it != params.end(); ++it) {
+ if (it != params.begin()) { query += "&"; }
+ query += it->first;
+ query += "=";
+ query += encode_url(it->second);
}
-
- return result;
+ return query;
}
inline void parse_query_text(const std::string &s, Params &params) {
- split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) {
+ split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
std::string key;
std::string val;
split(b, e, '=', [&](const char *b2, const char *e2) {
@@ -2079,7 +2922,10 @@ inline void parse_query_text(const std::string &s, Params &params) {
val.assign(b2, e2);
}
});
- params.emplace(key, decode_url(val));
+
+ if (!key.empty()) {
+ params.emplace(decode_url(key, true), decode_url(val, true));
+ }
});
}
@@ -2087,17 +2933,20 @@ inline bool parse_multipart_boundary(const std::string &content_type,
std::string &boundary) {
auto pos = content_type.find("boundary=");
if (pos == std::string::npos) { return false; }
-
boundary = content_type.substr(pos + 9);
- return true;
+ if (boundary.length() >= 2 && boundary.front() == '"' &&
+ boundary.back() == '"') {
+ boundary = boundary.substr(1, boundary.size() - 2);
+ }
+ return !boundary.empty();
}
-inline bool parse_range_header(const std::string &s, Ranges &ranges) {
+inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
std::smatch m;
if (std::regex_match(s, m, re_first_range)) {
- auto pos = m.position(1);
- auto len = m.length(1);
+ auto pos = static_cast<size_t>(m.position(1));
+ auto len = static_cast<size_t>(m.length(1));
bool all_valid_ranges = true;
split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
if (!all_valid_ranges) return;
@@ -2124,25 +2973,26 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) {
return all_valid_ranges;
}
return false;
-}
+} catch (...) { return false; }
class MultipartFormDataParser {
public:
- MultipartFormDataParser() {}
+ MultipartFormDataParser() = default;
- void set_boundary(const std::string &boundary) { boundary_ = boundary; }
+ void set_boundary(std::string &&boundary) { boundary_ = boundary; }
bool is_valid() const { return is_valid_; }
template <typename T, typename U>
- bool parse(const char *buf, size_t n, T content_callback, U header_callback) {
- static const std::regex re_content_type(R"(^Content-Type:\s*(.*?)\s*$)",
- std::regex_constants::icase);
+ bool parse(const char *buf, size_t n, const T &content_callback,
+ const U &header_callback) {
static const std::regex re_content_disposition(
"^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
"\"(.*?)\")?\\s*$",
std::regex_constants::icase);
+ static const std::string dash_ = "--";
+ static const std::string crlf_ = "\r\n";
buf_.append(buf, n); // TODO: performance improvement
@@ -2152,10 +3002,7 @@ public:
auto pattern = dash_ + boundary_ + crlf_;
if (pattern.size() > buf_.size()) { return true; }
auto pos = buf_.find(pattern);
- if (pos != 0) {
- is_done_ = true;
- return false;
- }
+ if (pos != 0) { return false; }
buf_.erase(0, pattern.size());
off_ += pattern.size();
state_ = 1;
@@ -2173,7 +3020,6 @@ public:
if (pos == 0) {
if (!header_callback(file_)) {
is_valid_ = false;
- is_done_ = false;
return false;
}
buf_.erase(0, crlf_.size());
@@ -2182,12 +3028,13 @@ public:
break;
}
- auto header = buf_.substr(0, pos);
- {
+ static const std::string header_name = "content-type:";
+ const auto header = buf_.substr(0, pos);
+ if (start_with(header, header_name)) {
+ file_.content_type = trim_copy(header.substr(header_name.size()));
+ } else {
std::smatch m;
- if (std::regex_match(header, m, re_content_type)) {
- file_.content_type = m[1];
- } else if (std::regex_match(header, m, re_content_disposition)) {
+ if (std::regex_match(header, m, re_content_disposition)) {
file_.name = m[1];
file_.filename = m[2];
}
@@ -2197,6 +3044,7 @@ public:
off_ += pos + crlf_.size();
pos = buf_.find(crlf_);
}
+ if (state_ != 3) { return true; }
break;
}
case 3: { // Body
@@ -2205,10 +3053,17 @@ public:
if (pattern.size() > buf_.size()) { return true; }
auto pos = buf_.find(pattern);
- if (pos == std::string::npos) { pos = buf_.size(); }
+ if (pos == std::string::npos) {
+ pos = buf_.size();
+ while (pos > 0) {
+ auto c = buf_[pos - 1];
+ if (c != '\r' && c != '\n' && c != '-') { break; }
+ pos--;
+ }
+ }
+
if (!content_callback(buf_.data(), pos)) {
is_valid_ = false;
- is_done_ = false;
return false;
}
@@ -2224,7 +3079,6 @@ public:
if (pos != std::string::npos) {
if (!content_callback(buf_.data(), pos)) {
is_valid_ = false;
- is_done_ = false;
return false;
}
@@ -2234,7 +3088,6 @@ public:
} else {
if (!content_callback(buf_.data(), pattern.size())) {
is_valid_ = false;
- is_done_ = false;
return false;
}
@@ -2246,20 +3099,19 @@ public:
}
case 4: { // Boundary
if (crlf_.size() > buf_.size()) { return true; }
- if (buf_.find(crlf_) == 0) {
+ if (buf_.compare(0, crlf_.size(), crlf_) == 0) {
buf_.erase(0, crlf_.size());
off_ += crlf_.size();
state_ = 1;
} else {
auto pattern = dash_ + crlf_;
if (pattern.size() > buf_.size()) { return true; }
- if (buf_.find(pattern) == 0) {
+ if (buf_.compare(0, pattern.size(), pattern) == 0) {
buf_.erase(0, pattern.size());
off_ += pattern.size();
is_valid_ = true;
state_ = 5;
} else {
- is_done_ = true;
return true;
}
}
@@ -2282,14 +3134,11 @@ private:
file_.content_type.clear();
}
- const std::string dash_ = "--";
- const std::string crlf_ = "\r\n";
std::string boundary_;
std::string buf_;
size_t state_ = 0;
- size_t is_valid_ = false;
- size_t is_done_ = false;
+ bool is_valid_ = false;
size_t off_ = 0;
MultipartFormData file_;
};
@@ -2308,8 +3157,13 @@ inline std::string make_multipart_data_boundary() {
static const char data[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ // std::random_device might actually be deterministic on some
+ // platforms, but due to lack of support in the c++ standard library,
+ // doing better requires either some ugly hacks or breaking portability.
std::random_device seed_gen;
- std::mt19937 engine(seed_gen());
+ // Request 128 bits of entropy for initialization
+ std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
+ std::mt19937 engine(seed_sequence);
std::string result = "--cpp-httplib-multipart-data-";
@@ -2329,12 +3183,14 @@ get_range_offset_and_length(const Request &req, size_t content_length,
return std::make_pair(0, content_length);
}
+ auto slen = static_cast<ssize_t>(content_length);
+
if (r.first == -1) {
- r.first = content_length - r.second;
- r.second = content_length - 1;
+ r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
+ r.second = slen - 1;
}
- if (r.second == -1) { r.second = content_length - 1; }
+ if (r.second == -1) { r.second = slen - 1; }
return std::make_pair(r.first, r.second - r.first + 1);
}
@@ -2420,16 +3276,19 @@ get_multipart_ranges_data_length(const Request &req, Response &res,
return data_length;
}
+template <typename T>
inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
Response &res,
const std::string &boundary,
- const std::string &content_type) {
+ const std::string &content_type,
+ T is_shutting_down) {
return process_multipart_ranges_data(
req, res, boundary, content_type,
[&](const std::string &token) { strm.write(token); },
[&](const char *token) { strm.write(token); },
[&](size_t offset, size_t length) {
- return write_content(strm, res.content_provider, offset, length) >= 0;
+ return write_content(strm, res.content_provider_, offset, length,
+ is_shutting_down) >= 0;
});
}
@@ -2438,20 +3297,31 @@ get_range_offset_and_length(const Request &req, const Response &res,
size_t index) {
auto r = req.ranges[index];
- if (r.second == -1) { r.second = res.content_length - 1; }
+ if (r.second == -1) {
+ r.second = static_cast<ssize_t>(res.content_length_) - 1;
+ }
return std::make_pair(r.first, r.second - r.first + 1);
}
inline bool expect_content(const Request &req) {
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
- req.method == "PRI") {
+ req.method == "PRI" || req.method == "DELETE") {
return true;
}
// TODO: check if Content-Length is set
return false;
}
+inline bool has_crlf(const char *s) {
+ auto p = s;
+ while (*p) {
+ if (*p == '\r' || *p == '\n') { return true; }
+ p++;
+ }
+ return false;
+}
+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
template <typename CTX, typename Init, typename Update, typename Final>
inline std::string message_digest(const std::string &s, Init init,
@@ -2489,6 +3359,33 @@ inline std::string SHA_512(const std::string &s) {
#endif
#ifdef _WIN32
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+// NOTE: This code came up with the following stackoverflow post:
+// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
+inline bool load_system_certs_on_windows(X509_STORE *store) {
+ auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
+
+ if (!hStore) { return false; }
+
+ PCCERT_CONTEXT pContext = NULL;
+ while (pContext = CertEnumCertificatesInStore(hStore, pContext)) {
+ auto encoded_cert =
+ static_cast<const unsigned char *>(pContext->pbCertEncoded);
+
+ auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
+ if (x509) {
+ X509_STORE_add_cert(store, x509);
+ X509_free(x509);
+ }
+ }
+
+ CertFreeCertificateContext(pContext);
+ CertCloseStore(hStore, 0);
+
+ return true;
+}
+#endif
+
class WSInit {
public:
WSInit() {
@@ -2502,31 +3399,6 @@ public:
static WSInit wsinit_;
#endif
-} // namespace detail
-
-// Header utilities
-inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
- std::string field = "bytes=";
- auto i = 0;
- for (auto r : ranges) {
- if (i != 0) { field += ", "; }
- if (r.first != -1) { field += std::to_string(r.first); }
- field += '-';
- if (r.second != -1) { field += std::to_string(r.second); }
- i++;
- }
- return std::make_pair("Range", field);
-}
-
-inline std::pair<std::string, std::string>
-make_basic_authentication_header(const std::string &username,
- const std::string &password,
- bool is_proxy = false) {
- auto field = "Basic " + detail::base64_encode(username + ":" + password);
- auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
- return std::make_pair(key, field);
-}
-
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline std::pair<std::string, std::string> make_digest_authentication_header(
const Request &req, const std::map<std::string, std::string> &auth,
@@ -2566,17 +3438,18 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
":" + qop + ":" + H(A2));
}
- auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
- "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
- "\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc +
- "\", cnonce=\"" + cnonce + "\", response=\"" + response + "\"";
+ auto field = "Digest username=\"" + username + "\", realm=\"" +
+ auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
+ "\", uri=\"" + req.path + "\", algorithm=" + algo +
+ ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce +
+ "\", response=\"" + response + "\"";
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
return std::make_pair(key, field);
}
#endif
-inline bool parse_www_authenticate(const httplib::Response &res,
+inline bool parse_www_authenticate(const Response &res,
std::map<std::string, std::string> &auth,
bool is_proxy) {
auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
@@ -2593,9 +3466,13 @@ inline bool parse_www_authenticate(const httplib::Response &res,
auto beg = std::sregex_iterator(s.begin(), s.end(), re);
for (auto i = beg; i != std::sregex_iterator(); ++i) {
auto m = *i;
- auto key = s.substr(m.position(1), m.length(1));
- auto val = m.length(2) > 0 ? s.substr(m.position(2), m.length(2))
- : s.substr(m.position(3), m.length(3));
+ auto key = s.substr(static_cast<size_t>(m.position(1)),
+ static_cast<size_t>(m.length(1)));
+ auto val = m.length(2) > 0
+ ? s.substr(static_cast<size_t>(m.position(2)),
+ static_cast<size_t>(m.length(2)))
+ : s.substr(static_cast<size_t>(m.position(3)),
+ static_cast<size_t>(m.length(3)));
auth[key] = val;
}
return true;
@@ -2612,13 +3489,60 @@ inline std::string random_string(size_t length) {
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const size_t max_index = (sizeof(charset) - 1);
- return charset[rand() % max_index];
+ return charset[static_cast<size_t>(rand()) % max_index];
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
return str;
}
+class ContentProviderAdapter {
+public:
+ explicit ContentProviderAdapter(
+ ContentProviderWithoutLength &&content_provider)
+ : content_provider_(content_provider) {}
+
+ bool operator()(size_t offset, size_t, DataSink &sink) {
+ return content_provider_(offset, sink);
+ }
+
+private:
+ ContentProviderWithoutLength content_provider_;
+};
+
+} // namespace detail
+
+// Header utilities
+inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
+ std::string field = "bytes=";
+ auto i = 0;
+ for (auto r : ranges) {
+ if (i != 0) { field += ", "; }
+ if (r.first != -1) { field += std::to_string(r.first); }
+ field += '-';
+ if (r.second != -1) { field += std::to_string(r.second); }
+ i++;
+ }
+ return std::make_pair("Range", std::move(field));
+}
+
+inline std::pair<std::string, std::string>
+make_basic_authentication_header(const std::string &username,
+ const std::string &password,
+ bool is_proxy = false) {
+ auto field = "Basic " + detail::base64_encode(username + ":" + password);
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+ return std::make_pair(key, std::move(field));
+}
+
+inline std::pair<std::string, std::string>
+make_bearer_token_authentication_header(const std::string &token,
+ bool is_proxy = false) {
+ auto field = "Bearer " + token;
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+ return std::make_pair(key, std::move(field));
+}
+
// Request implementation
inline bool Request::has_header(const char *key) const {
return detail::has_header(headers, key);
@@ -2628,17 +3552,26 @@ inline std::string Request::get_header_value(const char *key, size_t id) const {
return detail::get_header_value(headers, key, id, "");
}
+template <typename T>
+inline T Request::get_header_value(const char *key, size_t id) const {
+ return detail::get_header_value<T>(headers, key, id, 0);
+}
+
inline size_t Request::get_header_value_count(const char *key) const {
auto r = headers.equal_range(key);
- return std::distance(r.first, r.second);
+ return static_cast<size_t>(std::distance(r.first, r.second));
}
inline void Request::set_header(const char *key, const char *val) {
- headers.emplace(key, val);
+ if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+ headers.emplace(key, val);
+ }
}
inline void Request::set_header(const char *key, const std::string &val) {
- headers.emplace(key, val);
+ if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
+ headers.emplace(key, val);
+ }
}
inline bool Request::has_param(const char *key) const {
@@ -2646,15 +3579,16 @@ inline bool Request::has_param(const char *key) const {
}
inline std::string Request::get_param_value(const char *key, size_t id) const {
- auto it = params.find(key);
- std::advance(it, id);
- if (it != params.end()) { return it->second; }
+ auto rng = params.equal_range(key);
+ auto it = rng.first;
+ std::advance(it, static_cast<ssize_t>(id));
+ if (it != rng.second) { return it->second; }
return std::string();
}
inline size_t Request::get_param_value_count(const char *key) const {
auto r = params.equal_range(key);
- return std::distance(r.first, r.second);
+ return static_cast<size_t>(std::distance(r.first, r.second));
}
inline bool Request::is_multipart_form_data() const {
@@ -2682,22 +3616,41 @@ inline std::string Response::get_header_value(const char *key,
return detail::get_header_value(headers, key, id, "");
}
+template <typename T>
+inline T Response::get_header_value(const char *key, size_t id) const {
+ return detail::get_header_value<T>(headers, key, id, 0);
+}
+
inline size_t Response::get_header_value_count(const char *key) const {
auto r = headers.equal_range(key);
- return std::distance(r.first, r.second);
+ return static_cast<size_t>(std::distance(r.first, r.second));
}
inline void Response::set_header(const char *key, const char *val) {
- headers.emplace(key, val);
+ if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+ headers.emplace(key, val);
+ }
}
inline void Response::set_header(const char *key, const std::string &val) {
- headers.emplace(key, val);
+ if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
+ headers.emplace(key, val);
+ }
+}
+
+inline void Response::set_redirect(const char *url, int stat) {
+ if (!detail::has_crlf(url)) {
+ set_header("Location", url);
+ if (300 <= stat && stat < 400) {
+ this->status = stat;
+ } else {
+ this->status = 302;
+ }
+ }
}
-inline void Response::set_redirect(const char *url) {
- set_header("Location", url);
- status = 302;
+inline void Response::set_redirect(const std::string &url, int stat) {
+ set_redirect(url.c_str(), stat);
}
inline void Response::set_content(const char *s, size_t n,
@@ -2706,62 +3659,79 @@ inline void Response::set_content(const char *s, size_t n,
set_header("Content-Type", content_type);
}
-inline void Response::set_content(const std::string &s,
- const char *content_type) {
- body = s;
+inline void Response::set_content(std::string s, const char *content_type) {
+ body = std::move(s);
set_header("Content-Type", content_type);
}
-inline void Response::set_content_provider(
- size_t in_length,
- std::function<void(size_t offset, size_t length, DataSink &sink)> provider,
- std::function<void()> resource_releaser) {
+inline void
+Response::set_content_provider(size_t in_length, const char *content_type,
+ ContentProvider provider,
+ const std::function<void()> &resource_releaser) {
assert(in_length > 0);
- content_length = in_length;
- content_provider = [provider](size_t offset, size_t length, DataSink &sink) {
- provider(offset, length, sink);
- };
- content_provider_resource_releaser = resource_releaser;
+ set_header("Content-Type", content_type);
+ content_length_ = in_length;
+ content_provider_ = std::move(provider);
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider = false;
+}
+
+inline void
+Response::set_content_provider(const char *content_type,
+ ContentProviderWithoutLength provider,
+ const std::function<void()> &resource_releaser) {
+ set_header("Content-Type", content_type);
+ content_length_ = 0;
+ content_provider_ = detail::ContentProviderAdapter(std::move(provider));
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider = false;
}
inline void Response::set_chunked_content_provider(
- std::function<void(size_t offset, DataSink &sink)> provider,
- std::function<void()> resource_releaser) {
- content_length = 0;
- content_provider = [provider](size_t offset, size_t, DataSink &sink) {
- provider(offset, sink);
- };
- content_provider_resource_releaser = resource_releaser;
+ const char *content_type, ContentProviderWithoutLength provider,
+ const std::function<void()> &resource_releaser) {
+ set_header("Content-Type", content_type);
+ content_length_ = 0;
+ content_provider_ = detail::ContentProviderAdapter(std::move(provider));
+ content_provider_resource_releaser_ = resource_releaser;
+ is_chunked_content_provider = true;
}
// Rstream implementation
-inline int Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); }
+inline ssize_t Stream::write(const char *ptr) {
+ return write(ptr, strlen(ptr));
+}
-inline int Stream::write(const std::string &s) {
+inline ssize_t Stream::write(const std::string &s) {
return write(s.data(), s.size());
}
template <typename... Args>
-inline int Stream::write_format(const char *fmt, const Args &... args) {
- std::array<char, 2048> buf;
+inline ssize_t Stream::write_format(const char *fmt, const Args &... args) {
+ const auto bufsiz = 2048;
+ std::array<char, bufsiz> buf;
#if defined(_MSC_VER) && _MSC_VER < 1900
- auto n = _snprintf_s(buf, bufsiz, buf.size() - 1, fmt, args...);
+ auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...);
#else
- auto n = snprintf(buf.data(), buf.size() - 1, fmt, args...);
+ auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
#endif
- if (n <= 0) { return n; }
+ if (sn <= 0) { return sn; }
- if (n >= static_cast<int>(buf.size()) - 1) {
+ auto n = static_cast<size_t>(sn);
+
+ if (n >= buf.size() - 1) {
std::vector<char> glowable_buf(buf.size());
- while (n >= static_cast<int>(glowable_buf.size() - 1)) {
+ while (n >= glowable_buf.size() - 1) {
glowable_buf.resize(glowable_buf.size() * 2);
#if defined(_MSC_VER) && _MSC_VER < 1900
- n = _snprintf_s(&glowable_buf[0], glowable_buf.size(),
- glowable_buf.size() - 1, fmt, args...);
+ n = static_cast<size_t>(_snprintf_s(&glowable_buf[0], glowable_buf.size(),
+ glowable_buf.size() - 1, fmt,
+ args...));
#else
- n = snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...);
+ n = static_cast<size_t>(
+ snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...));
#endif
}
return write(&glowable_buf[0], n);
@@ -2774,32 +3744,53 @@ namespace detail {
// Socket stream implementation
inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
- time_t read_timeout_usec)
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec)
: sock_(sock), read_timeout_sec_(read_timeout_sec),
- read_timeout_usec_(read_timeout_usec) {}
+ read_timeout_usec_(read_timeout_usec),
+ write_timeout_sec_(write_timeout_sec),
+ write_timeout_usec_(write_timeout_usec) {}
inline SocketStream::~SocketStream() {}
inline bool SocketStream::is_readable() const {
- return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+ return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
}
inline bool SocketStream::is_writable() const {
- return detail::select_write(sock_, 0, 0) > 0;
+ return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
}
-inline int SocketStream::read(char *ptr, size_t size) {
- if (is_readable()) { return recv(sock_, ptr, static_cast<int>(size), 0); }
- return -1;
+inline ssize_t SocketStream::read(char *ptr, size_t size) {
+ if (!is_readable()) { return -1; }
+
+#ifdef _WIN32
+ if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
+ return -1;
+ }
+ return recv(sock_, ptr, static_cast<int>(size), 0);
+#else
+ return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); });
+#endif
}
-inline int SocketStream::write(const char *ptr, size_t size) {
- if (is_writable()) { return send(sock_, ptr, static_cast<int>(size), 0); }
- return -1;
+inline ssize_t SocketStream::write(const char *ptr, size_t size) {
+ if (!is_writable()) { return -1; }
+
+#ifdef _WIN32
+ if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
+ return -1;
+ }
+ return send(sock_, ptr, static_cast<int>(size), 0);
+#else
+ return handle_EINTR([&]() { return send(sock_, ptr, size, 0); });
+#endif
}
-inline std::string SocketStream::get_remote_addr() const {
- return detail::get_remote_addr(sock_);
+inline void SocketStream::get_remote_ip_and_port(std::string &ip,
+ int &port) const {
+ return detail::get_remote_ip_and_port(sock_, ip, port);
}
// Buffer stream implementation
@@ -2807,22 +3798,23 @@ inline bool BufferStream::is_readable() const { return true; }
inline bool BufferStream::is_writable() const { return true; }
-inline int BufferStream::read(char *ptr, size_t size) {
-#if defined(_MSC_VER) && _MSC_VER < 1900
- int len_read = static_cast<int>(buffer._Copy_s(ptr, size, size, position));
+inline ssize_t BufferStream::read(char *ptr, size_t size) {
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+ auto len_read = buffer._Copy_s(ptr, size, size, position);
#else
- int len_read = static_cast<int>(buffer.copy(ptr, size, position));
+ auto len_read = buffer.copy(ptr, size, position);
#endif
- position += len_read;
- return len_read;
+ position += static_cast<size_t>(len_read);
+ return static_cast<ssize_t>(len_read);
}
-inline int BufferStream::write(const char *ptr, size_t size) {
+inline ssize_t BufferStream::write(const char *ptr, size_t size) {
buffer.append(ptr, size);
- return static_cast<int>(size);
+ return static_cast<ssize_t>(size);
}
-inline std::string BufferStream::get_remote_addr() const { return ""; }
+inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
+ int & /*port*/) const {}
inline const std::string &BufferStream::get_buffer() const { return buffer; }
@@ -2830,67 +3822,77 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; }
// HTTP server implementation
inline Server::Server()
- : keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT),
- read_timeout_sec_(CPPHTTPLIB_READ_TIMEOUT_SECOND),
- read_timeout_usec_(CPPHTTPLIB_READ_TIMEOUT_USECOND),
- payload_max_length_(CPPHTTPLIB_PAYLOAD_MAX_LENGTH), is_running_(false),
- svr_sock_(INVALID_SOCKET) {
+ : new_task_queue(
+ [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
+ svr_sock_(INVALID_SOCKET), is_running_(false) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
#endif
- new_task_queue = [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); };
}
inline Server::~Server() {}
inline Server &Server::Get(const char *pattern, Handler handler) {
- get_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ get_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Post(const char *pattern, Handler handler) {
- post_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ post_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Post(const char *pattern,
HandlerWithContentReader handler) {
post_handlers_for_content_reader_.push_back(
- std::make_pair(std::regex(pattern), handler));
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Put(const char *pattern, Handler handler) {
- put_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ put_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Put(const char *pattern,
HandlerWithContentReader handler) {
put_handlers_for_content_reader_.push_back(
- std::make_pair(std::regex(pattern), handler));
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Patch(const char *pattern, Handler handler) {
- patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ patch_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Patch(const char *pattern,
HandlerWithContentReader handler) {
patch_handlers_for_content_reader_.push_back(
- std::make_pair(std::regex(pattern), handler));
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Delete(const char *pattern, Handler handler) {
- delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ delete_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
+ return *this;
+}
+
+inline Server &Server::Delete(const char *pattern,
+ HandlerWithContentReader handler) {
+ delete_handlers_for_content_reader_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
inline Server &Server::Options(const char *pattern, Handler handler) {
- options_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
+ options_handlers_.push_back(
+ std::make_pair(std::regex(pattern), std::move(handler)));
return *this;
}
@@ -2898,11 +3900,12 @@ inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
return set_mount_point(mount_point, dir);
}
-inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
+inline bool Server::set_mount_point(const char *mount_point, const char *dir,
+ Headers headers) {
if (detail::is_dir(dir)) {
std::string mnt = mount_point ? mount_point : "/";
if (!mnt.empty() && mnt[0] == '/') {
- base_dirs_.emplace_back(mnt, dir);
+ base_dirs_.push_back({mnt, dir, std::move(headers)});
return true;
}
}
@@ -2911,7 +3914,7 @@ inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
inline bool Server::remove_mount_point(const char *mount_point) {
for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
- if (it->first == mount_point) {
+ if (it->mount_point == mount_point) {
base_dirs_.erase(it);
return true;
}
@@ -2932,6 +3935,12 @@ inline void Server::set_error_handler(Handler handler) {
error_handler_ = std::move(handler);
}
+inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
+
+inline void Server::set_socket_options(SocketOptions socket_options) {
+ socket_options_ = std::move(socket_options);
+}
+
inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); }
inline void
@@ -2943,11 +3952,25 @@ inline void Server::set_keep_alive_max_count(size_t count) {
keep_alive_max_count_ = count;
}
+inline void Server::set_keep_alive_timeout(time_t sec) {
+ keep_alive_timeout_sec_ = sec;
+}
+
inline void Server::set_read_timeout(time_t sec, time_t usec) {
read_timeout_sec_ = sec;
read_timeout_usec_ = usec;
}
+inline void Server::set_write_timeout(time_t sec, time_t usec) {
+ write_timeout_sec_ = sec;
+ write_timeout_usec_ = usec;
+}
+
+inline void Server::set_idle_interval(time_t sec, time_t usec) {
+ idle_interval_sec_ = sec;
+ idle_interval_usec_ = usec;
+}
+
inline void Server::set_payload_max_length(size_t length) {
payload_max_length_ = length;
}
@@ -2987,7 +4010,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
req.version = std::string(m[5]);
req.method = std::string(m[1]);
req.target = std::string(m[2]);
- req.path = detail::decode_url(m[3]);
+ req.path = detail::decode_url(m[3], false);
// Parse query text
auto len = std::distance(m[4].first, m[4].second);
@@ -2999,7 +4022,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
return false;
}
-inline bool Server::write_response(Stream &strm, bool last_connection,
+inline bool Server::write_response(Stream &strm, bool close_connection,
const Request &req, Response &res) {
assert(res.status != -1);
@@ -3014,16 +4037,17 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
}
// Headers
- if (last_connection || req.get_header_value("Connection") == "close") {
+ if (close_connection || req.get_header_value("Connection") == "close") {
res.set_header("Connection", "close");
- }
-
- if (!last_connection && req.get_header_value("Connection") == "Keep-Alive") {
- res.set_header("Connection", "Keep-Alive");
+ } else {
+ std::stringstream ss;
+ ss << "timeout=" << keep_alive_timeout_sec_
+ << ", max=" << keep_alive_max_count_;
+ res.set_header("Keep-Alive", ss.str());
}
if (!res.has_header("Content-Type") &&
- (!res.body.empty() || res.content_length > 0)) {
+ (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) {
res.set_header("Content-Type", "text/plain");
}
@@ -3047,18 +4071,20 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
"multipart/byteranges; boundary=" + boundary);
}
+ auto type = detail::encoding_type(req, res);
+
if (res.body.empty()) {
- if (res.content_length > 0) {
+ if (res.content_length_ > 0) {
size_t length = 0;
if (req.ranges.empty()) {
- length = res.content_length;
+ length = res.content_length_;
} else if (req.ranges.size() == 1) {
auto offsets =
- detail::get_range_offset_and_length(req, res.content_length, 0);
+ detail::get_range_offset_and_length(req, res.content_length_, 0);
auto offset = offsets.first;
length = offsets.second;
auto content_range = detail::make_content_range_header_field(
- offset, length, res.content_length);
+ offset, length, res.content_length_);
res.set_header("Content-Range", content_range);
} else {
length = detail::get_multipart_ranges_data_length(req, res, boundary,
@@ -3066,8 +4092,15 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
}
res.set_header("Content-Length", std::to_string(length));
} else {
- if (res.content_provider) {
- res.set_header("Transfer-Encoding", "chunked");
+ if (res.content_provider_) {
+ if (res.is_chunked_content_provider) {
+ res.set_header("Transfer-Encoding", "chunked");
+ if (type == detail::EncodingType::Gzip) {
+ res.set_header("Content-Encoding", "gzip");
+ } else if (type == detail::EncodingType::Brotli) {
+ res.set_header("Content-Encoding", "br");
+ }
+ }
} else {
res.set_header("Content-Length", "0");
}
@@ -3089,16 +4122,35 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
detail::make_multipart_ranges_data(req, res, boundary, content_type);
}
+ if (type != detail::EncodingType::None) {
+ std::unique_ptr<detail::compressor> compressor;
+
+ if (type == detail::EncodingType::Gzip) {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
- // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
- const auto &encodings = req.get_header_value("Accept-Encoding");
- if (encodings.find("gzip") != std::string::npos &&
- detail::can_compress(res.get_header_value("Content-Type"))) {
- if (detail::compress(res.body)) {
+ compressor = detail::make_unique<detail::gzip_compressor>();
res.set_header("Content-Encoding", "gzip");
+#endif
+ } else if (type == detail::EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ compressor = detail::make_unique<detail::brotli_compressor>();
+ res.set_header("Content-Encoding", "brotli");
+#endif
+ }
+
+ if (compressor) {
+ std::string compressed;
+
+ if (!compressor->compress(res.body.data(), res.body.size(), true,
+ [&](const char *data, size_t data_len) {
+ compressed.append(data, data_len);
+ return true;
+ })) {
+ return false;
+ }
+
+ res.body.swap(compressed);
}
}
-#endif
auto length = std::to_string(res.body.size());
res.set_header("Content-Length", length);
@@ -3111,13 +4163,14 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
strm.write(data.data(), data.size());
// Body
+ auto ret = true;
if (req.method != "HEAD") {
if (!res.body.empty()) {
- if (!strm.write(res.body)) { return false; }
- } else if (res.content_provider) {
+ if (!strm.write(res.body)) { ret = false; }
+ } else if (res.content_provider_) {
if (!write_content_with_provider(strm, req, res, boundary,
content_type)) {
- return false;
+ ret = false;
}
}
}
@@ -3125,119 +4178,158 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
// Log
if (logger_) { logger_(req, res); }
- return true;
+ return ret;
}
inline bool
Server::write_content_with_provider(Stream &strm, const Request &req,
Response &res, const std::string &boundary,
const std::string &content_type) {
- if (res.content_length) {
+ auto is_shutting_down = [this]() {
+ return this->svr_sock_ == INVALID_SOCKET;
+ };
+
+ if (res.content_length_ > 0) {
if (req.ranges.empty()) {
- if (detail::write_content(strm, res.content_provider, 0,
- res.content_length) < 0) {
+ if (detail::write_content(strm, res.content_provider_, 0,
+ res.content_length_, is_shutting_down) < 0) {
return false;
}
} else if (req.ranges.size() == 1) {
auto offsets =
- detail::get_range_offset_and_length(req, res.content_length, 0);
+ detail::get_range_offset_and_length(req, res.content_length_, 0);
auto offset = offsets.first;
auto length = offsets.second;
- if (detail::write_content(strm, res.content_provider, offset, length) <
- 0) {
+ if (detail::write_content(strm, res.content_provider_, offset, length,
+ is_shutting_down) < 0) {
return false;
}
} else {
- if (!detail::write_multipart_ranges_data(strm, req, res, boundary,
- content_type)) {
+ if (!detail::write_multipart_ranges_data(
+ strm, req, res, boundary, content_type, is_shutting_down)) {
return false;
}
}
} else {
- auto is_shutting_down = [this]() {
- return this->svr_sock_ == INVALID_SOCKET;
- };
- if (detail::write_content_chunked(strm, res.content_provider,
- is_shutting_down) < 0) {
- return false;
+ if (res.is_chunked_content_provider) {
+ auto type = detail::encoding_type(req, res);
+
+ std::unique_ptr<detail::compressor> compressor;
+ if (type == detail::EncodingType::Gzip) {
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+ compressor = detail::make_unique<detail::gzip_compressor>();
+#endif
+ } else if (type == detail::EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+ compressor = detail::make_unique<detail::brotli_compressor>();
+#endif
+ } else {
+ compressor = detail::make_unique<detail::nocompressor>();
+ }
+ assert(compressor != nullptr);
+
+ if (detail::write_content_chunked(strm, res.content_provider_,
+ is_shutting_down, *compressor) < 0) {
+ return false;
+ }
+ } else {
+ if (detail::write_content_without_length(strm, res.content_provider_,
+ is_shutting_down) < 0) {
+ return false;
+ }
}
}
return true;
}
-inline bool Server::read_content(Stream &strm, bool last_connection,
- Request &req, Response &res) {
+inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
MultipartFormDataMap::iterator cur;
- auto ret = read_content_core(
- strm, last_connection, req, res,
- // Regular
- [&](const char *buf, size_t n) {
- if (req.body.size() + n > req.body.max_size()) { return false; }
- req.body.append(buf, n);
- return true;
- },
- // Multipart
- [&](const MultipartFormData &file) {
- cur = req.files.emplace(file.name, file);
- return true;
- },
- [&](const char *buf, size_t n) {
- auto &content = cur->second.content;
- if (content.size() + n > content.max_size()) { return false; }
- content.append(buf, n);
- return true;
- });
-
- const auto &content_type = req.get_header_value("Content-Type");
- if (!content_type.find("application/x-www-form-urlencoded")) {
- detail::parse_query_text(req.body, req.params);
+ if (read_content_core(
+ strm, req, res,
+ // Regular
+ [&](const char *buf, size_t n) {
+ if (req.body.size() + n > req.body.max_size()) { return false; }
+ req.body.append(buf, n);
+ return true;
+ },
+ // Multipart
+ [&](const MultipartFormData &file) {
+ cur = req.files.emplace(file.name, file);
+ return true;
+ },
+ [&](const char *buf, size_t n) {
+ auto &content = cur->second.content;
+ if (content.size() + n > content.max_size()) { return false; }
+ content.append(buf, n);
+ return true;
+ })) {
+ const auto &content_type = req.get_header_value("Content-Type");
+ if (!content_type.find("application/x-www-form-urlencoded")) {
+ detail::parse_query_text(req.body, req.params);
+ }
+ return true;
}
-
- return ret;
+ return false;
}
inline bool Server::read_content_with_content_receiver(
- Stream &strm, bool last_connection, Request &req, Response &res,
- ContentReceiver receiver, MultipartContentHeader multipart_header,
+ Stream &strm, Request &req, Response &res, ContentReceiver receiver,
+ MultipartContentHeader multipart_header,
ContentReceiver multipart_receiver) {
- return read_content_core(strm, last_connection, req, res, receiver,
- multipart_header, multipart_receiver);
+ return read_content_core(strm, req, res, std::move(receiver),
+ std::move(multipart_header),
+ std::move(multipart_receiver));
}
-inline bool Server::read_content_core(Stream &strm, bool last_connection,
- Request &req, Response &res,
+inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
ContentReceiver receiver,
MultipartContentHeader mulitpart_header,
ContentReceiver multipart_receiver) {
detail::MultipartFormDataParser multipart_form_data_parser;
- ContentReceiver out;
+ ContentReceiverWithProgress out;
if (req.is_multipart_form_data()) {
const auto &content_type = req.get_header_value("Content-Type");
std::string boundary;
if (!detail::parse_multipart_boundary(content_type, boundary)) {
res.status = 400;
- return write_response(strm, last_connection, req, res);
+ return false;
}
- multipart_form_data_parser.set_boundary(boundary);
- out = [&](const char *buf, size_t n) {
+ multipart_form_data_parser.set_boundary(std::move(boundary));
+ out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) {
+ /* For debug
+ size_t pos = 0;
+ while (pos < n) {
+ auto read_size = std::min<size_t>(1, n - pos);
+ auto ret = multipart_form_data_parser.parse(
+ buf + pos, read_size, multipart_receiver, mulitpart_header);
+ if (!ret) { return false; }
+ pos += read_size;
+ }
+ return true;
+ */
return multipart_form_data_parser.parse(buf, n, multipart_receiver,
mulitpart_header);
};
} else {
- out = receiver;
+ out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
+ uint64_t /*len*/) { return receiver(buf, n); };
}
- if (!detail::read_content(strm, req, payload_max_length_, res.status,
- Progress(), out)) {
- return write_response(strm, last_connection, req, res);
+ if (req.method == "DELETE" && !req.has_header("Content-Length")) {
+ return true;
+ }
+
+ if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
+ out, true)) {
+ return false;
}
if (req.is_multipart_form_data()) {
if (!multipart_form_data_parser.is_valid()) {
res.status = 400;
- return write_response(strm, last_connection, req, res);
+ return false;
}
}
@@ -3246,15 +4338,12 @@ inline bool Server::read_content_core(Stream &strm, bool last_connection,
inline bool Server::handle_file_request(Request &req, Response &res,
bool head) {
- for (const auto &kv : base_dirs_) {
- const auto &mount_point = kv.first;
- const auto &base_dir = kv.second;
-
+ for (const auto &entry : base_dirs_) {
// Prefix match
- if (!req.path.find(mount_point)) {
- std::string sub_path = "/" + req.path.substr(mount_point.size());
+ if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
+ std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
if (detail::is_valid_path(sub_path)) {
- auto path = base_dir + sub_path;
+ auto path = entry.base_dir + sub_path;
if (path.back() == '/') { path += "index.html"; }
if (detail::is_file(path)) {
@@ -3262,6 +4351,9 @@ inline bool Server::handle_file_request(Request &req, Response &res,
auto type =
detail::find_content_type(path, file_extension_and_mimetype_map_);
if (type) { res.set_header("Content-Type", type); }
+ for (const auto &kv : entry.headers) {
+ res.set_header(kv.first.c_str(), kv.second);
+ }
res.status = 200;
if (!head && file_request_handler_) {
file_request_handler_(req, res);
@@ -3274,40 +4366,39 @@ inline bool Server::handle_file_request(Request &req, Response &res,
return false;
}
-inline socket_t Server::create_server_socket(const char *host, int port,
- int socket_flags) const {
+inline socket_t
+Server::create_server_socket(const char *host, int port, int socket_flags,
+ SocketOptions socket_options) const {
return detail::create_socket(
- host, port,
+ host, port, socket_flags, tcp_nodelay_, std::move(socket_options),
[](socket_t sock, struct addrinfo &ai) -> bool {
- if (::bind(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen))) {
+ if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
return false;
}
if (::listen(sock, 5)) { // Listen through 5 channels
return false;
}
return true;
- },
- socket_flags);
+ });
}
inline int Server::bind_internal(const char *host, int port, int socket_flags) {
if (!is_valid()) { return -1; }
- svr_sock_ = create_server_socket(host, port, socket_flags);
+ svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
if (svr_sock_ == INVALID_SOCKET) { return -1; }
if (port == 0) {
- struct sockaddr_storage address;
- socklen_t len = sizeof(address);
- if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&address),
- &len) == -1) {
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+ if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
+ &addr_len) == -1) {
return -1;
}
- if (address.ss_family == AF_INET) {
- return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
- } else if (address.ss_family == AF_INET6) {
- return ntohs(
- reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
+ if (addr.ss_family == AF_INET) {
+ return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
+ } else if (addr.ss_family == AF_INET6) {
+ return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
} else {
return -1;
}
@@ -3323,18 +4414,19 @@ inline bool Server::listen_internal() {
{
std::unique_ptr<TaskQueue> task_queue(new_task_queue());
- for (;;) {
- if (svr_sock_ == INVALID_SOCKET) {
- // The server socket was closed by 'stop' method.
- break;
- }
-
- auto val = detail::select_read(svr_sock_, 0, 100000);
-
- if (val == 0) { // Timeout
- continue;
+ while (svr_sock_ != INVALID_SOCKET) {
+#ifndef _WIN32
+ if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
+#endif
+ auto val = detail::select_read(svr_sock_, idle_interval_sec_,
+ idle_interval_usec_);
+ if (val == 0) { // Timeout
+ task_queue->on_idle();
+ continue;
+ }
+#ifndef _WIN32
}
-
+#endif
socket_t sock = accept(svr_sock_, nullptr, nullptr);
if (sock == INVALID_SOCKET) {
@@ -3353,7 +4445,11 @@ inline bool Server::listen_internal() {
break;
}
+#if __cplusplus > 201703L
+ task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
+#else
task_queue->enqueue([=]() { process_and_close_socket(sock); });
+#endif
}
task_queue->shutdown();
@@ -3363,8 +4459,7 @@ inline bool Server::listen_internal() {
return ret;
}
-inline bool Server::routing(Request &req, Response &res, Stream &strm,
- bool last_connection) {
+inline bool Server::routing(Request &req, Response &res, Stream &strm) {
// File handler
bool is_head_request = req.method == "HEAD";
if ((req.method == "GET" || is_head_request) &&
@@ -3378,33 +4473,43 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm,
ContentReader reader(
[&](ContentReceiver receiver) {
return read_content_with_content_receiver(
- strm, last_connection, req, res, receiver, nullptr, nullptr);
+ strm, req, res, std::move(receiver), nullptr, nullptr);
},
[&](MultipartContentHeader header, ContentReceiver receiver) {
- return read_content_with_content_receiver(
- strm, last_connection, req, res, nullptr, header, receiver);
+ return read_content_with_content_receiver(strm, req, res, nullptr,
+ std::move(header),
+ std::move(receiver));
});
if (req.method == "POST") {
if (dispatch_request_for_content_reader(
- req, res, reader, post_handlers_for_content_reader_)) {
+ req, res, std::move(reader),
+ post_handlers_for_content_reader_)) {
return true;
}
} else if (req.method == "PUT") {
if (dispatch_request_for_content_reader(
- req, res, reader, put_handlers_for_content_reader_)) {
+ req, res, std::move(reader),
+ put_handlers_for_content_reader_)) {
return true;
}
} else if (req.method == "PATCH") {
if (dispatch_request_for_content_reader(
- req, res, reader, patch_handlers_for_content_reader_)) {
+ req, res, std::move(reader),
+ patch_handlers_for_content_reader_)) {
+ return true;
+ }
+ } else if (req.method == "DELETE") {
+ if (dispatch_request_for_content_reader(
+ req, res, std::move(reader),
+ delete_handlers_for_content_reader_)) {
return true;
}
}
}
// Read content into `req.body`
- if (!read_content(strm, last_connection, req, res)) { return false; }
+ if (!read_content(strm, req, res)) { return false; }
}
// Regular handler
@@ -3427,22 +4532,30 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm,
}
inline bool Server::dispatch_request(Request &req, Response &res,
- Handlers &handlers) {
- for (const auto &x : handlers) {
- const auto &pattern = x.first;
- const auto &handler = x.second;
-
- if (std::regex_match(req.path, req.matches, pattern)) {
- handler(req, res);
- return true;
+ const Handlers &handlers) {
+ try {
+ for (const auto &x : handlers) {
+ const auto &pattern = x.first;
+ const auto &handler = x.second;
+
+ if (std::regex_match(req.path, req.matches, pattern)) {
+ handler(req, res);
+ return true;
+ }
}
+ } catch (const std::exception &ex) {
+ res.status = 500;
+ res.set_header("EXCEPTION_WHAT", ex.what());
+ } catch (...) {
+ res.status = 500;
+ res.set_header("EXCEPTION_WHAT", "UNKNOWN");
}
return false;
}
inline bool Server::dispatch_request_for_content_reader(
Request &req, Response &res, ContentReader content_reader,
- HandlersForContentReader &handlers) {
+ const HandlersForContentReader &handlers) {
for (const auto &x : handlers) {
const auto &pattern = x.first;
const auto &handler = x.second;
@@ -3456,8 +4569,8 @@ inline bool Server::dispatch_request_for_content_reader(
}
inline bool
-Server::process_request(Stream &strm, bool last_connection,
- bool &connection_close,
+Server::process_request(Stream &strm, bool close_connection,
+ bool &connection_closed,
const std::function<void(Request &)> &setup_request) {
std::array<char, 2048> buf{};
@@ -3476,31 +4589,34 @@ Server::process_request(Stream &strm, bool last_connection,
Headers dummy;
detail::read_headers(strm, dummy);
res.status = 414;
- return write_response(strm, last_connection, req, res);
+ return write_response(strm, close_connection, req, res);
}
// Request line and headers
if (!parse_request_line(line_reader.ptr(), req) ||
!detail::read_headers(strm, req.headers)) {
res.status = 400;
- return write_response(strm, last_connection, req, res);
+ return write_response(strm, close_connection, req, res);
}
if (req.get_header_value("Connection") == "close") {
- connection_close = true;
+ connection_closed = true;
}
if (req.version == "HTTP/1.0" &&
req.get_header_value("Connection") != "Keep-Alive") {
- connection_close = true;
+ connection_closed = true;
}
- req.set_header("REMOTE_ADDR", strm.get_remote_addr());
+ strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
+ req.set_header("REMOTE_ADDR", req.remote_addr);
+ req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
if (req.has_header("Range")) {
const auto &range_header_value = req.get_header_value("Range");
if (!detail::parse_range_header(range_header_value, req.ranges)) {
- // TODO: error
+ res.status = 416;
+ return write_response(strm, close_connection, req, res);
}
}
@@ -3517,136 +4633,278 @@ Server::process_request(Stream &strm, bool last_connection,
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
detail::status_message(status));
break;
- default: return write_response(strm, last_connection, req, res);
+ default: return write_response(strm, close_connection, req, res);
}
}
// Rounting
- if (routing(req, res, strm, last_connection)) {
+ if (routing(req, res, strm)) {
if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
} else {
if (res.status == -1) { res.status = 404; }
}
- return write_response(strm, last_connection, req, res);
+ return write_response(strm, close_connection, req, res);
}
inline bool Server::is_valid() const { return true; }
inline bool Server::process_and_close_socket(socket_t sock) {
- return detail::process_and_close_socket(
- false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
- [this](Stream &strm, bool last_connection, bool &connection_close) {
- return process_request(strm, last_connection, connection_close,
+ auto ret = detail::process_server_socket(
+ sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_,
+ read_timeout_usec_, write_timeout_sec_, write_timeout_usec_,
+ [this](Stream &strm, bool close_connection, bool &connection_closed) {
+ return process_request(strm, close_connection, connection_closed,
nullptr);
});
+
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ return ret;
}
// HTTP client implementation
-inline Client::Client(const std::string &host, int port,
- const std::string &client_cert_path,
- const std::string &client_key_path)
- : host_(host), port_(port),
+inline ClientImpl::ClientImpl(const std::string &host)
+ : ClientImpl(host, 80, std::string(), std::string()) {}
+
+inline ClientImpl::ClientImpl(const std::string &host, int port)
+ : ClientImpl(host, port, std::string(), std::string()) {}
+
+inline ClientImpl::ClientImpl(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path)
+ : error_(Error::Success), host_(host), port_(port),
host_and_port_(host_ + ":" + std::to_string(port_)),
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
-inline Client::~Client() {}
+inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); }
+
+inline bool ClientImpl::is_valid() const { return true; }
-inline bool Client::is_valid() const { return true; }
+inline Error ClientImpl::get_last_error() const { return error_; }
-inline socket_t Client::create_client_socket() const {
- if (!proxy_host_.empty()) {
- return detail::create_client_socket(proxy_host_.c_str(), proxy_port_,
- timeout_sec_, interface_);
+inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
+ client_cert_path_ = rhs.client_cert_path_;
+ client_key_path_ = rhs.client_key_path_;
+ connection_timeout_sec_ = rhs.connection_timeout_sec_;
+ read_timeout_sec_ = rhs.read_timeout_sec_;
+ read_timeout_usec_ = rhs.read_timeout_usec_;
+ write_timeout_sec_ = rhs.write_timeout_sec_;
+ write_timeout_usec_ = rhs.write_timeout_usec_;
+ basic_auth_username_ = rhs.basic_auth_username_;
+ basic_auth_password_ = rhs.basic_auth_password_;
+ bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ digest_auth_username_ = rhs.digest_auth_username_;
+ digest_auth_password_ = rhs.digest_auth_password_;
+#endif
+ keep_alive_ = rhs.keep_alive_;
+ follow_location_ = rhs.follow_location_;
+ tcp_nodelay_ = rhs.tcp_nodelay_;
+ socket_options_ = rhs.socket_options_;
+ compress_ = rhs.compress_;
+ decompress_ = rhs.decompress_;
+ interface_ = rhs.interface_;
+ proxy_host_ = rhs.proxy_host_;
+ proxy_port_ = rhs.proxy_port_;
+ proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
+ proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
+ proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
+ proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
+#endif
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ server_certificate_verification_ = rhs.server_certificate_verification_;
+#endif
+ logger_ = rhs.logger_;
+}
+
+inline socket_t ClientImpl::create_client_socket() const {
+ if (!proxy_host_.empty() && proxy_port_ != -1) {
+ return detail::create_client_socket(
+ proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_,
+ connection_timeout_sec_, connection_timeout_usec_, interface_, error_);
}
- return detail::create_client_socket(host_.c_str(), port_, timeout_sec_,
- interface_);
+ return detail::create_client_socket(
+ host_.c_str(), port_, tcp_nodelay_, socket_options_,
+ connection_timeout_sec_, connection_timeout_usec_, interface_, error_);
+}
+
+inline bool ClientImpl::create_and_connect_socket(Socket &socket) {
+ auto sock = create_client_socket();
+ if (sock == INVALID_SOCKET) { return false; }
+ socket.sock = sock;
+ return true;
+}
+
+inline void ClientImpl::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
+ (void)socket;
+ (void)shutdown_gracefully;
+ //If there are any requests in flight from threads other than us, then it's
+ //a thread-unsafe race because individual ssl* objects are not thread-safe.
+ assert(socket_requests_in_flight_ == 0 ||
+ socket_requests_are_from_thread_ == std::this_thread::get_id());
+}
+
+inline void ClientImpl::shutdown_socket(Socket &socket) {
+ if (socket.sock == INVALID_SOCKET)
+ return;
+ detail::shutdown_socket(socket.sock);
+}
+
+inline void ClientImpl::close_socket(Socket &socket) {
+ // If there are requests in flight in another thread, usually closing
+ // the socket will be fine and they will simply receive an error when
+ // using the closed socket, but it is still a bug since rarely the OS
+ // may reassign the socket id to be used for a new socket, and then
+ // suddenly they will be operating on a live socket that is different
+ // than the one they intended!
+ assert(socket_requests_in_flight_ == 0 ||
+ socket_requests_are_from_thread_ == std::this_thread::get_id());
+ // It is also a bug if this happens while SSL is still active
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ assert(socket.ssl == nullptr);
+#endif
+ if (socket.sock == INVALID_SOCKET)
+ return;
+ detail::close_socket(socket.sock);
+ socket.sock = INVALID_SOCKET;
}
-inline bool Client::read_response_line(Stream &strm, Response &res) {
+inline void ClientImpl::lock_socket_and_shutdown_and_close() {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+}
+
+inline bool ClientImpl::read_response_line(Stream &strm, Response &res) {
std::array<char, 2048> buf;
detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
if (!line_reader.getline()) { return false; }
- const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n");
+ const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n");
std::cmatch m;
- if (std::regex_match(line_reader.ptr(), m, re)) {
+ if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
+ res.version = std::string(m[1]);
+ res.status = std::stoi(std::string(m[2]));
+ res.reason = std::string(m[3]);
+
+ // Ignore '100 Continue'
+ while (res.status == 100) {
+ if (!line_reader.getline()) { return false; } // CRLF
+ if (!line_reader.getline()) { return false; } // next response line
+
+ if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
res.version = std::string(m[1]);
res.status = std::stoi(std::string(m[2]));
+ res.reason = std::string(m[3]);
}
return true;
}
-inline bool Client::send(const Request &req, Response &res) {
- auto sock = create_client_socket();
- if (sock == INVALID_SOCKET) { return false; }
+inline bool ClientImpl::send(const Request &req, Response &res) {
+ std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
+
+ {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ // Set this to false immediately - if it ever gets set to true by the end of the
+ // request, we know another thread instructed us to close the socket.
+ socket_should_be_closed_when_request_is_done_ = false;
+
+ auto is_alive = false;
+ if (socket_.is_open()) {
+ is_alive = detail::select_write(socket_.sock, 0, 0) > 0;
+ if (!is_alive) {
+ // Attempt to avoid sigpipe by shutting down nongracefully if it seems like
+ // the other side has already closed the connection
+ // Also, there cannot be any requests in flight from other threads since we locked
+ // request_mutex_, so safe to close everything immediately
+ const bool shutdown_gracefully = false;
+ shutdown_ssl(socket_, shutdown_gracefully);
+ shutdown_socket(socket_);
+ close_socket(socket_);
+ }
+ }
+
+ if (!is_alive) {
+ if (!create_and_connect_socket(socket_)) { return false; }
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- if (is_ssl() && !proxy_host_.empty()) {
- bool error;
- if (!connect(sock, res, error)) { return error; }
- }
+ // TODO: refactoring
+ if (is_ssl()) {
+ auto &scli = static_cast<SSLClient &>(*this);
+ if (!proxy_host_.empty() && proxy_port_ != -1) {
+ bool success = false;
+ if (!scli.connect_with_proxy(socket_, res, success)) {
+ return success;
+ }
+ }
+
+ if (!scli.initialize_ssl(socket_)) { return false; }
+ }
#endif
+ }
- return process_and_close_socket(
- sock, 1, [&](Stream &strm, bool last_connection, bool &connection_close) {
- return handle_request(strm, req, res, last_connection,
- connection_close);
- });
-}
+ // Mark the current socket as being in use so that it cannot be closed by anyone
+ // else while this request is ongoing, even though we will be releasing the mutex.
+ if (socket_requests_in_flight_ > 1) {
+ assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
+ }
+ socket_requests_in_flight_ += 1;
+ socket_requests_are_from_thread_ = std::this_thread::get_id();
+ }
-inline bool Client::send(const std::vector<Request> &requests,
- std::vector<Response> &responses) {
- size_t i = 0;
- while (i < requests.size()) {
- auto sock = create_client_socket();
- if (sock == INVALID_SOCKET) { return false; }
+ auto close_connection = !keep_alive_;
+ auto ret = process_socket(socket_, [&](Stream &strm) {
+ return handle_request(strm, req, res, close_connection);
+ });
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- if (is_ssl() && !proxy_host_.empty()) {
- Response res;
- bool error;
- if (!connect(sock, res, error)) { return false; }
+ //Briefly lock mutex in order to mark that a request is no longer ongoing
+ {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ socket_requests_in_flight_ -= 1;
+ if (socket_requests_in_flight_ <= 0) {
+ assert(socket_requests_in_flight_ == 0);
+ socket_requests_are_from_thread_ = std::thread::id();
}
-#endif
- if (!process_and_close_socket(sock, requests.size() - i,
- [&](Stream &strm, bool last_connection,
- bool &connection_close) -> bool {
- auto &req = requests[i++];
- auto res = Response();
- auto ret = handle_request(strm, req, res,
- last_connection,
- connection_close);
- if (ret) {
- responses.emplace_back(std::move(res));
- }
- return ret;
- })) {
- return false;
+ if (socket_should_be_closed_when_request_is_done_ ||
+ close_connection ||
+ !ret ) {
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
}
}
- return true;
+ if (!ret) {
+ if (error_ == Error::Success) { error_ = Error::Unknown; }
+ }
+
+ return ret;
}
-inline bool Client::handle_request(Stream &strm, const Request &req,
- Response &res, bool last_connection,
- bool &connection_close) {
- if (req.path.empty()) { return false; }
+inline bool ClientImpl::handle_request(Stream &strm, const Request &req,
+ Response &res, bool close_connection) {
+ if (req.path.empty()) {
+ error_ = Error::Connection;
+ return false;
+ }
bool ret;
- if (!is_ssl() && !proxy_host_.empty()) {
+ if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
auto req2 = req;
req2.path = "http://" + host_and_port_ + req.path;
- ret = process_request(strm, req2, res, last_connection, connection_close);
+ ret = process_request(strm, req2, res, close_connection);
} else {
- ret = process_request(strm, req, res, last_connection, connection_close);
+ ret = process_request(strm, req, res, close_connection);
}
if (!ret) { return false; }
@@ -3656,7 +4914,8 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- if (res.status == 401 || res.status == 407) {
+ if ((res.status == 401 || res.status == 407) &&
+ req.authorization_count_ < 5) {
auto is_proxy = res.status == 407;
const auto &username =
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
@@ -3665,12 +4924,14 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
if (!username.empty() && !password.empty()) {
std::map<std::string, std::string> auth;
- if (parse_www_authenticate(res, auth, is_proxy)) {
+ if (detail::parse_www_authenticate(res, auth, is_proxy)) {
Request new_req = req;
- auto key = is_proxy ? "Proxy-Authorization" : "WWW-Authorization";
+ new_req.authorization_count_ += 1;
+ auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
new_req.headers.erase(key);
- new_req.headers.insert(make_digest_authentication_header(
- req, auth, 1, random_string(10), username, password, is_proxy));
+ new_req.headers.insert(detail::make_digest_authentication_header(
+ req, auth, new_req.authorization_count_, detail::random_string(10),
+ username, password, is_proxy));
Response new_res;
@@ -3684,102 +4945,64 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
return ret;
}
-#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline bool Client::connect(socket_t sock, Response &res, bool &error) {
- error = true;
- Response res2;
-
- if (!detail::process_socket(
- true, sock, 1, read_timeout_sec_, read_timeout_usec_,
- [&](Stream &strm, bool /*last_connection*/, bool &connection_close) {
- Request req2;
- req2.method = "CONNECT";
- req2.path = host_and_port_;
- return process_request(strm, req2, res2, false, connection_close);
- })) {
- detail::close_socket(sock);
- error = false;
+inline bool ClientImpl::redirect(const Request &req, Response &res) {
+ if (req.redirect_count == 0) {
+ error_ = Error::ExceedRedirectCount;
return false;
}
- if (res2.status == 407) {
- if (!proxy_digest_auth_username_.empty() &&
- !proxy_digest_auth_password_.empty()) {
- std::map<std::string, std::string> auth;
- if (parse_www_authenticate(res2, auth, true)) {
- Response res3;
- if (!detail::process_socket(
- true, sock, 1, read_timeout_sec_, read_timeout_usec_,
- [&](Stream &strm, bool /*last_connection*/,
- bool &connection_close) {
- Request req3;
- req3.method = "CONNECT";
- req3.path = host_and_port_;
- req3.headers.insert(make_digest_authentication_header(
- req3, auth, 1, random_string(10),
- proxy_digest_auth_username_, proxy_digest_auth_password_,
- true));
- return process_request(strm, req3, res3, false,
- connection_close);
- })) {
- detail::close_socket(sock);
- error = false;
- return false;
- }
- }
- } else {
- res = res2;
- return false;
- }
- }
-
- return true;
-}
-#endif
-
-inline bool Client::redirect(const Request &req, Response &res) {
- if (req.redirect_count == 0) { return false; }
-
- auto location = res.get_header_value("location");
+ auto location = detail::decode_url(res.get_header_value("location"), true);
if (location.empty()) { return false; }
const static std::regex re(
- R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
+ R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
std::smatch m;
- if (!regex_match(location, m, re)) { return false; }
+ if (!std::regex_match(location, m, re)) { return false; }
auto scheme = is_ssl() ? "https" : "http";
auto next_scheme = m[1].str();
auto next_host = m[2].str();
- auto next_path = m[3].str();
- if (next_scheme.empty()) { next_scheme = scheme; }
+ auto port_str = m[3].str();
+ auto next_path = m[4].str();
+
+ auto next_port = port_;
+ if (!port_str.empty()) {
+ next_port = std::stoi(port_str);
+ } else if (!next_scheme.empty()) {
+ next_port = next_scheme == "https" ? 443 : 80;
+ }
+
if (next_scheme.empty()) { next_scheme = scheme; }
if (next_host.empty()) { next_host = host_; }
if (next_path.empty()) { next_path = "/"; }
- if (next_scheme == scheme && next_host == host_) {
+ if (next_scheme == scheme && next_host == host_ && next_port == port_) {
return detail::redirect(*this, req, res, next_path);
} else {
if (next_scheme == "https") {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
- SSLClient cli(next_host.c_str());
+ SSLClient cli(next_host.c_str(), next_port);
cli.copy_settings(*this);
- return detail::redirect(cli, req, res, next_path);
+ auto ret = detail::redirect(cli, req, res, next_path);
+ if (!ret) { error_ = cli.get_last_error(); }
+ return ret;
#else
return false;
#endif
} else {
- Client cli(next_host.c_str());
+ ClientImpl cli(next_host.c_str(), next_port);
cli.copy_settings(*this);
- return detail::redirect(cli, req, res, next_path);
+ auto ret = detail::redirect(cli, req, res, next_path);
+ if (!ret) { error_ = cli.get_last_error(); }
+ return ret;
}
}
}
-inline bool Client::write_request(Stream &strm, const Request &req,
- bool last_connection) {
+inline bool ClientImpl::write_request(Stream &strm, const Request &req,
+ bool close_connection) {
detail::BufferStream bstrm;
// Request line
@@ -3789,7 +5012,7 @@ inline bool Client::write_request(Stream &strm, const Request &req,
// Additonal headers
Headers headers;
- if (last_connection) { headers.emplace("Connection", "close"); }
+ if (close_connection) { headers.emplace("Connection", "close"); }
if (!req.has_header("Host")) {
if (is_ssl()) {
@@ -3810,7 +5033,7 @@ inline bool Client::write_request(Stream &strm, const Request &req,
if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); }
if (!req.has_header("User-Agent")) {
- headers.emplace("User-Agent", "cpp-httplib/0.5");
+ headers.emplace("User-Agent", "cpp-httplib/0.7");
}
if (req.body.empty()) {
@@ -3818,7 +5041,10 @@ inline bool Client::write_request(Stream &strm, const Request &req,
auto length = std::to_string(req.content_length);
headers.emplace("Content-Length", length);
} else {
- headers.emplace("Content-Length", "0");
+ if (req.method == "POST" || req.method == "PUT" ||
+ req.method == "PATCH") {
+ headers.emplace("Content-Length", "0");
+ }
}
} else {
if (!req.has_header("Content-Type")) {
@@ -3831,7 +5057,7 @@ inline bool Client::write_request(Stream &strm, const Request &req,
}
}
- if (!basic_auth_username_.empty() && !basic_auth_password_.empty()) {
+ if (!basic_auth_password_.empty()) {
headers.insert(make_basic_authentication_header(
basic_auth_username_, basic_auth_password_, false));
}
@@ -3842,11 +5068,24 @@ inline bool Client::write_request(Stream &strm, const Request &req,
proxy_basic_auth_username_, proxy_basic_auth_password_, true));
}
+ if (!bearer_token_auth_token_.empty()) {
+ headers.insert(make_bearer_token_authentication_header(
+ bearer_token_auth_token_, false));
+ }
+
+ if (!proxy_bearer_token_auth_token_.empty()) {
+ headers.insert(make_bearer_token_authentication_header(
+ proxy_bearer_token_auth_token_, true));
+ }
+
detail::write_headers(bstrm, req, headers);
// Flush buffer
auto &data = bstrm.get_buffer();
- strm.write(data.data(), data.size());
+ if (!detail::write_data(strm, data.data(), data.size())) {
+ error_ = Error::Write;
+ return false;
+ }
// Body
if (req.body.empty()) {
@@ -3854,282 +5093,370 @@ inline bool Client::write_request(Stream &strm, const Request &req,
size_t offset = 0;
size_t end_offset = req.content_length;
+ bool ok = true;
+
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
- auto written_length = strm.write(d, l);
- offset += written_length;
+ if (ok) {
+ if (detail::write_data(strm, d, l)) {
+ offset += l;
+ } else {
+ ok = false;
+ }
+ }
};
- data_sink.is_writable = [&](void) { return strm.is_writable(); };
+ data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (offset < end_offset) {
- req.content_provider(offset, end_offset - offset, data_sink);
+ if (!req.content_provider(offset, end_offset - offset, data_sink)) {
+ error_ = Error::Canceled;
+ return false;
+ }
+ if (!ok) {
+ error_ = Error::Write;
+ return false;
+ }
}
}
} else {
- strm.write(req.body);
+ return detail::write_data(strm, req.body.data(), req.body.size());
}
return true;
}
-inline std::shared_ptr<Response> Client::send_with_content_provider(
+inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
const char *method, const char *path, const Headers &headers,
const std::string &body, size_t content_length,
ContentProvider content_provider, const char *content_type) {
+
Request req;
req.method = method;
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.path = path;
- req.headers.emplace("Content-Type", content_type);
+ if (content_type) { req.headers.emplace("Content-Type", content_type); }
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
if (compress_) {
+ detail::gzip_compressor compressor;
+
if (content_provider) {
+ auto ok = true;
size_t offset = 0;
DataSink data_sink;
data_sink.write = [&](const char *data, size_t data_len) {
- req.body.append(data, data_len);
- offset += data_len;
+ if (ok) {
+ auto last = offset + data_len == content_length;
+
+ auto ret = compressor.compress(
+ data, data_len, last, [&](const char *data, size_t data_len) {
+ req.body.append(data, data_len);
+ return true;
+ });
+
+ if (ret) {
+ offset += data_len;
+ } else {
+ ok = false;
+ }
+ }
};
- data_sink.is_writable = [&](void) { return true; };
+ data_sink.is_writable = [&](void) { return ok && true; };
- while (offset < content_length) {
- content_provider(offset, content_length - offset, data_sink);
+ while (ok && offset < content_length) {
+ if (!content_provider(offset, content_length - offset, data_sink)) {
+ error_ = Error::Canceled;
+ return nullptr;
+ }
}
} else {
- req.body = body;
+ if (!compressor.compress(body.data(), body.size(), true,
+ [&](const char *data, size_t data_len) {
+ req.body.append(data, data_len);
+ return true;
+ })) {
+ return nullptr;
+ }
}
- if (!detail::compress(req.body)) { return nullptr; }
req.headers.emplace("Content-Encoding", "gzip");
} else
#endif
{
if (content_provider) {
req.content_length = content_length;
- req.content_provider = content_provider;
+ req.content_provider = std::move(content_provider);
} else {
req.body = body;
}
}
- auto res = std::make_shared<Response>();
+ auto res = detail::make_unique<Response>();
- return send(req, *res) ? res : nullptr;
+ return send(req, *res) ? std::move(res) : nullptr;
}
-inline bool Client::process_request(Stream &strm, const Request &req,
- Response &res, bool last_connection,
- bool &connection_close) {
+inline bool ClientImpl::process_request(Stream &strm, const Request &req,
+ Response &res, bool close_connection) {
// Send request
- if (!write_request(strm, req, last_connection)) { return false; }
+ if (!write_request(strm, req, close_connection)) { return false; }
// Receive response and headers
if (!read_response_line(strm, res) ||
!detail::read_headers(strm, res.headers)) {
+ error_ = Error::Read;
return false;
}
- if (res.get_header_value("Connection") == "close" ||
- res.version == "HTTP/1.0") {
- connection_close = true;
- }
-
if (req.response_handler) {
- if (!req.response_handler(res)) { return false; }
+ if (!req.response_handler(res)) {
+ error_ = Error::Canceled;
+ return false;
+ }
}
// Body
if (req.method != "HEAD" && req.method != "CONNECT") {
- ContentReceiver out = [&](const char *buf, size_t n) {
- if (res.body.size() + n > res.body.max_size()) { return false; }
- res.body.append(buf, n);
- return true;
+ auto out =
+ req.content_receiver
+ ? static_cast<ContentReceiverWithProgress>(
+ [&](const char *buf, size_t n, uint64_t off, uint64_t len) {
+ auto ret = req.content_receiver(buf, n, off, len);
+ if (!ret) { error_ = Error::Canceled; }
+ return ret;
+ })
+ : static_cast<ContentReceiverWithProgress>(
+ [&](const char *buf, size_t n, uint64_t /*off*/,
+ uint64_t /*len*/) {
+ if (res.body.size() + n > res.body.max_size()) {
+ return false;
+ }
+ res.body.append(buf, n);
+ return true;
+ });
+
+ auto progress = [&](uint64_t current, uint64_t total) {
+ if (!req.progress) { return true; }
+ auto ret = req.progress(current, total);
+ if (!ret) { error_ = Error::Canceled; }
+ return ret;
};
- if (req.content_receiver) {
- out = [&](const char *buf, size_t n) {
- return req.content_receiver(buf, n);
- };
- }
-
int dummy_status;
- if (!detail::read_content(strm, res, std::numeric_limits<size_t>::max(),
- dummy_status, req.progress, out)) {
+ if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
+ dummy_status, std::move(progress), std::move(out),
+ decompress_)) {
+ if (error_ != Error::Canceled) { error_ = Error::Read; }
return false;
}
}
+ if (res.get_header_value("Connection") == "close" ||
+ (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
+ // TODO this requires a not-entirely-obvious chain of calls to be correct
+ // for this to be safe. Maybe a code refactor (such as moving this out to
+ // the send function and getting rid of the recursiveness of the mutex)
+ // could make this more obvious.
+
+ // This is safe to call because process_request is only called by handle_request
+ // which is only called by send, which locks the request mutex during the process.
+ // It would be a bug to call it from a different thread since it's a thread-safety
+ // issue to do these things to the socket if another thread is using the socket.
+ lock_socket_and_shutdown_and_close();
+ }
+
// Log
if (logger_) { logger_(req, res); }
return true;
}
-inline bool Client::process_and_close_socket(
- socket_t sock, size_t request_count,
- std::function<bool(Stream &strm, bool last_connection,
- bool &connection_close)>
- callback) {
- request_count = std::min(request_count, keep_alive_max_count_);
- return detail::process_and_close_socket(true, sock, request_count,
- read_timeout_sec_, read_timeout_usec_,
- callback);
+inline bool
+ClientImpl::process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) {
+ return detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_, std::move(callback));
}
-inline bool Client::is_ssl() const { return false; }
+inline bool ClientImpl::is_ssl() const { return false; }
-inline std::shared_ptr<Response> Client::Get(const char *path) {
+inline Result ClientImpl::Get(const char *path) {
return Get(path, Headers(), Progress());
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- Progress progress) {
+inline Result ClientImpl::Get(const char *path, Progress progress) {
return Get(path, Headers(), std::move(progress));
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers) {
+inline Result ClientImpl::Get(const char *path, const Headers &headers) {
return Get(path, headers, Progress());
}
-inline std::shared_ptr<Response>
-Client::Get(const char *path, const Headers &headers, Progress progress) {
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ Progress progress) {
Request req;
req.method = "GET";
req.path = path;
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.progress = std::move(progress);
- auto res = std::make_shared<Response>();
- return send(req, *res) ? res : nullptr;
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- ContentReceiver content_receiver) {
- return Get(path, Headers(), nullptr, std::move(content_receiver), Progress());
+inline Result ClientImpl::Get(const char *path,
+ ContentReceiver content_receiver) {
+ return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr);
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- ContentReceiver content_receiver,
- Progress progress) {
+inline Result ClientImpl::Get(const char *path,
+ ContentReceiver content_receiver,
+ Progress progress) {
return Get(path, Headers(), nullptr, std::move(content_receiver),
std::move(progress));
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers,
- ContentReceiver content_receiver) {
- return Get(path, headers, nullptr, std::move(content_receiver), Progress());
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver) {
+ return Get(path, headers, nullptr, std::move(content_receiver), nullptr);
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers,
- ContentReceiver content_receiver,
- Progress progress) {
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver,
+ Progress progress) {
return Get(path, headers, nullptr, std::move(content_receiver),
std::move(progress));
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers,
- ResponseHandler response_handler,
- ContentReceiver content_receiver) {
- return Get(path, headers, std::move(response_handler), content_receiver,
- Progress());
+inline Result ClientImpl::Get(const char *path,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return Get(path, Headers(), std::move(response_handler),
+ std::move(content_receiver), nullptr);
+}
+
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return Get(path, headers, std::move(response_handler),
+ std::move(content_receiver), nullptr);
+}
+
+inline Result ClientImpl::Get(const char *path,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver,
+ Progress progress) {
+ return Get(path, Headers(), std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
}
-inline std::shared_ptr<Response> Client::Get(const char *path,
- const Headers &headers,
- ResponseHandler response_handler,
- ContentReceiver content_receiver,
- Progress progress) {
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver,
+ Progress progress) {
Request req;
req.method = "GET";
req.path = path;
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.response_handler = std::move(response_handler);
- req.content_receiver = std::move(content_receiver);
+ req.content_receiver =
+ [content_receiver](const char *data, size_t data_length,
+ uint64_t /*offset*/, uint64_t /*total_length*/) {
+ return content_receiver(data, data_length);
+ };
req.progress = std::move(progress);
- auto res = std::make_shared<Response>();
- return send(req, *res) ? res : nullptr;
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
}
-inline std::shared_ptr<Response> Client::Head(const char *path) {
+inline Result ClientImpl::Head(const char *path) {
return Head(path, Headers());
}
-inline std::shared_ptr<Response> Client::Head(const char *path,
- const Headers &headers) {
+inline Result ClientImpl::Head(const char *path, const Headers &headers) {
Request req;
req.method = "HEAD";
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.path = path;
- auto res = std::make_shared<Response>();
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
+}
- return send(req, *res) ? res : nullptr;
+inline Result ClientImpl::Post(const char *path) {
+ return Post(path, std::string(), nullptr);
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Post(const char *path, const std::string &body,
+ const char *content_type) {
return Post(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- const Headers &headers,
- const std::string &body,
- const char *content_type) {
- return send_with_content_provider("POST", path, headers, body, 0, nullptr,
- content_type);
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
+ auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr,
+ content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- const Params &params) {
+inline Result ClientImpl::Post(const char *path, const Params &params) {
return Post(path, Headers(), params);
}
-inline std::shared_ptr<Response> Client::Post(const char *path,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type) {
- return Post(path, Headers(), content_length, content_provider, content_type);
+inline Result ClientImpl::Post(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return Post(path, Headers(), content_length, std::move(content_provider),
+ content_type);
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type) {
- return send_with_content_provider("POST", path, headers, std::string(),
- content_length, content_provider,
- content_type);
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ auto ret = send_with_content_provider(
+ "POST", path, headers, std::string(), content_length,
+ std::move(content_provider), content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const Headers &headers, const Params &params) {
- std::string query;
- for (auto it = params.begin(); it != params.end(); ++it) {
- if (it != params.begin()) { query += "&"; }
- query += it->first;
- query += "=";
- query += detail::encode_url(it->second);
- }
-
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ const Params &params) {
+ auto query = detail::params_to_query_str(params);
return Post(path, headers, query, "application/x-www-form-urlencoded");
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const MultipartFormDataItems &items) {
+inline Result ClientImpl::Post(const char *path,
+ const MultipartFormDataItems &items) {
return Post(path, Headers(), items);
}
-inline std::shared_ptr<Response>
-Client::Post(const char *path, const Headers &headers,
- const MultipartFormDataItems &items) {
- auto boundary = detail::make_multipart_data_boundary();
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items) {
+ return Post(path, headers, items, detail::make_multipart_data_boundary());
+}
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items,
+ const std::string &boundary) {
+ for (size_t i = 0; i < boundary.size(); i++) {
+ char c = boundary[i];
+ if (!std::isalnum(c) && c != '-' && c != '_') {
+ error_ = Error::UnsupportedMultipartBoundaryChars;
+ return Result{nullptr, error_};
+ }
+ }
std::string body;
@@ -4153,182 +5480,240 @@ Client::Post(const char *path, const Headers &headers,
return Post(path, headers, body, content_type.c_str());
}
-inline std::shared_ptr<Response> Client::Put(const char *path,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Put(const char *path) {
+ return Put(path, std::string(), nullptr);
+}
+
+inline Result ClientImpl::Put(const char *path, const std::string &body,
+ const char *content_type) {
return Put(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response> Client::Put(const char *path,
- const Headers &headers,
- const std::string &body,
- const char *content_type) {
- return send_with_content_provider("PUT", path, headers, body, 0, nullptr,
- content_type);
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
+ auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr,
+ content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Put(const char *path,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type) {
- return Put(path, Headers(), content_length, content_provider, content_type);
+inline Result ClientImpl::Put(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return Put(path, Headers(), content_length, std::move(content_provider),
+ content_type);
}
-inline std::shared_ptr<Response>
-Client::Put(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type) {
- return send_with_content_provider("PUT", path, headers, std::string(),
- content_length, content_provider,
- content_type);
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ auto ret = send_with_content_provider(
+ "PUT", path, headers, std::string(), content_length,
+ std::move(content_provider), content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Put(const char *path,
- const Params &params) {
+inline Result ClientImpl::Put(const char *path, const Params &params) {
return Put(path, Headers(), params);
}
-inline std::shared_ptr<Response>
-Client::Put(const char *path, const Headers &headers, const Params &params) {
- std::string query;
- for (auto it = params.begin(); it != params.end(); ++it) {
- if (it != params.begin()) { query += "&"; }
- query += it->first;
- query += "=";
- query += detail::encode_url(it->second);
- }
-
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+ const Params &params) {
+ auto query = detail::params_to_query_str(params);
return Put(path, headers, query, "application/x-www-form-urlencoded");
}
-inline std::shared_ptr<Response> Client::Patch(const char *path,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Patch(const char *path, const std::string &body,
+ const char *content_type) {
return Patch(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response> Client::Patch(const char *path,
- const Headers &headers,
- const std::string &body,
- const char *content_type) {
- return send_with_content_provider("PATCH", path, headers, body, 0, nullptr,
- content_type);
+inline Result ClientImpl::Patch(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
+ auto ret = send_with_content_provider("PATCH", path, headers, body, 0,
+ nullptr, content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Patch(const char *path,
- size_t content_length,
- ContentProvider content_provider,
- const char *content_type) {
- return Patch(path, Headers(), content_length, content_provider, content_type);
+inline Result ClientImpl::Patch(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return Patch(path, Headers(), content_length, std::move(content_provider),
+ content_type);
}
-inline std::shared_ptr<Response>
-Client::Patch(const char *path, const Headers &headers, size_t content_length,
- ContentProvider content_provider, const char *content_type) {
- return send_with_content_provider("PATCH", path, headers, std::string(),
- content_length, content_provider,
- content_type);
+inline Result ClientImpl::Patch(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ auto ret = send_with_content_provider(
+ "PATCH", path, headers, std::string(), content_length,
+ std::move(content_provider), content_type);
+ return Result{std::move(ret), get_last_error()};
}
-inline std::shared_ptr<Response> Client::Delete(const char *path) {
+inline Result ClientImpl::Delete(const char *path) {
return Delete(path, Headers(), std::string(), nullptr);
}
-inline std::shared_ptr<Response> Client::Delete(const char *path,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Delete(const char *path, const std::string &body,
+ const char *content_type) {
return Delete(path, Headers(), body, content_type);
}
-inline std::shared_ptr<Response> Client::Delete(const char *path,
- const Headers &headers) {
+inline Result ClientImpl::Delete(const char *path, const Headers &headers) {
return Delete(path, headers, std::string(), nullptr);
}
-inline std::shared_ptr<Response> Client::Delete(const char *path,
- const Headers &headers,
- const std::string &body,
- const char *content_type) {
+inline Result ClientImpl::Delete(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
Request req;
req.method = "DELETE";
- req.headers = headers;
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.path = path;
if (content_type) { req.headers.emplace("Content-Type", content_type); }
req.body = body;
- auto res = std::make_shared<Response>();
-
- return send(req, *res) ? res : nullptr;
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
}
-inline std::shared_ptr<Response> Client::Options(const char *path) {
+inline Result ClientImpl::Options(const char *path) {
return Options(path, Headers());
}
-inline std::shared_ptr<Response> Client::Options(const char *path,
- const Headers &headers) {
+inline Result ClientImpl::Options(const char *path, const Headers &headers) {
Request req;
req.method = "OPTIONS";
+ req.headers = default_headers_;
+ req.headers.insert(headers.begin(), headers.end());
req.path = path;
- req.headers = headers;
- auto res = std::make_shared<Response>();
+ auto res = detail::make_unique<Response>();
+ auto ret = send(req, *res);
+ return Result{ret ? std::move(res) : nullptr, get_last_error()};
+}
- return send(req, *res) ? res : nullptr;
+inline size_t ClientImpl::is_socket_open() const {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ return socket_.is_open();
}
-inline void Client::set_timeout_sec(time_t timeout_sec) {
- timeout_sec_ = timeout_sec;
+inline void ClientImpl::stop() {
+ std::lock_guard<std::mutex> guard(socket_mutex_);
+ // There is no guarantee that this doesn't get overwritten later, but set it so that
+ // there is a good chance that any threads stopping as a result pick up this error.
+ error_ = Error::Canceled;
+
+ // If there is anything ongoing right now, the ONLY thread-safe thing we can do
+ // is to shutdown_socket, so that threads using this socket suddenly discover
+ // they can't read/write any more and error out.
+ // Everything else (closing the socket, shutting ssl down) is unsafe because these
+ // actions are not thread-safe.
+ if (socket_requests_in_flight_ > 0) {
+ shutdown_socket(socket_);
+ // Aside from that, we set a flag for the socket to be closed when we're done.
+ socket_should_be_closed_when_request_is_done_ = true;
+ return;
+ }
+
+ //Otherwise, sitll holding the mutex, we can shut everything down ourselves
+ shutdown_ssl(socket_, true);
+ shutdown_socket(socket_);
+ close_socket(socket_);
}
-inline void Client::set_read_timeout(time_t sec, time_t usec) {
+inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
+ connection_timeout_sec_ = sec;
+ connection_timeout_usec_ = usec;
+}
+
+inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
read_timeout_sec_ = sec;
read_timeout_usec_ = usec;
}
-inline void Client::set_keep_alive_max_count(size_t count) {
- keep_alive_max_count_ = count;
+inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
+ write_timeout_sec_ = sec;
+ write_timeout_usec_ = usec;
}
-inline void Client::set_basic_auth(const char *username, const char *password) {
+inline void ClientImpl::set_basic_auth(const char *username,
+ const char *password) {
basic_auth_username_ = username;
basic_auth_password_ = password;
}
+inline void ClientImpl::set_bearer_token_auth(const char *token) {
+ bearer_token_auth_token_ = token;
+}
+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void Client::set_digest_auth(const char *username,
- const char *password) {
+inline void ClientImpl::set_digest_auth(const char *username,
+ const char *password) {
digest_auth_username_ = username;
digest_auth_password_ = password;
}
#endif
-inline void Client::set_follow_location(bool on) { follow_location_ = on; }
+inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
-inline void Client::set_compress(bool on) { compress_ = on; }
+inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
-inline void Client::set_interface(const char *intf) { interface_ = intf; }
+inline void ClientImpl::set_default_headers(Headers headers) {
+ default_headers_ = std::move(headers);
+}
-inline void Client::set_proxy(const char *host, int port) {
+inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
+
+inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
+ socket_options_ = std::move(socket_options);
+}
+
+inline void ClientImpl::set_compress(bool on) { compress_ = on; }
+
+inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
+
+inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; }
+
+inline void ClientImpl::set_proxy(const char *host, int port) {
proxy_host_ = host;
proxy_port_ = port;
}
-inline void Client::set_proxy_basic_auth(const char *username,
- const char *password) {
+inline void ClientImpl::set_proxy_basic_auth(const char *username,
+ const char *password) {
proxy_basic_auth_username_ = username;
proxy_basic_auth_password_ = password;
}
+inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) {
+ proxy_bearer_token_auth_token_ = token;
+}
+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void Client::set_proxy_digest_auth(const char *username,
- const char *password) {
+inline void ClientImpl::set_proxy_digest_auth(const char *username,
+ const char *password) {
proxy_digest_auth_username_ = username;
proxy_digest_auth_password_ = password;
}
#endif
-inline void Client::set_logger(Logger logger) { logger_ = std::move(logger); }
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
+ server_certificate_verification_ = enabled;
+}
+#endif
+
+inline void ClientImpl::set_logger(Logger logger) {
+ logger_ = std::move(logger);
+}
/*
* SSL Implementation
@@ -4336,72 +5721,69 @@ inline void Client::set_logger(Logger logger) { logger_ = std::move(logger); }
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
namespace detail {
-template <typename U, typename V, typename T>
-inline bool process_and_close_socket_ssl(
- bool is_client_request, socket_t sock, size_t keep_alive_max_count,
- time_t read_timeout_sec, time_t read_timeout_usec, SSL_CTX *ctx,
- std::mutex &ctx_mutex, U SSL_connect_or_accept, V setup, T callback) {
- assert(keep_alive_max_count > 0);
-
+template <typename U, typename V>
+inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
+ U SSL_connect_or_accept, V setup) {
SSL *ssl = nullptr;
{
std::lock_guard<std::mutex> guard(ctx_mutex);
ssl = SSL_new(ctx);
}
- if (!ssl) {
- close_socket(sock);
- return false;
- }
+ if (ssl) {
+ auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
+ SSL_set_bio(ssl, bio, bio);
- auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
- SSL_set_bio(ssl, bio, bio);
-
- if (!setup(ssl)) {
- SSL_shutdown(ssl);
- {
- std::lock_guard<std::mutex> guard(ctx_mutex);
- SSL_free(ssl);
+ if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
+ SSL_shutdown(ssl);
+ {
+ std::lock_guard<std::mutex> guard(ctx_mutex);
+ SSL_free(ssl);
+ }
+ return nullptr;
}
-
- close_socket(sock);
- return false;
}
- auto ret = false;
+ return ssl;
+}
- if (SSL_connect_or_accept(ssl) == 1) {
- if (keep_alive_max_count > 1) {
- auto count = keep_alive_max_count;
- while (count > 0 &&
- (is_client_request ||
- detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
- CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
- SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
- auto last_connection = count == 1;
- auto connection_close = false;
-
- ret = callback(ssl, strm, last_connection, connection_close);
- if (!ret || connection_close) { break; }
-
- count--;
- }
- } else {
- SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
- auto dummy_connection_close = false;
- ret = callback(ssl, strm, true, dummy_connection_close);
- }
+inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
+ bool shutdown_gracefully) {
+ // sometimes we may want to skip this to try to avoid SIGPIPE if we know
+ // the remote has closed the network connection
+ // Note that it is not always possible to avoid SIGPIPE, this is merely a best-efforts.
+ if (shutdown_gracefully) {
+ SSL_shutdown(ssl);
}
- SSL_shutdown(ssl);
- {
- std::lock_guard<std::mutex> guard(ctx_mutex);
- SSL_free(ssl);
- }
+ std::lock_guard<std::mutex> guard(ctx_mutex);
+ SSL_free(ssl);
+}
- close_socket(sock);
+template <typename T>
+inline bool
+process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count,
+ time_t keep_alive_timeout_sec,
+ time_t read_timeout_sec, time_t read_timeout_usec,
+ time_t write_timeout_sec, time_t write_timeout_usec,
+ T callback) {
+ return process_server_socket_core(
+ sock, keep_alive_max_count, keep_alive_timeout_sec,
+ [&](bool close_connection, bool &connection_closed) {
+ SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm, close_connection, connection_closed);
+ });
+}
- return ret;
+template <typename T>
+inline bool
+process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
+ time_t read_timeout_usec, time_t write_timeout_sec,
+ time_t write_timeout_usec, T callback) {
+ SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
+ write_timeout_sec, write_timeout_usec);
+ return callback(strm);
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
@@ -4420,11 +5802,11 @@ public:
private:
static void locking_callback(int mode, int type, const char * /*file*/,
int /*line*/) {
- auto &locks = *openSSL_locks_;
+ auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
if (mode & CRYPTO_LOCK) {
- locks[type].lock();
+ lk.lock();
} else {
- locks[type].unlock();
+ lk.unlock();
}
}
};
@@ -4458,9 +5840,15 @@ private:
// SSL socket stream implementation
inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
time_t read_timeout_sec,
- time_t read_timeout_usec)
+ time_t read_timeout_usec,
+ time_t write_timeout_sec,
+ time_t write_timeout_usec)
: sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
- read_timeout_usec_(read_timeout_usec) {}
+ read_timeout_usec_(read_timeout_usec),
+ write_timeout_sec_(write_timeout_sec),
+ write_timeout_usec_(write_timeout_usec) {
+ SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
+}
inline SSLSocketStream::~SSLSocketStream() {}
@@ -4469,24 +5857,44 @@ inline bool SSLSocketStream::is_readable() const {
}
inline bool SSLSocketStream::is_writable() const {
- return detail::select_write(sock_, 0, 0) > 0;
+ return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) >
+ 0;
}
-inline int SSLSocketStream::read(char *ptr, size_t size) {
- if (SSL_pending(ssl_) > 0 ||
- select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0) {
+inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
+ if (SSL_pending(ssl_) > 0) {
return SSL_read(ssl_, ptr, static_cast<int>(size));
+ } else if (is_readable()) {
+ auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
+ if (ret < 0) {
+ auto err = SSL_get_error(ssl_, ret);
+ while (err == SSL_ERROR_WANT_READ) {
+ if (SSL_pending(ssl_) > 0) {
+ return SSL_read(ssl_, ptr, static_cast<int>(size));
+ } else if (is_readable()) {
+ ret = SSL_read(ssl_, ptr, static_cast<int>(size));
+ if (ret >= 0) {
+ return ret;
+ }
+ err = SSL_get_error(ssl_, ret);
+ } else {
+ return -1;
+ }
+ }
+ }
+ return ret;
}
return -1;
}
-inline int SSLSocketStream::write(const char *ptr, size_t size) {
+inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
if (is_writable()) { return SSL_write(ssl_, ptr, static_cast<int>(size)); }
return -1;
}
-inline std::string SSLSocketStream::get_remote_addr() const {
- return detail::get_remote_addr(sock_);
+inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
+ int &port) const {
+ detail::get_remote_ip_and_port(sock_, ip, port);
}
static SSLInit sslinit_;
@@ -4532,6 +5940,33 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
}
}
+inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
+ X509_STORE *client_ca_cert_store) {
+ ctx_ = SSL_CTX_new(SSLv23_server_method());
+
+ if (ctx_) {
+ SSL_CTX_set_options(ctx_,
+ SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
+ SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ } else if (client_ca_cert_store) {
+
+ SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
+
+ SSL_CTX_set_verify(
+ ctx_,
+ SSL_VERIFY_PEER |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE,
+ nullptr);
+ }
+ }
+}
+
inline SSLServer::~SSLServer() {
if (ctx_) { SSL_CTX_free(ctx_); }
}
@@ -4539,21 +5974,42 @@ inline SSLServer::~SSLServer() {
inline bool SSLServer::is_valid() const { return ctx_; }
inline bool SSLServer::process_and_close_socket(socket_t sock) {
- return detail::process_and_close_socket_ssl(
- false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
- ctx_, ctx_mutex_, SSL_accept, [](SSL * /*ssl*/) { return true; },
- [this](SSL *ssl, Stream &strm, bool last_connection,
- bool &connection_close) {
- return process_request(strm, last_connection, connection_close,
- [&](Request &req) { req.ssl = ssl; });
- });
+ auto ssl = detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept,
+ [](SSL * /*ssl*/) { return true; });
+
+ if (ssl) {
+ auto ret = detail::process_server_socket_ssl(
+ ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
+ read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+ write_timeout_usec_,
+ [this, ssl](Stream &strm, bool close_connection,
+ bool &connection_closed) {
+ return process_request(strm, close_connection, connection_closed,
+ [&](Request &req) { req.ssl = ssl; });
+ });
+
+ detail::ssl_delete(ctx_mutex_, ssl, ret);
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ return ret;
+ }
+
+ detail::shutdown_socket(sock);
+ detail::close_socket(sock);
+ return false;
}
// SSL HTTP client implementation
+inline SSLClient::SSLClient(const std::string &host)
+ : SSLClient(host, 443, std::string(), std::string()) {}
+
+inline SSLClient::SSLClient(const std::string &host, int port)
+ : SSLClient(host, port, std::string(), std::string()) {}
+
inline SSLClient::SSLClient(const std::string &host, int port,
const std::string &client_cert_path,
const std::string &client_key_path)
- : Client(host, port, client_cert_path, client_key_path) {
+ : ClientImpl(host, port, client_cert_path, client_key_path) {
ctx_ = SSL_CTX_new(SSLv23_client_method());
detail::split(&host_[0], &host_[host_.size()], '.',
@@ -4571,8 +6027,30 @@ inline SSLClient::SSLClient(const std::string &host, int port,
}
}
+inline SSLClient::SSLClient(const std::string &host, int port,
+ X509 *client_cert, EVP_PKEY *client_key)
+ : ClientImpl(host, port) {
+ ctx_ = SSL_CTX_new(SSLv23_client_method());
+
+ detail::split(&host_[0], &host_[host_.size()], '.',
+ [&](const char *b, const char *e) {
+ host_components_.emplace_back(std::string(b, e));
+ });
+ if (client_cert != nullptr && client_key != nullptr) {
+ if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
+ SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
+ SSL_CTX_free(ctx_);
+ ctx_ = nullptr;
+ }
+ }
+}
+
inline SSLClient::~SSLClient() {
if (ctx_) { SSL_CTX_free(ctx_); }
+ // Make sure to shut down SSL since shutdown_ssl will resolve to the
+ // base function rather than the derived function once we get to the
+ // base class destructor, and won't free the SSL (causing a leak).
+ SSLClient::shutdown_ssl(socket_, true);
}
inline bool SSLClient::is_valid() const { return ctx_; }
@@ -4583,67 +6061,188 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
}
-inline void SSLClient::enable_server_certificate_verification(bool enabled) {
- server_certificate_verification_ = enabled;
+inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
+ if (ca_cert_store) {
+ if (ctx_) {
+ if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
+ // Free memory allocated for old cert and use new store `ca_cert_store`
+ SSL_CTX_set_cert_store(ctx_, ca_cert_store);
+ }
+ } else {
+ X509_STORE_free(ca_cert_store);
+ }
+ }
}
inline long SSLClient::get_openssl_verify_result() const {
return verify_result_;
}
-inline SSL_CTX *SSLClient::ssl_context() const noexcept { return ctx_; }
+inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
-inline bool SSLClient::process_and_close_socket(
- socket_t sock, size_t request_count,
- std::function<bool(Stream &strm, bool last_connection,
- bool &connection_close)>
- callback) {
+inline bool SSLClient::create_and_connect_socket(Socket &socket) {
+ return is_valid() && ClientImpl::create_and_connect_socket(socket);
+}
- request_count = std::min(request_count, keep_alive_max_count_);
+// Assumes that socket_mutex_ is locked and that there are no requests in flight
+inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
+ bool &success) {
+ success = true;
+ Response res2;
+ if (!detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ Request req2;
+ req2.method = "CONNECT";
+ req2.path = host_and_port_;
+ return process_request(strm, req2, res2, false);
+ })) {
+ // Thread-safe to close everything because we are assuming there are no requests in flight
+ shutdown_ssl(socket, true);
+ shutdown_socket(socket);
+ close_socket(socket);
+ success = false;
+ return false;
+ }
+
+ if (res2.status == 407) {
+ if (!proxy_digest_auth_username_.empty() &&
+ !proxy_digest_auth_password_.empty()) {
+ std::map<std::string, std::string> auth;
+ if (detail::parse_www_authenticate(res2, auth, true)) {
+ Response res3;
+ if (!detail::process_client_socket(
+ socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+ Request req3;
+ req3.method = "CONNECT";
+ req3.path = host_and_port_;
+ req3.headers.insert(detail::make_digest_authentication_header(
+ req3, auth, 1, detail::random_string(10),
+ proxy_digest_auth_username_, proxy_digest_auth_password_,
+ true));
+ return process_request(strm, req3, res3, false);
+ })) {
+ // Thread-safe to close everything because we are assuming there are no requests in flight
+ shutdown_ssl(socket, true);
+ shutdown_socket(socket);
+ close_socket(socket);
+ success = false;
+ return false;
+ }
+ }
+ } else {
+ res = res2;
+ return false;
+ }
+ }
- return is_valid() &&
- detail::process_and_close_socket_ssl(
- true, sock, request_count, read_timeout_sec_, read_timeout_usec_,
- ctx_, ctx_mutex_,
- [&](SSL *ssl) {
- if (ca_cert_file_path_.empty()) {
- SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr);
- } else {
- if (!SSL_CTX_load_verify_locations(
- ctx_, ca_cert_file_path_.c_str(), nullptr)) {
- return false;
- }
- SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr);
- }
+ return true;
+}
- if (SSL_connect(ssl) != 1) { return false; }
+inline bool SSLClient::load_certs() {
+ bool ret = true;
- if (server_certificate_verification_) {
- verify_result_ = SSL_get_verify_result(ssl);
+ std::call_once(initialize_cert_, [&]() {
+ std::lock_guard<std::mutex> guard(ctx_mutex_);
+ if (!ca_cert_file_path_.empty()) {
+ if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
+ nullptr)) {
+ ret = false;
+ }
+ } else if (!ca_cert_dir_path_.empty()) {
+ if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
+ ca_cert_dir_path_.c_str())) {
+ ret = false;
+ }
+ } else {
+#ifdef _WIN32
+ detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
+#else
+ SSL_CTX_set_default_verify_paths(ctx_);
+#endif
+ }
+ });
- if (verify_result_ != X509_V_OK) { return false; }
+ return ret;
+}
- auto server_cert = SSL_get_peer_certificate(ssl);
+inline bool SSLClient::initialize_ssl(Socket &socket) {
+ auto ssl = detail::ssl_new(
+ socket.sock, ctx_, ctx_mutex_,
+ [&](SSL *ssl) {
+ if (server_certificate_verification_) {
+ if (!load_certs()) {
+ error_ = Error::SSLLoadingCerts;
+ return false;
+ }
+ SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
+ }
- if (server_cert == nullptr) { return false; }
+ if (SSL_connect(ssl) != 1) {
+ error_ = Error::SSLConnection;
+ return false;
+ }
- if (!verify_host(server_cert)) {
- X509_free(server_cert);
- return false;
- }
- X509_free(server_cert);
- }
+ if (server_certificate_verification_) {
+ verify_result_ = SSL_get_verify_result(ssl);
- return true;
- },
- [&](SSL *ssl) {
- SSL_set_tlsext_host_name(ssl, host_.c_str());
- return true;
- },
- [&](SSL * /*ssl*/, Stream &strm, bool last_connection,
- bool &connection_close) {
- return callback(strm, last_connection, connection_close);
- });
+ if (verify_result_ != X509_V_OK) {
+ error_ = Error::SSLServerVerification;
+ return false;
+ }
+
+ auto server_cert = SSL_get_peer_certificate(ssl);
+
+ if (server_cert == nullptr) {
+ error_ = Error::SSLServerVerification;
+ return false;
+ }
+
+ if (!verify_host(server_cert)) {
+ X509_free(server_cert);
+ error_ = Error::SSLServerVerification;
+ return false;
+ }
+ X509_free(server_cert);
+ }
+
+ return true;
+ },
+ [&](SSL *ssl) {
+ SSL_set_tlsext_host_name(ssl, host_.c_str());
+ return true;
+ });
+
+ if (ssl) {
+ socket.ssl = ssl;
+ return true;
+ }
+
+ shutdown_socket(socket);
+ close_socket(socket);
+ return false;
+}
+
+inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
+ if (socket.sock == INVALID_SOCKET) {
+ assert(socket.ssl == nullptr);
+ return;
+ }
+ if (socket.ssl) {
+ detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully);
+ socket.ssl = nullptr;
+ }
+ assert(socket.ssl == nullptr);
+}
+
+inline bool
+SSLClient::process_socket(const Socket &socket,
+ std::function<bool(Stream &strm)> callback) {
+ assert(socket.ssl);
+ return detail::process_client_socket_ssl(
+ socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
+ write_timeout_sec_, write_timeout_usec_, std::move(callback));
}
inline bool SSLClient::is_ssl() const { return true; }
@@ -4703,7 +6302,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
auto count = sk_GENERAL_NAME_num(alt_names);
- for (auto i = 0; i < count && !dsn_matched; i++) {
+ for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
auto val = sk_GENERAL_NAME_value(alt_names, i);
if (val->type == type) {
auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
@@ -4728,7 +6327,6 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
}
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
-
return ret;
}
@@ -4740,7 +6338,9 @@ inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
name, sizeof(name));
- if (name_len != -1) { return check_host_name(name, name_len); }
+ if (name_len != -1) {
+ return check_host_name(name, static_cast<size_t>(name_len));
+ }
}
return false;
@@ -4775,6 +6375,338 @@ inline bool SSLClient::check_host_name(const char *pattern,
}
#endif
+// Universal client implementation
+inline Client::Client(const char *scheme_host_port)
+ : Client(scheme_host_port, std::string(), std::string()) {}
+
+inline Client::Client(const char *scheme_host_port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path) {
+ const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)");
+
+ std::cmatch m;
+ if (std::regex_match(scheme_host_port, m, re)) {
+ auto scheme = m[1].str();
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
+#else
+ if (!scheme.empty() && scheme != "http") {
+#endif
+ std::string msg = "'" + scheme + "' scheme is not supported.";
+ throw std::invalid_argument(msg);
+ return;
+ }
+
+ auto is_ssl = scheme == "https";
+
+ auto host = m[2].str();
+
+ auto port_str = m[3].str();
+ auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
+
+ if (is_ssl) {
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+ cli_ = detail::make_unique<SSLClient>(host.c_str(), port,
+ client_cert_path, client_key_path);
+ is_ssl_ = is_ssl;
+#endif
+ } else {
+ cli_ = detail::make_unique<ClientImpl>(host.c_str(), port,
+ client_cert_path, client_key_path);
+ }
+ } else {
+ cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
+ client_cert_path, client_key_path);
+ }
+}
+
+inline Client::Client(const std::string &host, int port)
+ : cli_(detail::make_unique<ClientImpl>(host, port)) {}
+
+inline Client::Client(const std::string &host, int port,
+ const std::string &client_cert_path,
+ const std::string &client_key_path)
+ : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
+ client_key_path)) {}
+
+inline Client::~Client() {}
+
+inline bool Client::is_valid() const {
+ return cli_ != nullptr && cli_->is_valid();
+}
+
+inline Result Client::Get(const char *path) { return cli_->Get(path); }
+inline Result Client::Get(const char *path, const Headers &headers) {
+ return cli_->Get(path, headers);
+}
+inline Result Client::Get(const char *path, Progress progress) {
+ return cli_->Get(path, std::move(progress));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ Progress progress) {
+ return cli_->Get(path, headers, std::move(progress));
+}
+inline Result Client::Get(const char *path, ContentReceiver content_receiver) {
+ return cli_->Get(path, std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, headers, std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, ContentReceiver content_receiver,
+ Progress progress) {
+ return cli_->Get(path, std::move(content_receiver), std::move(progress));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, headers, std::move(content_receiver),
+ std::move(progress));
+}
+inline Result Client::Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, std::move(response_handler),
+ std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver) {
+ return cli_->Get(path, headers, std::move(response_handler),
+ std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
+}
+inline Result Client::Get(const char *path, const Headers &headers,
+ ResponseHandler response_handler,
+ ContentReceiver content_receiver, Progress progress) {
+ return cli_->Get(path, headers, std::move(response_handler),
+ std::move(content_receiver), std::move(progress));
+}
+
+inline Result Client::Head(const char *path) { return cli_->Head(path); }
+inline Result Client::Head(const char *path, const Headers &headers) {
+ return cli_->Head(path, headers);
+}
+
+inline Result Client::Post(const char *path) { return cli_->Post(path); }
+inline Result Client::Post(const char *path, const std::string &body,
+ const char *content_type) {
+ return cli_->Post(path, body, content_type);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type) {
+ return cli_->Post(path, headers, body, content_type);
+}
+inline Result Client::Post(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Post(path, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Post(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Post(const char *path, const Params &params) {
+ return cli_->Post(path, params);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ const Params &params) {
+ return cli_->Post(path, headers, params);
+}
+inline Result Client::Post(const char *path,
+ const MultipartFormDataItems &items) {
+ return cli_->Post(path, items);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items) {
+ return cli_->Post(path, headers, items);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
+ const MultipartFormDataItems &items,
+ const std::string &boundary) {
+ return cli_->Post(path, headers, items, boundary);
+}
+inline Result Client::Put(const char *path) { return cli_->Put(path); }
+inline Result Client::Put(const char *path, const std::string &body,
+ const char *content_type) {
+ return cli_->Put(path, body, content_type);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type) {
+ return cli_->Put(path, headers, body, content_type);
+}
+inline Result Client::Put(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Put(path, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Put(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Put(const char *path, const Params &params) {
+ return cli_->Put(path, params);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+ const Params &params) {
+ return cli_->Put(path, headers, params);
+}
+inline Result Client::Patch(const char *path, const std::string &body,
+ const char *content_type) {
+ return cli_->Patch(path, body, content_type);
+}
+inline Result Client::Patch(const char *path, const Headers &headers,
+ const std::string &body, const char *content_type) {
+ return cli_->Patch(path, headers, body, content_type);
+}
+inline Result Client::Patch(const char *path, size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Patch(path, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Patch(const char *path, const Headers &headers,
+ size_t content_length,
+ ContentProvider content_provider,
+ const char *content_type) {
+ return cli_->Patch(path, headers, content_length, std::move(content_provider),
+ content_type);
+}
+inline Result Client::Delete(const char *path) { return cli_->Delete(path); }
+inline Result Client::Delete(const char *path, const std::string &body,
+ const char *content_type) {
+ return cli_->Delete(path, body, content_type);
+}
+inline Result Client::Delete(const char *path, const Headers &headers) {
+ return cli_->Delete(path, headers);
+}
+inline Result Client::Delete(const char *path, const Headers &headers,
+ const std::string &body,
+ const char *content_type) {
+ return cli_->Delete(path, headers, body, content_type);
+}
+inline Result Client::Options(const char *path) { return cli_->Options(path); }
+inline Result Client::Options(const char *path, const Headers &headers) {
+ return cli_->Options(path, headers);
+}
+
+inline bool Client::send(const Request &req, Response &res) {
+ return cli_->send(req, res);
+}
+
+inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
+
+inline void Client::stop() { cli_->stop(); }
+
+inline void Client::set_default_headers(Headers headers) {
+ cli_->set_default_headers(std::move(headers));
+}
+
+inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
+inline void Client::set_socket_options(SocketOptions socket_options) {
+ cli_->set_socket_options(std::move(socket_options));
+}
+
+inline void Client::set_connection_timeout(time_t sec, time_t usec) {
+ cli_->set_connection_timeout(sec, usec);
+}
+inline void Client::set_read_timeout(time_t sec, time_t usec) {
+ cli_->set_read_timeout(sec, usec);
+}
+inline void Client::set_write_timeout(time_t sec, time_t usec) {
+ cli_->set_write_timeout(sec, usec);
+}
+
+inline void Client::set_basic_auth(const char *username, const char *password) {
+ cli_->set_basic_auth(username, password);
+}
+inline void Client::set_bearer_token_auth(const char *token) {
+ cli_->set_bearer_token_auth(token);
+}
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void Client::set_digest_auth(const char *username,
+ const char *password) {
+ cli_->set_digest_auth(username, password);
+}
+#endif
+
+inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
+inline void Client::set_follow_location(bool on) {
+ cli_->set_follow_location(on);
+}
+
+inline void Client::set_compress(bool on) { cli_->set_compress(on); }
+
+inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
+
+inline void Client::set_interface(const char *intf) {
+ cli_->set_interface(intf);
+}
+
+inline void Client::set_proxy(const char *host, int port) {
+ cli_->set_proxy(host, port);
+}
+inline void Client::set_proxy_basic_auth(const char *username,
+ const char *password) {
+ cli_->set_proxy_basic_auth(username, password);
+}
+inline void Client::set_proxy_bearer_token_auth(const char *token) {
+ cli_->set_proxy_bearer_token_auth(token);
+}
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void Client::set_proxy_digest_auth(const char *username,
+ const char *password) {
+ cli_->set_proxy_digest_auth(username, password);
+}
+#endif
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void Client::enable_server_certificate_verification(bool enabled) {
+ cli_->enable_server_certificate_verification(enabled);
+}
+#endif
+
+inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void Client::set_ca_cert_path(const char *ca_cert_file_path,
+ const char *ca_cert_dir_path) {
+ if (is_ssl_) {
+ static_cast<SSLClient &>(*cli_).set_ca_cert_path(ca_cert_file_path,
+ ca_cert_dir_path);
+ }
+}
+
+inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
+ if (is_ssl_) {
+ static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
+ }
+}
+
+inline long Client::get_openssl_verify_result() const {
+ if (is_ssl_) {
+ return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
+ }
+ return -1; // NOTE: -1 doesn't match any of X509_V_ERR_???
+}
+
+inline SSL_CTX *Client::ssl_context() const {
+ if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); }
+ return nullptr;
+}
+#endif
+
// ----------------------------------------------------------------------------
} // namespace httplib
diff --git a/externals/inih/inih b/externals/inih/inih
-Subproject 603729dec89aaca42d7bd08f08bc333165b7d5d
+Subproject 1e80a47dffbda813604f0913e2ad68c7054c14e
diff --git a/externals/libressl b/externals/libressl
-Subproject 7d01cb01cb1a926ecb4c9c98b107ef3c26f59df
+Subproject 8289d0d07de6553bf4b900bf60e808ea3f7f59d
diff --git a/externals/libusb b/externals/libusb
deleted file mode 160000
-Subproject 3406d72cda879f8792a88bf5f6bd0b7a65636f7
diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt
new file mode 100644
index 000000000..c0d24b126
--- /dev/null
+++ b/externals/libusb/CMakeLists.txt
@@ -0,0 +1,150 @@
+add_library(usb STATIC EXCLUDE_FROM_ALL
+ libusb/libusb/core.c
+ libusb/libusb/core.c
+ libusb/libusb/descriptor.c
+ libusb/libusb/hotplug.c
+ libusb/libusb/io.c
+ libusb/libusb/strerror.c
+ libusb/libusb/sync.c
+)
+set_target_properties(usb PROPERTIES VERSION 1.0.23)
+if(WIN32)
+ target_include_directories(usb
+ BEFORE
+ PUBLIC
+ libusb/libusb
+
+ PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}"
+ )
+
+ if (NOT MINGW)
+ target_include_directories(usb BEFORE PRIVATE libusb/msvc)
+ endif()
+
+ # Works around other libraries providing their own definition of USB GUIDs (e.g. SDL2)
+ target_compile_definitions(usb PRIVATE "-DGUID_DEVINTERFACE_USB_DEVICE=(GUID){ 0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}}")
+else()
+target_include_directories(usb
+ # turns out other projects also have "config.h", so make sure the
+ # LibUSB one comes first
+ BEFORE
+
+ PUBLIC
+ libusb/libusb
+
+ PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}"
+)
+endif()
+
+if(WIN32 OR CYGWIN)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/threads_windows.c
+ libusb/libusb/os/windows_winusb.c
+ libusb/libusb/os/windows_usbdk.c
+ libusb/libusb/os/windows_nt_common.c
+ )
+ set(OS_WINDOWS TRUE)
+elseif(APPLE)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/darwin_usb.c
+ )
+ find_library(COREFOUNDATION_LIBRARY CoreFoundation)
+ find_library(IOKIT_LIBRARY IOKit)
+ find_library(OBJC_LIBRARY objc)
+ target_link_libraries(usb PRIVATE
+ ${COREFOUNDATION_LIBRARY}
+ ${IOKIT_LIBRARY}
+ ${OBJC_LIBRARY}
+ )
+ set(OS_DARWIN TRUE)
+elseif(ANDROID)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/linux_usbfs.c
+ libusb/libusb/os/linux_netlink.c
+ )
+ find_library(LOG_LIBRARY log)
+ target_link_libraries(usb PRIVATE ${LOG_LIBRARY})
+ set(OS_LINUX TRUE)
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ target_sources(usb PRIVATE
+ libusb/libusb/os/linux_usbfs.c
+ )
+ find_package(Libudev)
+ if(LIBUDEV_FOUND)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/linux_udev.c
+ )
+ target_link_libraries(usb PRIVATE "${LIBUDEV_LIBRARIES}")
+ target_include_directories(usb PRIVATE "${LIBUDEV_INCLUDE_DIR}")
+ set(HAVE_LIBUDEV TRUE)
+ set(USE_UDEV TRUE)
+ else()
+ target_sources(usb PRIVATE
+ libusb/libusb/os/linux_netlink.c
+ )
+ endif()
+ set(OS_LINUX TRUE)
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD")
+ target_sources(usb PRIVATE
+ libusb/libusb/os/netbsd_usb.c
+ )
+ set(OS_NETBSD TRUE)
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
+ target_sources(usb PRIVATE
+ libusb/libusb/os/openbsd_usb.c
+ )
+ set(OS_OPENBSD TRUE)
+endif()
+
+if(UNIX)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/poll_posix.c
+ libusb/libusb/os/threads_posix.c
+ )
+ find_package(Threads REQUIRED)
+ if(THREADS_HAVE_PTHREAD_ARG)
+ target_compile_options(usb PUBLIC "-pthread")
+ endif()
+ if(CMAKE_THREAD_LIBS_INIT)
+ target_link_libraries(usb PRIVATE "${CMAKE_THREAD_LIBS_INIT}")
+ endif()
+ set(THREADS_POSIX TRUE)
+elseif(WIN32)
+ target_sources(usb PRIVATE
+ libusb/libusb/os/poll_windows.c
+ libusb/libusb/os/threads_windows.c
+ )
+endif()
+
+include(CheckFunctionExists)
+include(CheckIncludeFiles)
+include(CheckTypeSize)
+check_include_files(asm/types.h HAVE_ASM_TYPES_H)
+check_function_exists(gettimeofday HAVE_GETTIMEOFDAY)
+check_include_files(linux/filter.h HAVE_LINUX_FILTER_H)
+check_include_files(linux/netlink.h HAVE_LINUX_NETLINK_H)
+check_include_files(poll.h HAVE_POLL_H)
+check_include_files(signal.h HAVE_SIGNAL_H)
+check_include_files(strings.h HAVE_STRINGS_H)
+check_type_size("struct timespec" STRUCT_TIMESPEC)
+check_function_exists(syslog HAVE_SYSLOG_FUNC)
+check_include_files(syslog.h HAVE_SYSLOG_H)
+check_include_files(sys/socket.h HAVE_SYS_SOCKET_H)
+check_include_files(sys/time.h HAVE_SYS_TIME_H)
+check_include_files(sys/types.h HAVE_SYS_TYPES_H)
+
+set(CMAKE_EXTRA_INCLUDE_FILES poll.h)
+check_type_size("nfds_t" nfds_t)
+unset(CMAKE_EXTRA_INCLUDE_FILES)
+if(HAVE_NFDS_T)
+ set(POLL_NFDS_TYPE "nfds_t")
+else()
+ set(POLL_NFDS_TYPE "unsigned int")
+endif()
+
+check_include_files(sys/timerfd.h USBI_TIMERFD_AVAILABLE)
+
+
+configure_file(config.h.in config.h)
diff --git a/externals/libusb/config.h.in b/externals/libusb/config.h.in
new file mode 100644
index 000000000..915b7390f
--- /dev/null
+++ b/externals/libusb/config.h.in
@@ -0,0 +1,90 @@
+/* Default visibility */
+#if defined(__GNUC__) || defined(__clang__)
+ #define DEFAULT_VISIBILITY __attribute__((visibility("default")))
+#elif defined(_MSC_VER)
+ #define DEFAULT_VISIBILITY __declspec(dllexport)
+#endif
+
+/* Start with debug message logging enabled */
+#undef ENABLE_DEBUG_LOGGING
+
+/* Message logging */
+#undef ENABLE_LOGGING
+
+/* Define to 1 if you have the <asm/types.h> header file. */
+#cmakedefine HAVE_ASM_TYPES_H 1
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#cmakedefine HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the `udev' library (-ludev). */
+#cmakedefine HAVE_LIBUDEV 1
+
+/* Define to 1 if you have the <linux/filter.h> header file. */
+#cmakedefine HAVE_LINUX_FILTER_H 1
+
+/* Define to 1 if you have the <linux/netlink.h> header file. */
+#cmakedefine HAVE_LINUX_NETLINK_H 1
+
+/* Define to 1 if you have the <poll.h> header file. */
+#cmakedefine HAVE_POLL_H 1
+
+/* Define to 1 if you have the <signal.h> header file. */
+#cmakedefine HAVE_SIGNAL_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#cmakedefine HAVE_STRINGS_H 1
+
+/* Define to 1 if the system has the type `struct timespec'. */
+#cmakedefine HAVE_STRUCT_TIMESPEC 1
+
+/* syslog() function available */
+#cmakedefine HAVE_SYSLOG_FUNC 1
+
+/* Define to 1 if you have the <syslog.h> header file. */
+#cmakedefine HAVE_SYSLOG_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#cmakedefine HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#cmakedefine HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#cmakedefine HAVE_SYS_TYPES_H 1
+
+/* Darwin backend */
+#cmakedefine OS_DARWIN 1
+
+/* Linux backend */
+#cmakedefine OS_LINUX 1
+
+/* NetBSD backend */
+#cmakedefine OS_NETBSD 1
+
+/* OpenBSD backend */
+#cmakedefine OS_OPENBSD 1
+
+/* Windows backend */
+#cmakedefine OS_WINDOWS 1
+
+/* type of second poll() argument */
+#define POLL_NFDS_TYPE @POLL_NFDS_TYPE@
+
+/* Use POSIX Threads */
+#cmakedefine THREADS_POSIX
+
+/* timerfd headers available */
+#cmakedefine USBI_TIMERFD_AVAILABLE 1
+
+/* Enable output to system log */
+#define USE_SYSTEM_LOGGING_FACILITY 1
+
+/* Use udev for device enumeration/hotplug */
+#cmakedefine USE_UDEV 1
+
+/* Use GNU extensions */
+#define _GNU_SOURCE
+
+/* Oldest Windows version supported */
+#define WINVER 0x0501
diff --git a/externals/libusb/libusb b/externals/libusb/libusb
new file mode 160000
+Subproject e782eeb2514266f6738e242cdcb18e3ae1ed06f
diff --git a/externals/lurlparser/CMakeLists.txt b/externals/lurlparser/CMakeLists.txt
deleted file mode 100644
index 45046ffd3..000000000
--- a/externals/lurlparser/CMakeLists.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-add_library(lurlparser
- LUrlParser.cpp
- LUrlParser.h
-)
-
-create_target_directory_groups(lurlparser)
-
-target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/externals/lurlparser/LUrlParser.cpp b/externals/lurlparser/LUrlParser.cpp
deleted file mode 100644
index 9c134e330..000000000
--- a/externals/lurlparser/LUrlParser.cpp
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Lightweight URL & URI parser (RFC 1738, RFC 3986)
- * https://github.com/corporateshark/LUrlParser
- *
- * The MIT License (MIT)
- *
- * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include "LUrlParser.h"
-
-#include <algorithm>
-#include <cstring>
-#include <stdlib.h>
-
-// check if the scheme name is valid
-static bool IsSchemeValid( const std::string& SchemeName )
-{
- for ( auto c : SchemeName )
- {
- if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
- }
-
- return true;
-}
-
-bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
-{
- if ( !IsValid() ) { return false; }
-
- int Port = atoi( m_Port.c_str() );
-
- if ( Port <= 0 || Port > 65535 ) { return false; }
-
- if ( OutPort ) { *OutPort = Port; }
-
- return true;
-}
-
-// based on RFC 1738 and RFC 3986
-LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
-{
- LUrlParser::clParseURL Result;
-
- const char* CurrentString = URL.c_str();
-
- /*
- * <scheme>:<scheme-specific-part>
- * <scheme> := [a-z\+\-\.]+
- * For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
- */
-
- // try to read scheme
- {
- const char* LocalString = strchr( CurrentString, ':' );
-
- if ( !LocalString )
- {
- return clParseURL( LUrlParserError_NoUrlCharacter );
- }
-
- // save the scheme name
- Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
-
- if ( !IsSchemeValid( Result.m_Scheme ) )
- {
- return clParseURL( LUrlParserError_InvalidSchemeName );
- }
-
- // scheme should be lowercase
- std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
-
- // skip ':'
- CurrentString = LocalString+1;
- }
-
- /*
- * //<user>:<password>@<host>:<port>/<url-path>
- * any ":", "@" and "/" must be normalized
- */
-
- // skip "//"
- if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
- if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
-
- // check if the user name and password are specified
- bool bHasUserName = false;
-
- const char* LocalString = CurrentString;
-
- while ( *LocalString )
- {
- if ( *LocalString == '@' )
- {
- // user name and password are specified
- bHasUserName = true;
- break;
- }
- else if ( *LocalString == '/' )
- {
- // end of <host>:<port> specification
- bHasUserName = false;
- break;
- }
-
- LocalString++;
- }
-
- // user name and password
- LocalString = CurrentString;
-
- if ( bHasUserName )
- {
- // read user name
- while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
-
- Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
-
- // proceed with the current pointer
- CurrentString = LocalString;
-
- if ( *CurrentString == ':' )
- {
- // skip ':'
- CurrentString++;
-
- // read password
- LocalString = CurrentString;
-
- while ( *LocalString && *LocalString != '@' ) LocalString++;
-
- Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
- }
-
- // skip '@'
- if ( *CurrentString != '@' )
- {
- return clParseURL( LUrlParserError_NoAtSign );
- }
-
- CurrentString++;
- }
-
- bool bHasBracket = ( *CurrentString == '[' );
-
- // go ahead, read the host name
- LocalString = CurrentString;
-
- while ( *LocalString )
- {
- if ( bHasBracket && *LocalString == ']' )
- {
- // end of IPv6 address
- LocalString++;
- break;
- }
- else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
- {
- // port number is specified
- break;
- }
-
- LocalString++;
- }
-
- Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
-
- // is port number specified?
- if ( *CurrentString == ':' )
- {
- CurrentString++;
-
- // read port number
- LocalString = CurrentString;
-
- while ( *LocalString && *LocalString != '/' ) LocalString++;
-
- Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
- }
-
- // end of string
- if ( !*CurrentString )
- {
- Result.m_ErrorCode = LUrlParserError_Ok;
-
- return Result;
- }
-
- // skip '/'
- if ( *CurrentString != '/' )
- {
- return clParseURL( LUrlParserError_NoSlash );
- }
-
- CurrentString++;
-
- // parse the path
- LocalString = CurrentString;
-
- while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
-
- Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
-
- // check for query
- if ( *CurrentString == '?' )
- {
- // skip '?'
- CurrentString++;
-
- // read query
- LocalString = CurrentString;
-
- while ( *LocalString && *LocalString != '#' ) LocalString++;
-
- Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
- }
-
- // check for fragment
- if ( *CurrentString == '#' )
- {
- // skip '#'
- CurrentString++;
-
- // read fragment
- LocalString = CurrentString;
-
- while ( *LocalString ) LocalString++;
-
- Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
-
- CurrentString = LocalString;
- }
-
- Result.m_ErrorCode = LUrlParserError_Ok;
-
- return Result;
-}
diff --git a/externals/lurlparser/LUrlParser.h b/externals/lurlparser/LUrlParser.h
deleted file mode 100644
index 25d210981..000000000
--- a/externals/lurlparser/LUrlParser.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Lightweight URL & URI parser (RFC 1738, RFC 3986)
- * https://github.com/corporateshark/LUrlParser
- *
- * The MIT License (MIT)
- *
- * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#pragma once
-
-#include <string>
-
-namespace LUrlParser
-{
-enum LUrlParserError
-{
- LUrlParserError_Ok = 0,
- LUrlParserError_Uninitialized = 1,
- LUrlParserError_NoUrlCharacter = 2,
- LUrlParserError_InvalidSchemeName = 3,
- LUrlParserError_NoDoubleSlash = 4,
- LUrlParserError_NoAtSign = 5,
- LUrlParserError_UnexpectedEndOfLine = 6,
- LUrlParserError_NoSlash = 7,
-};
-
-class clParseURL
-{
-public:
- LUrlParserError m_ErrorCode;
- std::string m_Scheme;
- std::string m_Host;
- std::string m_Port;
- std::string m_Path;
- std::string m_Query;
- std::string m_Fragment;
- std::string m_UserName;
- std::string m_Password;
-
- clParseURL()
- : m_ErrorCode( LUrlParserError_Uninitialized )
- {}
-
- /// return 'true' if the parsing was successful
- bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
-
- /// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
- bool GetPort( int* OutPort ) const;
-
- /// parse the URL
- static clParseURL ParseURL( const std::string& URL );
-
-private:
- explicit clParseURL( LUrlParserError ErrorCode )
- : m_ErrorCode( ErrorCode )
- {}
-};
-
-} // namespace LUrlParser
diff --git a/externals/lurlparser/README.md b/externals/lurlparser/README.md
deleted file mode 100644
index be7f0135a..000000000
--- a/externals/lurlparser/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2
-
-MIT License
-
-===
-
-Lightweight URL & URI parser (RFC 1738, RFC 3986)
-
-(C) Sergey Kosarevsky, 2015
-
-@corporateshark sk@linderdaum.com
-
-http://www.linderdaum.com
-
-http://blog.linderdaum.com
-
-=============================
-
-A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++.
diff --git a/externals/microprofile/microprofile.h b/externals/microprofile/microprofile.h
index 6dae65a66..a06f6457d 100644
--- a/externals/microprofile/microprofile.h
+++ b/externals/microprofile/microprofile.h
@@ -902,8 +902,10 @@ inline uint16_t MicroProfileGetGroupIndex(MicroProfileToken t)
#include <windows.h>
#define snprintf _snprintf
+#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
+#endif
int64_t MicroProfileTicksPerSecondCpu()
{
static int64_t nTicksPerSecond = 0;
@@ -946,7 +948,11 @@ typedef HANDLE MicroProfileThread;
DWORD _stdcall ThreadTrampoline(void* pFunc)
{
MicroProfileThreadFunc F = (MicroProfileThreadFunc)pFunc;
- return (uint32_t)F(0);
+
+ // The return value of F will always return a void*, however, this is for
+ // compatibility with pthreads. The underlying "address" of the pointer
+ // is always a 32-bit value, so this cast is safe to perform.
+ return static_cast<DWORD>(reinterpret_cast<uint64_t>(F(0)));
}
inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func)
@@ -1037,7 +1043,7 @@ static void MicroProfileCreateThreadLogKey()
#else
MP_THREAD_LOCAL MicroProfileThreadLog* g_MicroProfileThreadLog = 0;
#endif
-static bool g_bUseLock = false; /// This is used because windows does not support using mutexes under dll init(which is where global initialization is handled)
+static std::atomic<bool> g_bUseLock{false}; /// This is used because windows does not support using mutexes under dll init(which is where global initialization is handled)
MICROPROFILE_DEFINE(g_MicroProfileFlip, "MicroProfile", "MicroProfileFlip", 0x3355ee);
@@ -1742,10 +1748,10 @@ void MicroProfileFlip()
}
}
}
- for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i)
+ for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j)
{
- pLog->nGroupTicks[i] += nGroupTicks[i];
- pFrameGroup[i] += nGroupTicks[i];
+ pLog->nGroupTicks[j] += nGroupTicks[j];
+ pFrameGroup[j] += nGroupTicks[j];
}
pLog->nStackPos = nStackPos;
}
@@ -3328,7 +3334,7 @@ bool MicroProfileIsLocalThread(uint32_t nThreadId)
#endif
#else
-bool MicroProfileIsLocalThread(uint32_t nThreadId){return false;}
+bool MicroProfileIsLocalThread([[maybe_unused]] uint32_t nThreadId) { return false; }
void MicroProfileStopContextSwitchTrace(){}
void MicroProfileStartContextSwitchTrace(){}
@@ -3576,7 +3582,7 @@ int MicroProfileGetGpuTickReference(int64_t* pOutCpu, int64_t* pOutGpu)
#undef S
-#ifdef _WIN32
+#ifdef _MSC_VER
#pragma warning(pop)
#endif
diff --git a/externals/unicorn b/externals/unicorn
deleted file mode 160000
-Subproject 73f45735354396766a4bfb26d0b96b06e5cf31b
diff --git a/externals/xbyak b/externals/xbyak
-Subproject 18c9caaa0a3ed5706c39f5aa86cce0db6e65b17
+Subproject c306b8e5786eeeb87b8925a8af5c3bf057ff5a9
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 71efbb40d..1cfd3bbc9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,7 +32,6 @@ if (MSVC)
# /Zc:inline - Let codegen omit inline functions in object files
# /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null
add_compile_options(
- /W3
/MP
/Zi
/Zo
@@ -43,6 +42,18 @@ if (MSVC)
/Zc:externConstexpr
/Zc:inline
/Zc:throwingNew
+
+ # Warnings
+ /W3
+ /we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled
+ /we4101 # 'identifier': unreferenced local variable
+ /we4265 # 'class': class has virtual functions, but destructor is not virtual
+ /we4388 # signed/unsigned mismatch
+ /we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect
+ /we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'?
+ /we4555 # Expression has no effect; expected expression with side-effect
+ /we4834 # Discarding return value of function with 'nodiscard' attribute
+ /we5038 # data member 'member1' will be initialized after data member 'member2'
)
# /GS- - No stack buffer overflow checks
@@ -53,12 +64,20 @@ if (MSVC)
else()
add_compile_options(
-Wall
+ -Werror=array-bounds
-Werror=implicit-fallthrough
-Werror=missing-declarations
+ -Werror=missing-field-initializers
-Werror=reorder
+ -Werror=switch
+ -Werror=uninitialized
+ -Werror=unused-function
+ -Werror=unused-result
+ -Werror=unused-variable
-Wextra
-Wmissing-declarations
-Wno-attributes
+ -Wno-invalid-offsetof
-Wno-unused-parameter
)
@@ -113,7 +132,6 @@ add_subdirectory(tests)
if (ENABLE_SDL2)
add_subdirectory(yuzu_cmd)
- add_subdirectory(yuzu_tester)
endif()
if (ENABLE_QT)
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 5ef38a337..a0ae07752 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -12,22 +12,58 @@ add_library(audio_core STATIC
buffer.h
codec.cpp
codec.h
+ command_generator.cpp
+ command_generator.h
common.h
+ delay_line.cpp
+ delay_line.h
+ effect_context.cpp
+ effect_context.h
+ info_updater.cpp
+ info_updater.h
+ memory_pool.cpp
+ memory_pool.h
+ mix_context.cpp
+ mix_context.h
null_sink.h
sink.h
+ sink_context.cpp
+ sink_context.h
sink_details.cpp
sink_details.h
sink_stream.h
+ splitter_context.cpp
+ splitter_context.h
stream.cpp
stream.h
time_stretch.cpp
time_stretch.h
+ voice_context.cpp
+ voice_context.h
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
)
create_target_directory_groups(audio_core)
+if (NOT MSVC)
+ target_compile_options(audio_core PRIVATE
+ -Werror=conversion
+ -Werror=ignored-qualifiers
+ -Werror=implicit-fallthrough
+ -Werror=reorder
+ -Werror=sign-compare
+ -Werror=shadow
+ -Werror=unused-parameter
+ -Werror=unused-variable
+
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
+
+ -Wno-sign-conversion
+ )
+endif()
+
target_link_libraries(audio_core PUBLIC common core)
target_link_libraries(audio_core PRIVATE SoundTouch)
diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp
index f65bf64f7..01b8dff6b 100644
--- a/src/audio_core/algorithm/filter.cpp
+++ b/src/audio_core/algorithm/filter.cpp
@@ -31,8 +31,8 @@ Filter Filter::LowPass(double cutoff, double Q) {
Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {}
-Filter::Filter(double a0, double a1, double a2, double b0, double b1, double b2)
- : a1(a1 / a0), a2(a2 / a0), b0(b0 / a0), b1(b1 / a0), b2(b2 / a0) {}
+Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_)
+ : a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {}
void Filter::Process(std::vector<s16>& signal) {
const std::size_t num_frames = signal.size() / 2;
@@ -55,7 +55,8 @@ void Filter::Process(std::vector<s16>& signal) {
/// @param total_count The total number of biquads to be cascaded.
/// @param index 0-index of the biquad to calculate the Q value for.
static double CascadingBiquadQ(std::size_t total_count, std::size_t index) {
- const double pole = M_PI * (2 * index + 1) / (4.0 * total_count);
+ const auto pole =
+ M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count));
return 1.0 / (2.0 * std::cos(pole));
}
@@ -68,7 +69,7 @@ CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size
}
CascadingFilter::CascadingFilter() = default;
-CascadingFilter::CascadingFilter(std::vector<Filter> filters) : filters(std::move(filters)) {}
+CascadingFilter::CascadingFilter(std::vector<Filter> filters_) : filters(std::move(filters_)) {}
void CascadingFilter::Process(std::vector<s16>& signal) {
for (auto& filter : filters) {
diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h
index 3546d149b..a291fe79b 100644
--- a/src/audio_core/algorithm/filter.h
+++ b/src/audio_core/algorithm/filter.h
@@ -25,7 +25,7 @@ public:
/// Passthrough filter.
Filter();
- Filter(double a0, double a1, double a2, double b0, double b1, double b2);
+ Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_);
void Process(std::vector<s16>& signal);
@@ -51,7 +51,7 @@ public:
/// Passthrough.
CascadingFilter();
- explicit CascadingFilter(std::vector<Filter> filters);
+ explicit CascadingFilter(std::vector<Filter> filters_);
void Process(std::vector<s16>& signal);
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp
index 49ab9d3e1..3b4144e21 100644
--- a/src/audio_core/algorithm/interpolate.cpp
+++ b/src/audio_core/algorithm/interpolate.cpp
@@ -146,7 +146,7 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
return {};
if (ratio <= 0) {
- LOG_CRITICAL(Audio, "Nonsensical interpolation ratio {}", ratio);
+ LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio);
return input;
}
@@ -164,7 +164,8 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
const std::size_t num_frames{input.size() / 2};
std::vector<s16> output;
- output.reserve(static_cast<std::size_t>(input.size() / ratio + InterpolationState::taps));
+ output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio +
+ InterpolationState::taps));
for (std::size_t frame{}; frame < num_frames; ++frame) {
const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps};
@@ -197,4 +198,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
return output;
}
+void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
+ const std::array<s16, 512>& lut = [pitch] {
+ if (pitch > 0xaaaa) {
+ return curve_lut0;
+ }
+ if (pitch <= 0x8000) {
+ return curve_lut1;
+ }
+ return curve_lut2;
+ }();
+
+ std::size_t index{};
+
+ for (std::size_t i = 0; i < sample_count; i++) {
+ const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
+ const auto l0 = lut[lut_index + 0];
+ const auto l1 = lut[lut_index + 1];
+ const auto l2 = lut[lut_index + 2];
+ const auto l3 = lut[lut_index + 3];
+
+ const auto s0 = static_cast<s32>(input[index + 0]);
+ const auto s1 = static_cast<s32>(input[index + 1]);
+ const auto s2 = static_cast<s32>(input[index + 2]);
+ const auto s3 = static_cast<s32>(input[index + 3]);
+
+ output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
+ fraction += pitch;
+ index += (fraction >> 15);
+ fraction &= 0x7fff;
+ }
+}
+
} // namespace AudioCore
diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h
index ab1a31754..d534077af 100644
--- a/src/audio_core/algorithm/interpolate.h
+++ b/src/audio_core/algorithm/interpolate.h
@@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16>
return Interpolate(state, std::move(input), ratio);
}
+/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
+void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
+
} // namespace AudioCore
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
index 8619a3f03..fe3a898ad 100644
--- a/src/audio_core/audio_out.cpp
+++ b/src/audio_core/audio_out.cpp
@@ -43,6 +43,10 @@ std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream,
return stream->GetTagsAndReleaseBuffers(max_count);
}
+std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) {
+ return stream->GetTagsAndReleaseBuffers();
+}
+
void AudioOut::StartStream(StreamPtr stream) {
stream->Play();
}
diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h
index b07588287..6ce08cd0d 100644
--- a/src/audio_core/audio_out.h
+++ b/src/audio_core/audio_out.h
@@ -31,6 +31,9 @@ public:
/// Returns a vector of recently released buffers specified by tag for the specified stream
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count);
+ /// Returns a vector of all recently released buffers specified by tag for the specified stream
+ std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream);
+
/// Starts an audio stream for playback
void StartStream(StreamPtr stream);
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index d64452617..d2ce8c814 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -2,95 +2,96 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "audio_core/algorithm/interpolate.h"
+#include <limits>
+#include <vector>
+
#include "audio_core/audio_out.h"
#include "audio_core/audio_renderer.h"
-#include "audio_core/codec.h"
#include "audio_core/common.h"
-#include "common/assert.h"
+#include "audio_core/info_updater.h"
+#include "audio_core/voice_context.h"
#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/memory.h"
+#include "core/settings.h"
-namespace AudioCore {
+namespace {
+[[nodiscard]] static constexpr s16 ClampToS16(s32 value) {
+ return static_cast<s16>(std::clamp(value, s32{std::numeric_limits<s16>::min()},
+ s32{std::numeric_limits<s16>::max()}));
+}
-constexpr u32 STREAM_SAMPLE_RATE{48000};
-constexpr u32 STREAM_NUM_CHANNELS{2};
-using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
-class AudioRenderer::VoiceState {
-public:
- bool IsPlaying() const {
- return is_in_use && info.play_state == PlayState::Started;
- }
+[[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) {
+ // Mix 50% from left and 50% from right channel
+ constexpr float l_mix_amount = 50.0f / 100.0f;
+ constexpr float r_mix_amount = 50.0f / 100.0f;
+ return ClampToS16(static_cast<s32>((static_cast<float>(l_channel) * l_mix_amount) +
+ (static_cast<float>(r_channel) * r_mix_amount)));
+}
- const VoiceOutStatus& GetOutStatus() const {
- return out_status;
- }
+[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2(s16 fl_channel, s16 fr_channel,
+ s16 fc_channel,
+ [[maybe_unused]] s16 lf_channel,
+ s16 bl_channel, s16 br_channel) {
+ // Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels
+ // are mixed to be 36.94%
- const VoiceInfo& GetInfo() const {
- return info;
- }
+ constexpr float front_mix_amount = 36.94f / 100.0f;
+ constexpr float center_mix_amount = 26.12f / 100.0f;
+ constexpr float back_mix_amount = 36.94f / 100.0f;
- VoiceInfo& GetInfo() {
- return info;
- }
+ // Mix 50% from left and 50% from right channel
+ const auto left = front_mix_amount * static_cast<float>(fl_channel) +
+ center_mix_amount * static_cast<float>(fc_channel) +
+ back_mix_amount * static_cast<float>(bl_channel);
- void SetWaveIndex(std::size_t index);
- std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
- const VoiceChannelHolder& voice_resources);
- void UpdateState();
- void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
-
-private:
- bool is_in_use{};
- bool is_refresh_pending{};
- std::size_t wave_index{};
- std::size_t offset{};
- Codec::ADPCMState adpcm_state{};
- InterpolationState interp_state{};
- std::vector<s16> samples;
- VoiceOutStatus out_status{};
- VoiceInfo info{};
-};
-
-class AudioRenderer::EffectState {
-public:
- const EffectOutStatus& GetOutStatus() const {
- return out_status;
- }
+ const auto right = front_mix_amount * static_cast<float>(fr_channel) +
+ center_mix_amount * static_cast<float>(fc_channel) +
+ back_mix_amount * static_cast<float>(br_channel);
- const EffectInStatus& GetInfo() const {
- return info;
- }
+ return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
+}
- EffectInStatus& GetInfo() {
- return info;
- }
+[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2WithCoefficients(
+ s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel,
+ const std::array<float_le, 4>& coeff) {
+ const auto left =
+ static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
+ static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[0];
+
+ const auto right =
+ static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
+ static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[0];
- void UpdateState(Core::Memory::Memory& memory);
+ return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
+}
-private:
- EffectOutStatus out_status{};
- EffectInStatus info{};
-};
+} // namespace
+namespace AudioCore {
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
- AudioRendererParameter params,
- std::shared_ptr<Kernel::WritableEvent> buffer_event,
+ AudioCommon::AudioRendererParameter params,
+ Stream::ReleaseCallback&& release_callback,
std::size_t instance_number)
- : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
- voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} {
+ : worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4),
+ voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
+ sink_context(params.sink_count), splitter_context(),
+ voices(params.voice_count), memory{memory_},
+ command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
+ memory) {
behavior_info.SetUserRevision(params.revision);
+ splitter_context.Initialize(behavior_info, params.splitter_count,
+ params.num_splitter_send_channels);
+ mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
audio_out = std::make_unique<AudioCore::AudioOut>();
- stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
- fmt::format("AudioRenderer-Instance{}", instance_number),
- [=]() { buffer_event->Signal(); });
+ stream = audio_out->OpenStream(
+ core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
+ fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
audio_out->StartStream(stream);
QueueMixedBuffer(0);
QueueMixedBuffer(1);
QueueMixedBuffer(2);
+ QueueMixedBuffer(3);
}
AudioRenderer::~AudioRenderer() = default;
@@ -111,359 +112,210 @@ Stream::State AudioRenderer::GetStreamState() const {
return stream->GetState();
}
-ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
- // Copy UpdateDataHeader struct
- UpdateDataHeader config{};
- std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
- u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
+ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
+ std::vector<u8>& output_params) {
+
+ InfoUpdater info_updater{input_params, output_params, behavior_info};
- if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) {
+ if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
- return Audren::ERR_INVALID_PARAMETERS;
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- // Copy MemoryPoolInfo structs
- std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
- std::memcpy(mem_pool_info.data(),
- input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
- memory_pool_count * sizeof(MemoryPoolInfo));
-
- // Copy voice resources
- const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
- config.memory_pools_size};
- std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
- sizeof(VoiceResourceInformation) * voice_resources.size());
-
- // Copy VoiceInfo structs
- std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
- config.memory_pools_size + config.voice_resource_size};
- for (auto& voice : voices) {
- std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
- voice_offset += sizeof(VoiceInfo);
+ if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
+ LOG_ERROR(Audio, "Failed to update memory pool parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
- config.memory_pools_size + config.voice_resource_size +
- config.voices_size};
- for (auto& effect : effects) {
- std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
- effect_offset += sizeof(EffectInStatus);
+ if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
+ LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- // Update memory pool state
- std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
- for (std::size_t index = 0; index < memory_pool.size(); ++index) {
- if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
- memory_pool[index].state = MemoryPoolStates::Attached;
- } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
- memory_pool[index].state = MemoryPoolStates::Detached;
- }
+ if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
+ LOG_ERROR(Audio, "Failed to update voice parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- // Update voices
- for (auto& voice : voices) {
- voice.UpdateState();
- if (!voice.GetInfo().is_in_use) {
- continue;
- }
- if (voice.GetInfo().is_new) {
- voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
- }
+ // TODO(ogniK): Deal with stopped audio renderer but updates still taking place
+ if (!info_updater.UpdateEffects(effect_context, true)) {
+ LOG_ERROR(Audio, "Failed to update effect parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- for (auto& effect : effects) {
- effect.UpdateState(memory);
+ if (behavior_info.IsSplitterSupported()) {
+ if (!info_updater.UpdateSplitterInfo(splitter_context)) {
+ LOG_ERROR(Audio, "Failed to update splitter parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
}
- // Release previous buffers and queue next ones for playback
- ReleaseAndQueueBuffers();
+ const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
+ splitter_context, effect_context);
- // Copy output header
- UpdateDataHeader response_data{worker_params};
- if (behavior_info.IsElapsedFrameCountSupported()) {
- response_data.render_info = sizeof(RendererInfo);
- response_data.total_size += sizeof(RendererInfo);
+ if (mix_result.IsError()) {
+ LOG_ERROR(Audio, "Failed to update mix parameters");
+ return mix_result;
}
- std::vector<u8> output_params(response_data.total_size);
- std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
-
- // Copy output memory pool entries
- std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
- response_data.memory_pools_size);
-
- // Copy output voice status
- std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
- for (const auto& voice : voices) {
- std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
- sizeof(VoiceOutStatus));
- voice_out_status_offset += sizeof(VoiceOutStatus);
+ // TODO(ogniK): Sinks
+ if (!info_updater.UpdateSinks(sink_context)) {
+ LOG_ERROR(Audio, "Failed to update sink parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- std::size_t effect_out_status_offset{
- sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
- response_data.voice_resource_size};
- for (const auto& effect : effects) {
- std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
- sizeof(EffectOutStatus));
- effect_out_status_offset += sizeof(EffectOutStatus);
+ // TODO(ogniK): Performance buffer
+ if (!info_updater.UpdatePerformanceBuffer()) {
+ LOG_ERROR(Audio, "Failed to update performance buffer parameters");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- // Update behavior info output
- const std::size_t behavior_out_status_offset{
- sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
- response_data.effects_size + response_data.sinks_size +
- response_data.performance_manager_size};
-
- if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) {
- LOG_ERROR(Audio, "Failed to update behavior info output parameters");
- return Audren::ERR_INVALID_PARAMETERS;
+ if (!info_updater.UpdateErrorInfo(behavior_info)) {
+ LOG_ERROR(Audio, "Failed to update error info");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (behavior_info.IsElapsedFrameCountSupported()) {
- const std::size_t renderer_info_offset{
- sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
- response_data.effects_size + response_data.sinks_size +
- response_data.performance_manager_size + response_data.behavior_size};
- RendererInfo renderer_info{};
- renderer_info.elasped_frame_count = elapsed_frame_count;
- std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
- sizeof(RendererInfo));
- }
-
- return MakeResult(output_params);
-}
-
-void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
- wave_index = index & 3;
- is_refresh_pending = true;
-}
-
-std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
- std::size_t sample_count, Core::Memory::Memory& memory,
- const VoiceChannelHolder& voice_resources) {
- if (!IsPlaying()) {
- return {};
- }
-
- if (is_refresh_pending) {
- RefreshBuffer(memory, voice_resources);
- }
-
- const std::size_t max_size{samples.size() - offset};
- const std::size_t dequeue_offset{offset};
- std::size_t size{sample_count * STREAM_NUM_CHANNELS};
- if (size > max_size) {
- size = max_size;
- }
-
- out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
- offset += size;
-
- const auto& wave_buffer{info.wave_buffer[wave_index]};
- if (offset == samples.size()) {
- offset = 0;
-
- if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
- SetWaveIndex(wave_index + 1);
- }
-
- if (wave_buffer.buffer_sz) {
- out_status.wave_buffer_consumed++;
- }
-
- if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
- info.play_state = PlayState::Paused;
+ if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
+ LOG_ERROR(Audio, "Failed to update renderer info");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
+ // TODO(ogniK): Statistics
- return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
-}
-
-void AudioRenderer::VoiceState::UpdateState() {
- if (is_in_use && !info.is_in_use) {
- // No longer in use, reset state
- is_refresh_pending = true;
- wave_index = 0;
- offset = 0;
- out_status = {};
+ if (!info_updater.WriteOutputHeader()) {
+ LOG_ERROR(Audio, "Failed to write output header");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- is_in_use = info.is_in_use;
-}
-void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory,
- const VoiceChannelHolder& voice_resources) {
- const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr;
- const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz;
- std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
- memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
-
- switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
- case Codec::PcmFormat::Int16: {
- // PCM16 is played as-is
- break;
- }
- case Codec::PcmFormat::Adpcm: {
- // Decode ADPCM to PCM16
- Codec::ADPCM_Coeff coeffs;
- memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
- new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
- new_samples.size() * sizeof(s16), coeffs, adpcm_state);
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
- break;
- }
-
- switch (info.channel_count) {
- case 1: {
- // 1 channel is upsampled to 2 channel
- samples.resize(new_samples.size() * 2);
+ // TODO(ogniK): Check when all sections are implemented
- for (std::size_t index = 0; index < new_samples.size(); ++index) {
- auto sample = static_cast<float>(new_samples[index]);
- if (voice_resources[0]->in_use) {
- sample *= voice_resources[0]->mix_volumes[0];
- }
-
- samples[index * 2] = static_cast<s16>(sample * info.volume);
- samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
- }
- break;
+ if (!info_updater.CheckConsumedSize()) {
+ LOG_ERROR(Audio, "Audio buffers were not consumed!");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
- case 2: {
- // 2 channel is played as is
- samples = std::move(new_samples);
- const std::size_t sample_count = (samples.size() / 2);
- for (std::size_t index = 0; index < sample_count; ++index) {
- const std::size_t index_l = index * 2;
- const std::size_t index_r = index * 2 + 1;
-
- auto sample_l = static_cast<float>(samples[index_l]);
- auto sample_r = static_cast<float>(samples[index_r]);
-
- if (voice_resources[0]->in_use) {
- sample_l *= voice_resources[0]->mix_volumes[0];
- }
-
- if (voice_resources[1]->in_use) {
- sample_r *= voice_resources[1]->mix_volumes[1];
- }
- samples[index_l] = static_cast<s16>(sample_l * info.volume);
- samples[index_r] = static_cast<s16>(sample_r * info.volume);
- }
- break;
- }
- case 6: {
- samples.resize((new_samples.size() / 6) * 2);
- const std::size_t sample_count = samples.size() / 2;
-
- for (std::size_t index = 0; index < sample_count; ++index) {
- auto FL = static_cast<float>(new_samples[index * 6]);
- auto FR = static_cast<float>(new_samples[index * 6 + 1]);
- auto FC = static_cast<float>(new_samples[index * 6 + 2]);
- auto BL = static_cast<float>(new_samples[index * 6 + 4]);
- auto BR = static_cast<float>(new_samples[index * 6 + 5]);
-
- if (voice_resources[0]->in_use) {
- FL *= voice_resources[0]->mix_volumes[0];
- }
- if (voice_resources[1]->in_use) {
- FR *= voice_resources[1]->mix_volumes[1];
- }
- if (voice_resources[2]->in_use) {
- FC *= voice_resources[2]->mix_volumes[2];
- }
- if (voice_resources[4]->in_use) {
- BL *= voice_resources[4]->mix_volumes[4];
- }
- if (voice_resources[5]->in_use) {
- BR *= voice_resources[5]->mix_volumes[5];
- }
-
- samples[index * 2] =
- static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume);
- samples[index * 2 + 1] =
- static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume);
- }
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
- break;
- }
-
- // Only interpolate when necessary, expensive.
- if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
- samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
- STREAM_SAMPLE_RATE);
- }
-
- is_refresh_pending = false;
-}
-
-void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) {
- if (info.is_new) {
- out_status.state = EffectStatus::New;
- } else {
- if (info.type == Effect::Aux) {
- ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
- "Aux buffers tried to update");
- ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
- "Aux buffers tried to update");
- ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
- "Aux buffers tried to update");
- ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
- "Aux buffers tried to update");
- }
- }
-}
+ ReleaseAndQueueBuffers();
-static constexpr s16 ClampToS16(s32 value) {
- return static_cast<s16>(std::clamp(value, -32768, 32767));
+ return RESULT_SUCCESS;
}
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
- constexpr std::size_t BUFFER_SIZE{512};
+ command_generator.PreCommand();
+ // Clear mix buffers before our next operation
+ command_generator.ClearMixBuffers();
+
+ // If the splitter is not in use, sort our mixes
+ if (!splitter_context.UsingSplitter()) {
+ mix_context.SortInfo();
+ }
+ // Sort our voices
+ voice_context.SortInfo();
+
+ // Handle samples
+ command_generator.GenerateVoiceCommands();
+ command_generator.GenerateSubMixCommands();
+ command_generator.GenerateFinalMixCommands();
+
+ command_generator.PostCommand();
+ // Base sample size
+ std::size_t BUFFER_SIZE{worker_params.sample_count};
+ // Samples
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
-
- for (auto& voice : voices) {
- if (!voice.IsPlaying()) {
- continue;
- }
- VoiceChannelHolder resources{};
- for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) {
- const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel];
- resources[channel] = &voice_resources[channel_resource_id];
+ // Make sure to clear our samples
+ std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
+
+ if (sink_context.InUse()) {
+ const auto stream_channel_count = stream->GetNumChannels();
+ const auto buffer_offsets = sink_context.OutputBuffers();
+ const auto channel_count = buffer_offsets.size();
+ const auto& final_mix = mix_context.GetFinalMixInfo();
+ const auto& in_params = final_mix.GetInParams();
+ std::vector<s32*> mix_buffers(channel_count);
+ for (std::size_t i = 0; i < channel_count; i++) {
+ mix_buffers[i] =
+ command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
}
- std::size_t offset{};
- s64 samples_remaining{BUFFER_SIZE};
- while (samples_remaining > 0) {
- const std::vector<s16> samples{
- voice.DequeueSamples(samples_remaining, memory, resources)};
-
- if (samples.empty()) {
- break;
- }
-
- samples_remaining -= samples.size() / stream->GetNumChannels();
-
- for (const auto& sample : samples) {
- const s32 buffer_sample{buffer[offset]};
- buffer[offset++] =
- ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
+ for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
+ if (channel_count == 1) {
+ const auto sample = ClampToS16(mix_buffers[0][i]);
+
+ // Place sample in all channels
+ for (u32 channel = 0; channel < stream_channel_count; channel++) {
+ buffer[i * stream_channel_count + channel] = sample;
+ }
+
+ if (stream_channel_count == 6) {
+ // Output stream has a LF channel, mute it!
+ buffer[i * stream_channel_count + 3] = 0;
+ }
+
+ } else if (channel_count == 2) {
+ const auto l_sample = ClampToS16(mix_buffers[0][i]);
+ const auto r_sample = ClampToS16(mix_buffers[1][i]);
+ if (stream_channel_count == 1) {
+ buffer[i * stream_channel_count + 0] = Mix2To1(l_sample, r_sample);
+ } else if (stream_channel_count == 2) {
+ buffer[i * stream_channel_count + 0] = l_sample;
+ buffer[i * stream_channel_count + 1] = r_sample;
+ } else if (stream_channel_count == 6) {
+ buffer[i * stream_channel_count + 0] = l_sample;
+ buffer[i * stream_channel_count + 1] = r_sample;
+
+ // Combine both left and right channels to the center channel
+ buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample);
+
+ buffer[i * stream_channel_count + 4] = l_sample;
+ buffer[i * stream_channel_count + 5] = r_sample;
+ }
+
+ } else if (channel_count == 6) {
+ const auto fl_sample = ClampToS16(mix_buffers[0][i]);
+ const auto fr_sample = ClampToS16(mix_buffers[1][i]);
+ const auto fc_sample = ClampToS16(mix_buffers[2][i]);
+ const auto lf_sample = ClampToS16(mix_buffers[3][i]);
+ const auto bl_sample = ClampToS16(mix_buffers[4][i]);
+ const auto br_sample = ClampToS16(mix_buffers[5][i]);
+
+ if (stream_channel_count == 1) {
+ // Games seem to ignore the center channel half the time, we use the front left
+ // and right channel for mixing as that's where majority of the audio goes
+ buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample);
+ } else if (stream_channel_count == 2) {
+ // Mix all channels into 2 channels
+ if (sink_context.HasDownMixingCoefficients()) {
+ const auto [left, right] = Mix6To2WithCoefficients(
+ fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample,
+ sink_context.GetDownmixCoefficients());
+ buffer[i * stream_channel_count + 0] = left;
+ buffer[i * stream_channel_count + 1] = right;
+ } else {
+ const auto [left, right] = Mix6To2(fl_sample, fr_sample, fc_sample,
+ lf_sample, bl_sample, br_sample);
+ buffer[i * stream_channel_count + 0] = left;
+ buffer[i * stream_channel_count + 1] = right;
+ }
+ } else if (stream_channel_count == 6) {
+ // Pass through
+ buffer[i * stream_channel_count + 0] = fl_sample;
+ buffer[i * stream_channel_count + 1] = fr_sample;
+ buffer[i * stream_channel_count + 2] = fc_sample;
+ buffer[i * stream_channel_count + 3] = lf_sample;
+ buffer[i * stream_channel_count + 4] = bl_sample;
+ buffer[i * stream_channel_count + 5] = br_sample;
+ }
}
}
}
+
audio_out->QueueBuffer(stream, tag, std::move(buffer));
elapsed_frame_count++;
+ voice_context.UpdateStateByDspShared();
}
void AudioRenderer::ReleaseAndQueueBuffers() {
- const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream, 2)};
+ const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
for (const auto& tag : released_buffers) {
QueueMixedBuffer(tag);
}
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index f0b691a86..18567f618 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -9,261 +9,64 @@
#include <vector>
#include "audio_core/behavior_info.h"
+#include "audio_core/command_generator.h"
#include "audio_core/common.h"
+#include "audio_core/effect_context.h"
+#include "audio_core/memory_pool.h"
+#include "audio_core/mix_context.h"
+#include "audio_core/sink_context.h"
+#include "audio_core/splitter_context.h"
#include "audio_core/stream.h"
+#include "audio_core/voice_context.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/hle/kernel/object.h"
#include "core/hle/result.h"
namespace Core::Timing {
class CoreTiming;
}
-namespace Kernel {
-class WritableEvent;
-}
-
namespace Core::Memory {
class Memory;
}
namespace AudioCore {
+using DSPStateHolder = std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>;
class AudioOut;
-enum class PlayState : u8 {
- Started = 0,
- Stopped = 1,
- Paused = 2,
-};
-
-enum class Effect : u8 {
- None = 0,
- Aux = 2,
-};
-
-enum class EffectStatus : u8 {
- None = 0,
- New = 1,
-};
-
-struct AudioRendererParameter {
- u32_le sample_rate;
- u32_le sample_count;
- u32_le mix_buffer_count;
- u32_le submix_count;
- u32_le voice_count;
- u32_le sink_count;
- u32_le effect_count;
- u32_le performance_frame_count;
- u8 is_voice_drop_enabled;
- u8 unknown_21;
- u8 unknown_22;
- u8 execution_mode;
- u32_le splitter_count;
- u32_le num_splitter_send_channels;
- u32_le unknown_30;
- u32_le revision;
-};
-static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
-
-enum class MemoryPoolStates : u32 { // Should be LE
- Invalid = 0x0,
- Unknown = 0x1,
- RequestDetach = 0x2,
- Detached = 0x3,
- RequestAttach = 0x4,
- Attached = 0x5,
- Released = 0x6,
-};
-
-struct MemoryPoolEntry {
- MemoryPoolStates state;
- u32_le unknown_4;
- u32_le unknown_8;
- u32_le unknown_c;
-};
-static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
-
-struct MemoryPoolInfo {
- u64_le pool_address;
- u64_le pool_size;
- MemoryPoolStates pool_state;
- INSERT_PADDING_WORDS(3); // Unknown
-};
-static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
-struct BiquadFilter {
- u8 enable;
- INSERT_PADDING_BYTES(1);
- std::array<s16_le, 3> numerator;
- std::array<s16_le, 2> denominator;
-};
-static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
-
-struct WaveBuffer {
- u64_le buffer_addr;
- u64_le buffer_sz;
- s32_le start_sample_offset;
- s32_le end_sample_offset;
- u8 is_looping;
- u8 end_of_stream;
- u8 sent_to_server;
- INSERT_PADDING_BYTES(5);
- u64 context_addr;
- u64 context_sz;
- INSERT_PADDING_BYTES(8);
-};
-static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
-
-struct VoiceResourceInformation {
- s32_le id{};
- std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
- bool in_use{};
- INSERT_PADDING_BYTES(11);
-};
-static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
-
-struct VoiceInfo {
- u32_le id;
- u32_le node_id;
- u8 is_new;
- u8 is_in_use;
- PlayState play_state;
- u8 sample_format;
- u32_le sample_rate;
- u32_le priority;
- u32_le sorting_order;
- u32_le channel_count;
- float_le pitch;
- float_le volume;
- std::array<BiquadFilter, 2> biquad_filter;
- u32_le wave_buffer_count;
- u32_le wave_buffer_head;
- INSERT_PADDING_WORDS(1);
- u64_le additional_params_addr;
- u64_le additional_params_sz;
- u32_le mix_id;
- u32_le splitter_info_id;
- std::array<WaveBuffer, 4> wave_buffer;
- std::array<u32_le, 6> voice_channel_resource_ids;
- INSERT_PADDING_BYTES(24);
-};
-static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
-
-struct VoiceOutStatus {
- u64_le played_sample_count;
- u32_le wave_buffer_consumed;
- u32_le voice_drops_count;
-};
-static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
-
-struct AuxInfo {
- std::array<u8, 24> input_mix_buffers;
- std::array<u8, 24> output_mix_buffers;
- u32_le mix_buffer_count;
- u32_le sample_rate; // Stored in the aux buffer currently
- u32_le sample_count;
- u64_le send_buffer_info;
- u64_le send_buffer_base;
-
- u64_le return_buffer_info;
- u64_le return_buffer_base;
-};
-static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
-
-struct EffectInStatus {
- Effect type;
- u8 is_new;
- u8 is_enabled;
- INSERT_PADDING_BYTES(1);
- u32_le mix_id;
- u64_le buffer_base;
- u64_le buffer_sz;
- s32_le priority;
- INSERT_PADDING_BYTES(4);
- union {
- std::array<u8, 0xa0> raw;
- AuxInfo aux_info;
- };
-};
-static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
-
-struct EffectOutStatus {
- EffectStatus state;
- INSERT_PADDING_BYTES(0xf);
-};
-static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
-
-struct RendererInfo {
- u64_le elasped_frame_count{};
- INSERT_PADDING_WORDS(2);
-};
-static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
-
-struct UpdateDataHeader {
- UpdateDataHeader() {}
-
- explicit UpdateDataHeader(const AudioRendererParameter& config) {
- revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
- behavior_size = 0xb0;
- memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
- voices_size = config.voice_count * 0x10;
- voice_resource_size = 0x0;
- effects_size = config.effect_count * 0x10;
- mixes_size = 0x0;
- sinks_size = config.sink_count * 0x20;
- performance_manager_size = 0x10;
- render_info = 0;
- total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
- effects_size + sinks_size + performance_manager_size;
- }
-
- u32_le revision{};
- u32_le behavior_size{};
- u32_le memory_pools_size{};
- u32_le voices_size{};
- u32_le voice_resource_size{};
- u32_le effects_size{};
- u32_le mixes_size{};
- u32_le sinks_size{};
- u32_le performance_manager_size{};
- u32_le splitter_size{};
- u32_le render_info{};
- INSERT_PADDING_WORDS(4);
- u32_le total_size{};
-};
-static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
-
class AudioRenderer {
public:
AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
- AudioRendererParameter params,
- std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
+ AudioCommon::AudioRendererParameter params,
+ Stream::ReleaseCallback&& release_callback, std::size_t instance_number);
~AudioRenderer();
- ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params);
+ [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
+ std::vector<u8>& output_params);
void QueueMixedBuffer(Buffer::Tag tag);
void ReleaseAndQueueBuffers();
- u32 GetSampleRate() const;
- u32 GetSampleCount() const;
- u32 GetMixBufferCount() const;
- Stream::State GetStreamState() const;
+ [[nodiscard]] u32 GetSampleRate() const;
+ [[nodiscard]] u32 GetSampleCount() const;
+ [[nodiscard]] u32 GetMixBufferCount() const;
+ [[nodiscard]] Stream::State GetStreamState() const;
private:
- class EffectState;
- class VoiceState;
BehaviorInfo behavior_info{};
- AudioRendererParameter worker_params;
- std::shared_ptr<Kernel::WritableEvent> buffer_event;
+ AudioCommon::AudioRendererParameter worker_params;
+ std::vector<ServerMemoryPoolInfo> memory_pool_info;
+ VoiceContext voice_context;
+ EffectContext effect_context;
+ MixContext mix_context;
+ SinkContext sink_context;
+ SplitterContext splitter_context;
std::vector<VoiceState> voices;
- std::vector<VoiceResourceInformation> voice_resources;
- std::vector<EffectState> effects;
std::unique_ptr<AudioOut> audio_out;
StreamPtr stream;
Core::Memory::Memory& memory;
+ CommandGenerator command_generator;
std::size_t elapsed_frame_count{};
};
diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp
index 94b7a3bf1..3c2e3e6f1 100644
--- a/src/audio_core/behavior_info.cpp
+++ b/src/audio_core/behavior_info.cpp
@@ -9,39 +9,11 @@
namespace AudioCore {
-BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {}
+BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
BehaviorInfo::~BehaviorInfo() = default;
-bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) {
- if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) {
- LOG_ERROR(Audio, "Buffer is an invalid size!");
- return false;
- }
- InParams params{};
- std::memcpy(&params, buffer.data() + offset, sizeof(InParams));
-
- if (!IsValidRevision(params.revision)) {
- LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision);
- return false;
- }
-
- if (user_revision != params.revision) {
- LOG_ERROR(Audio,
- "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
- user_revision, params.revision);
- return false;
- }
-
- ClearError();
- UpdateFlags(params.flags);
-
- // TODO(ogniK): Check input params size when InfoUpdater is used
-
- return true;
-}
-
bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
- if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
+ if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
@@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) {
user_revision = revision;
}
+u32_le BehaviorInfo::GetUserRevision() const {
+ return user_revision;
+}
+
+u32_le BehaviorInfo::GetProcessRevision() const {
+ return process_revision;
+}
+
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
- return IsRevisionSupported(2, user_revision);
+ return AudioCommon::IsRevisionSupported(2, user_revision);
}
bool BehaviorInfo::IsSplitterSupported() const {
- return IsRevisionSupported(2, user_revision);
+ return AudioCommon::IsRevisionSupported(2, user_revision);
}
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
- return IsRevisionSupported(3, user_revision);
+ return AudioCommon::IsRevisionSupported(3, user_revision);
}
-bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const {
- return IsRevisionSupported(5, user_revision);
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
}
-bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const {
- return IsRevisionSupported(4, user_revision);
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
+ return AudioCommon::IsRevisionSupported(4, user_revision);
}
-bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const {
- return IsRevisionSupported(1, user_revision);
+bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
+ return AudioCommon::IsRevisionSupported(1, user_revision);
}
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
- return IsRevisionSupported(5, user_revision);
+ return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
return (flags & 1) != 0;
}
+bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
+ return AudioCommon::IsRevisionSupported(7, user_revision);
+}
+
+bool BehaviorInfo::IsSplitterBugFixed() const {
+ return AudioCommon::IsRevisionSupported(5, user_revision);
+}
+
+void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
+ dst.error_count = static_cast<u32>(error_count);
+ std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
+}
+
} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h
index c5e91ab39..5a96bf75e 100644
--- a/src/audio_core/behavior_info.h
+++ b/src/audio_core/behavior_info.h
@@ -14,30 +14,6 @@
namespace AudioCore {
class BehaviorInfo {
public:
- explicit BehaviorInfo();
- ~BehaviorInfo();
-
- bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset);
- bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
-
- void ClearError();
- void UpdateFlags(u64_le dest_flags);
- void SetUserRevision(u32_le revision);
-
- bool IsAdpcmLoopContextBugFixed() const;
- bool IsSplitterSupported() const;
- bool IsLongSizePreDelaySupported() const;
- bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
- bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
- bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
- bool IsElapsedFrameCountSupported() const;
- bool IsMemoryPoolForceMappingEnabled() const;
-
-private:
- u32_le process_revision{};
- u32_le user_revision{};
- u64_le flags{};
-
struct ErrorInfo {
u32_le result{};
INSERT_PADDING_WORDS(1);
@@ -45,9 +21,6 @@ private:
};
static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
- std::array<ErrorInfo, 10> errors{};
- std::size_t error_count{};
-
struct InParams {
u32_le revision{};
u32_le padding{};
@@ -61,6 +34,39 @@ private:
INSERT_PADDING_BYTES(12);
};
static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
+
+ explicit BehaviorInfo();
+ ~BehaviorInfo();
+
+ bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
+
+ void ClearError();
+ void UpdateFlags(u64_le dest_flags);
+ void SetUserRevision(u32_le revision);
+ [[nodiscard]] u32_le GetUserRevision() const;
+ [[nodiscard]] u32_le GetProcessRevision() const;
+
+ [[nodiscard]] bool IsAdpcmLoopContextBugFixed() const;
+ [[nodiscard]] bool IsSplitterSupported() const;
+ [[nodiscard]] bool IsLongSizePreDelaySupported() const;
+ [[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
+ [[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
+ [[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
+ [[nodiscard]] bool IsElapsedFrameCountSupported() const;
+ [[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const;
+ [[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const;
+ [[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
+ [[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const;
+ [[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const;
+ [[nodiscard]] bool IsSplitterBugFixed() const;
+ void CopyErrorInfo(OutParams& dst);
+
+private:
+ u32_le process_revision{};
+ u32_le user_revision{};
+ u64_le flags{};
+ std::array<ErrorInfo, 10> errors{};
+ std::size_t error_count{};
};
} // namespace AudioCore
diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h
index 5ee09e9aa..ccc46ef82 100644
--- a/src/audio_core/buffer.h
+++ b/src/audio_core/buffer.h
@@ -18,7 +18,7 @@ class Buffer {
public:
using Tag = u64;
- Buffer(Tag tag, std::vector<s16>&& samples) : tag{tag}, samples{std::move(samples)} {}
+ Buffer(Tag tag_, std::vector<s16>&& samples_) : tag{tag_}, samples{std::move(samples_)} {}
/// Returns the raw audio data for the buffer
std::vector<s16>& GetSamples() {
diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp
index c5a0d98ce..2fb91c13a 100644
--- a/src/audio_core/codec.cpp
+++ b/src/audio_core/codec.cpp
@@ -16,8 +16,9 @@ std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM
constexpr std::size_t FRAME_LEN = 8;
constexpr std::size_t SAMPLES_PER_FRAME = 14;
- constexpr std::array<int, 16> SIGNED_NIBBLES = {
- {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
+ static constexpr std::array<int, 16> SIGNED_NIBBLES{
+ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
+ };
const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME;
const std::size_t ret_size =
diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h
index ef2ce01a8..9507abb1b 100644
--- a/src/audio_core/codec.h
+++ b/src/audio_core/codec.h
@@ -38,7 +38,7 @@ using ADPCM_Coeff = std::array<s16, 16>;
* @param state ADPCM state, this is updated with new state
* @return Decoded stereo signed PCM16 data, sample_count in length
*/
-std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff,
+std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff,
ADPCMState& state);
}; // namespace AudioCore::Codec
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
new file mode 100644
index 000000000..437cc5ccd
--- /dev/null
+++ b/src/audio_core/command_generator.cpp
@@ -0,0 +1,1308 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cmath>
+#include <numbers>
+#include "audio_core/algorithm/interpolate.h"
+#include "audio_core/command_generator.h"
+#include "audio_core/effect_context.h"
+#include "audio_core/mix_context.h"
+#include "audio_core/voice_context.h"
+#include "core/memory.h"
+
+namespace AudioCore {
+namespace {
+constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
+constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
+using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>;
+
+constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f};
+constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f};
+constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f};
+constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f};
+constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{
+ 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f,
+ 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f,
+ 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f};
+constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{
+ 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f,
+ 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f,
+ 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
+
+template <std::size_t N>
+void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
+ for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
+ for (std::size_t j = 0; j < N; j++) {
+ output[i + j] +=
+ static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
+ }
+ }
+}
+
+s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) {
+ s32 x = 0;
+ for (s32 i = 0; i < sample_count; i++) {
+ x = static_cast<s32>(static_cast<float>(input[i]) * gain);
+ output[i] += x;
+ gain += delta;
+ }
+ return x;
+}
+
+void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) {
+ for (s32 i = 0; i < sample_count; i++) {
+ output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
+ gain += delta;
+ }
+}
+
+void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) {
+ for (s32 i = 0; i < sample_count; i++) {
+ output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
+ }
+}
+
+s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
+ const bool positive = first_sample > 0;
+ auto final_sample = std::abs(first_sample);
+ for (s32 i = 0; i < sample_count; i++) {
+ final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
+ if (positive) {
+ output[i] += final_sample;
+ } else {
+ output[i] -= final_sample;
+ }
+ }
+ if (positive) {
+ return final_sample;
+ } else {
+ return -final_sample;
+ }
+}
+
+float Pow10(float x) {
+ if (x >= 0.0f) {
+ return 1.0f;
+ } else if (x <= -5.3f) {
+ return 0.0f;
+ }
+ return std::pow(10.0f, x);
+}
+
+float SinD(float degrees) {
+ return std::sin(degrees * std::numbers::pi_v<float> / 180.0f);
+}
+
+float CosD(float degrees) {
+ return std::cos(degrees * std::numbers::pi_v<float> / 180.0f);
+}
+
+float ToFloat(s32 sample) {
+ return static_cast<float>(sample) / 65536.f;
+}
+
+s32 ToS32(float sample) {
+ constexpr auto min = -8388608.0f;
+ constexpr auto max = 8388607.f;
+ float rescaled_sample = sample * 65536.0f;
+ if (rescaled_sample < min) {
+ rescaled_sample = min;
+ }
+ if (rescaled_sample > max) {
+ rescaled_sample = max;
+ }
+ return static_cast<s32>(rescaled_sample);
+}
+
+constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
+ 1, 1, 1, 0, 0, 0, 0, 1, 1, 1};
+
+constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
+ 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
+
+constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2,
+ 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
+
+template <std::size_t CHANNEL_COUNT>
+void ApplyReverbGeneric(I3dl2ReverbState& state,
+ const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input,
+ const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& output,
+ s32 sample_count) {
+
+ auto GetTapLookup = []() {
+ if constexpr (CHANNEL_COUNT == 1) {
+ return REVERB_TAP_INDEX_1CH;
+ } else if constexpr (CHANNEL_COUNT == 2) {
+ return REVERB_TAP_INDEX_2CH;
+ } else if constexpr (CHANNEL_COUNT == 4) {
+ return REVERB_TAP_INDEX_4CH;
+ } else if constexpr (CHANNEL_COUNT == 6) {
+ return REVERB_TAP_INDEX_6CH;
+ }
+ };
+
+ const auto& tap_index_lut = GetTapLookup();
+ for (s32 sample = 0; sample < sample_count; sample++) {
+ std::array<f32, CHANNEL_COUNT> out_samples{};
+ std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{};
+ std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{};
+ std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{};
+
+ // Mix everything into a single sample
+ s32 temp_mixed_sample = 0;
+ for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
+ temp_mixed_sample += input[i][sample];
+ }
+ const auto current_sample = ToFloat(temp_mixed_sample);
+ const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps);
+
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) {
+ const auto tapped_samp =
+ state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i];
+ out_samples[tap_index_lut[i]] += tapped_samp;
+
+ if constexpr (CHANNEL_COUNT == 6) {
+ // handle lfe
+ out_samples[5] += tapped_samp;
+ }
+ }
+
+ state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1;
+ state.early_delay_line.Tick(state.lowpass_0);
+
+ for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
+ out_samples[i] *= state.early_gain;
+ }
+
+ // Two channel seems to apply a latet gain, we require to save this
+ f32 filter{};
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ filter = state.fdn_delay_line[i].GetOutputSample();
+ const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i];
+ state.shelf_filter[i] =
+ filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i];
+ fsamp[i] = computed;
+ }
+
+ // Mixing matrix
+ mixed[0] = fsamp[1] + fsamp[2];
+ mixed[1] = -fsamp[0] - fsamp[3];
+ mixed[2] = fsamp[0] - fsamp[3];
+ mixed[3] = fsamp[1] - fsamp[2];
+
+ if constexpr (CHANNEL_COUNT == 2) {
+ for (auto& mix : mixed) {
+ mix *= (filter * state.late_gain);
+ }
+ }
+
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ const auto late = early_tap * state.late_gain;
+ osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]);
+ osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]);
+ state.fdn_delay_line[i].Tick(osamp[i]);
+ }
+
+ if constexpr (CHANNEL_COUNT == 1) {
+ output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) +
+ (out_samples[0] + osamp[0] + osamp[1]));
+ } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) {
+ for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
+ output[i][sample] =
+ ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
+ }
+ } else if constexpr (CHANNEL_COUNT == 6) {
+ const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3]));
+ for (std::size_t i = 0; i < 4; i++) {
+ output[i][sample] =
+ ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
+ }
+ output[4][sample] =
+ ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center));
+ output[5][sample] =
+ ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3]));
+ }
+ }
+}
+
+} // namespace
+
+CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
+ VoiceContext& voice_context_, MixContext& mix_context_,
+ SplitterContext& splitter_context_,
+ EffectContext& effect_context_, Core::Memory::Memory& memory_)
+ : worker_params(worker_params_), voice_context(voice_context_), mix_context(mix_context_),
+ splitter_context(splitter_context_), effect_context(effect_context_), memory(memory_),
+ mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
+ worker_params.sample_count),
+ sample_buffer(MIX_BUFFER_SIZE),
+ depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
+ worker_params.sample_count) {}
+CommandGenerator::~CommandGenerator() = default;
+
+void CommandGenerator::ClearMixBuffers() {
+ std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
+ std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
+ // std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
+}
+
+void CommandGenerator::GenerateVoiceCommands() {
+ if (dumping_frame) {
+ LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
+ }
+ // Grab all our voices
+ const auto voice_count = voice_context.GetVoiceCount();
+ for (std::size_t i = 0; i < voice_count; i++) {
+ auto& voice_info = voice_context.GetSortedInfo(i);
+ // Update voices and check if we should queue them
+ if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
+ continue;
+ }
+
+ // Queue our voice
+ GenerateVoiceCommand(voice_info);
+ }
+ // Update our splitters
+ splitter_context.UpdateInternalState();
+}
+
+void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
+ auto& in_params = voice_info.GetInParams();
+ const auto channel_count = in_params.channel_count;
+
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ const auto resource_id = in_params.voice_channel_resource_id[channel];
+ auto& dsp_state = voice_context.GetDspSharedState(resource_id);
+ auto& channel_resource = voice_context.GetChannelResource(resource_id);
+
+ // Decode our samples for our channel
+ GenerateDataSourceCommand(voice_info, dsp_state, channel);
+
+ if (in_params.should_depop) {
+ in_params.last_volume = 0.0f;
+ } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
+ in_params.mix_id != AudioCommon::NO_MIX) {
+ // Apply a biquad filter if needed
+ GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
+ worker_params.mix_buffer_count, channel);
+ // Base voice volume ramping
+ GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
+ in_params.node_id);
+ in_params.last_volume = in_params.volume;
+
+ if (in_params.mix_id != AudioCommon::NO_MIX) {
+ // If we're using a mix id
+ auto& mix_info = mix_context.GetInfo(in_params.mix_id);
+ const auto& dest_mix_params = mix_info.GetInParams();
+
+ // Voice Mixing
+ GenerateVoiceMixCommand(
+ channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
+ dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
+ worker_params.mix_buffer_count + channel, in_params.node_id);
+
+ // Update last mix volumes
+ channel_resource.UpdateLastMixVolumes();
+ } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
+ s32 base = channel;
+ while (auto* destination_data =
+ GetDestinationData(in_params.splitter_info_id, base)) {
+ base += channel_count;
+
+ if (!destination_data->IsConfigured()) {
+ continue;
+ }
+ if (destination_data->GetMixId() >= static_cast<int>(mix_context.GetCount())) {
+ continue;
+ }
+
+ const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
+ const auto& dest_mix_params = mix_info.GetInParams();
+ GenerateVoiceMixCommand(
+ destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
+ dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
+ worker_params.mix_buffer_count + channel, in_params.node_id);
+ destination_data->MarkDirty();
+ }
+ }
+ // Update biquad filter enabled states
+ for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
+ in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommands() {
+ const auto mix_count = mix_context.GetCount();
+ for (std::size_t i = 0; i < mix_count; i++) {
+ auto& mix_info = mix_context.GetSortedInfo(i);
+ const auto& in_params = mix_info.GetInParams();
+ if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
+ continue;
+ }
+ GenerateSubMixCommand(mix_info);
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommands() {
+ GenerateFinalMixCommand();
+}
+
+void CommandGenerator::PreCommand() {
+ if (!dumping_frame) {
+ return;
+ }
+ for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
+ const auto& base = splitter_context.GetInfo(i);
+ std::string graph = fmt::format("b[{}]", i);
+ const auto* head = base.GetHead();
+ while (head != nullptr) {
+ graph += fmt::format("->{}", head->GetMixId());
+ head = head->GetNextDestination();
+ }
+ LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
+ }
+}
+
+void CommandGenerator::PostCommand() {
+ if (!dumping_frame) {
+ return;
+ }
+ dumping_frame = false;
+}
+
+void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
+ s32 channel) {
+ const auto& in_params = voice_info.GetInParams();
+ const auto depop = in_params.should_depop;
+
+ if (depop) {
+ if (in_params.mix_id != AudioCommon::NO_MIX) {
+ auto& mix_info = mix_context.GetInfo(in_params.mix_id);
+ const auto& mix_in = mix_info.GetInParams();
+ GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
+ } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
+ s32 index{};
+ while (const auto* destination =
+ GetDestinationData(in_params.splitter_info_id, index++)) {
+ if (!destination->IsConfigured()) {
+ continue;
+ }
+ auto& mix_info = mix_context.GetInfo(destination->GetMixId());
+ const auto& mix_in = mix_info.GetInParams();
+ GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
+ }
+ }
+ } else {
+ switch (in_params.sample_format) {
+ case SampleFormat::Pcm16:
+ DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
+ worker_params.sample_rate, worker_params.sample_count,
+ in_params.node_id);
+ break;
+ case SampleFormat::Adpcm:
+ ASSERT(channel == 0 && in_params.channel_count == 1);
+ DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
+ worker_params.sample_rate, worker_params.sample_count,
+ in_params.node_id);
+ break;
+ default:
+ UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
+ VoiceState& dsp_state,
+ [[maybe_unused]] s32 mix_buffer_count,
+ [[maybe_unused]] s32 channel) {
+ for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
+ const auto& in_params = voice_info.GetInParams();
+ auto& biquad_filter = in_params.biquad_filter[i];
+ // Check if biquad filter is actually used
+ if (!biquad_filter.enabled) {
+ continue;
+ }
+
+ // Reinitialize our biquad filter state if it was enabled previously
+ if (!in_params.was_biquad_filter_enabled[i]) {
+ dsp_state.biquad_filter_state.fill(0);
+ }
+
+ // Generate biquad filter
+ // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
+ // dsp_state.biquad_filter_state,
+ // mix_buffer_count + channel, mix_buffer_count + channel,
+ // worker_params.sample_count, voice_info.GetInParams().node_id);
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buffer_id,
+ const BiquadFilterParameter& params,
+ std::array<s64, 2>& state,
+ std::size_t input_offset,
+ std::size_t output_offset, s32 sample_count,
+ s32 node_id) {
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
+ "input_mix_buffer={}, output_mix_buffer={}",
+ node_id, input_offset, output_offset);
+ }
+ const auto* input = GetMixBuffer(input_offset);
+ auto* output = GetMixBuffer(output_offset);
+
+ // Biquad filter parameters
+ const auto [n0, n1, n2] = params.numerator;
+ const auto [d0, d1] = params.denominator;
+
+ // Biquad filter states
+ auto [s0, s1] = state;
+
+ constexpr s64 int32_min = std::numeric_limits<s32>::min();
+ constexpr s64 int32_max = std::numeric_limits<s32>::max();
+
+ for (int i = 0; i < sample_count; ++i) {
+ const auto sample = static_cast<s64>(input[i]);
+ const auto f = (sample * n0 + s0 + 0x4000) >> 15;
+ const auto y = std::clamp(f, int32_min, int32_max);
+ s0 = sample * n1 + y * d0 + s1;
+ s1 = sample * n2 + y * d1;
+ output[i] = static_cast<s32>(y);
+ }
+
+ state = {s0, s1};
+}
+
+void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
+ std::size_t mix_buffer_count,
+ std::size_t mix_buffer_offset) {
+ for (std::size_t i = 0; i < mix_buffer_count; i++) {
+ auto& sample = dsp_state.previous_samples[i];
+ if (sample != 0) {
+ depop_buffer[mix_buffer_offset + i] += sample;
+ sample = 0;
+ }
+ }
+}
+
+void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
+ std::size_t mix_buffer_offset,
+ s32 sample_rate) {
+ const std::size_t end_offset =
+ std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
+ const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
+ for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
+ if (depop_buffer[i] == 0) {
+ continue;
+ }
+
+ depop_buffer[i] =
+ ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
+ }
+}
+
+void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
+ const std::size_t effect_count = effect_context.GetCount();
+ const auto buffer_offset = mix_info.GetInParams().buffer_offset;
+ for (std::size_t i = 0; i < effect_count; i++) {
+ const auto index = mix_info.GetEffectOrder(i);
+ if (index == AudioCommon::NO_EFFECT_ORDER) {
+ break;
+ }
+ auto* info = effect_context.GetInfo(index);
+ const auto type = info->GetType();
+
+ // TODO(ogniK): Finish remaining effects
+ switch (type) {
+ case EffectType::Aux:
+ GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ case EffectType::I3dl2Reverb:
+ GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ case EffectType::BiquadFilter:
+ GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
+ break;
+ default:
+ break;
+ }
+
+ info->UpdateForCommandGeneration();
+ }
+}
+
+void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
+ bool enabled) {
+ auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info);
+ const auto& params = reverb->GetParams();
+ auto& state = reverb->GetState();
+ const auto channel_count = params.channel_count;
+
+ if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) {
+ return;
+ }
+
+ std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> output{};
+
+ const auto status = params.status;
+ for (s32 i = 0; i < channel_count; i++) {
+ input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]);
+ output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]);
+ }
+
+ if (enabled) {
+ if (status == ParameterStatus::Initialized) {
+ InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer());
+ } else if (status == ParameterStatus::Updating) {
+ UpdateI3dl2Reverb(reverb->GetParams(), state, false);
+ }
+ }
+
+ if (enabled) {
+ switch (channel_count) {
+ case 1:
+ ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count);
+ break;
+ case 2:
+ ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count);
+ break;
+ case 4:
+ ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count);
+ break;
+ case 6:
+ ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count);
+ break;
+ }
+ } else {
+ for (s32 i = 0; i < channel_count; i++) {
+ // Only copy if the buffer input and output do not match!
+ if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) {
+ std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32));
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
+ bool enabled) {
+ if (!enabled) {
+ return;
+ }
+ const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
+ const auto channel_count = params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ // TODO(ogniK): Actually implement biquad filter
+ if (params.input[i] != params.output[i]) {
+ const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
+ auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
+ ApplyMix<1>(output, input, 32768, worker_params.sample_count);
+ }
+ }
+}
+
+void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
+ auto* aux = dynamic_cast<EffectAuxInfo*>(info);
+ const auto& params = aux->GetParams();
+ if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
+ const auto max_channels = params.count;
+ u32 offset{};
+ for (u32 channel = 0; channel < max_channels; channel++) {
+ u32 write_count = 0;
+ if (channel == (max_channels - 1)) {
+ write_count = offset + worker_params.sample_count;
+ }
+
+ const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
+ const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
+
+ if (enabled) {
+ AuxInfoDSP send_info{};
+ AuxInfoDSP recv_info{};
+ memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
+ memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
+
+ WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
+ GetMixBuffer(input_index), worker_params.sample_count, offset,
+ write_count);
+ memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
+
+ const auto samples_read = ReadAuxBuffer(
+ recv_info, aux->GetRecvBuffer(), params.sample_count,
+ GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
+ memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
+
+ if (samples_read != static_cast<int>(worker_params.sample_count) &&
+ samples_read <= params.sample_count) {
+ std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
+ }
+ } else {
+ AuxInfoDSP empty{};
+ memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
+ memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
+ if (output_index != input_index) {
+ std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
+ worker_params.sample_count * sizeof(s32));
+ }
+ }
+
+ offset += worker_params.sample_count;
+ }
+ }
+}
+
+ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
+ if (splitter_id == AudioCommon::NO_SPLITTER) {
+ return nullptr;
+ }
+ return splitter_context.GetDestinationData(splitter_id, index);
+}
+
+s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
+ const s32* data, u32 sample_count, u32 write_offset,
+ u32 write_count) {
+ if (max_samples == 0) {
+ return 0;
+ }
+ u32 offset = dsp_info.write_offset + write_offset;
+ if (send_buffer == 0 || offset > max_samples) {
+ return 0;
+ }
+
+ std::size_t data_offset{};
+ u32 remaining = sample_count;
+ while (remaining > 0) {
+ // Get position in buffer
+ const auto base = send_buffer + (offset * sizeof(u32));
+ const auto samples_to_grab = std::min(max_samples - offset, remaining);
+ // Write to output
+ memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
+ offset = (offset + samples_to_grab) % max_samples;
+ remaining -= samples_to_grab;
+ data_offset += samples_to_grab;
+ }
+
+ if (write_count != 0) {
+ dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
+ }
+ return sample_count;
+}
+
+s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
+ s32* out_data, u32 sample_count, u32 read_offset,
+ u32 read_count) {
+ if (max_samples == 0) {
+ return 0;
+ }
+
+ u32 offset = recv_info.read_offset + read_offset;
+ if (recv_buffer == 0 || offset > max_samples) {
+ return 0;
+ }
+
+ u32 remaining = sample_count;
+ while (remaining > 0) {
+ const auto base = recv_buffer + (offset * sizeof(u32));
+ const auto samples_to_grab = std::min(max_samples - offset, remaining);
+ std::vector<s32> buffer(samples_to_grab);
+ memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
+ std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
+ out_data += samples_to_grab;
+ offset = (offset + samples_to_grab) % max_samples;
+ remaining -= samples_to_grab;
+ }
+
+ if (read_count != 0) {
+ recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
+ }
+ return sample_count;
+}
+
+void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
+ std::vector<u8>& work_buffer) {
+ // Reset state
+ state.lowpass_0 = 0.0f;
+ state.lowpass_1 = 0.0f;
+ state.lowpass_2 = 0.0f;
+
+ state.early_delay_line.Reset();
+ state.early_tap_steps.fill(0);
+ state.early_gain = 0.0f;
+ state.late_gain = 0.0f;
+ state.early_to_late_taps = 0;
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ state.fdn_delay_line[i].Reset();
+ state.decay_delay_line0[i].Reset();
+ state.decay_delay_line1[i].Reset();
+ }
+ state.last_reverb_echo = 0.0f;
+ state.center_delay_line.Reset();
+ for (auto& coef : state.lpf_coefficients) {
+ coef.fill(0.0f);
+ }
+ state.shelf_filter.fill(0.0f);
+ state.dry_gain = 0.0f;
+
+ const auto sample_rate = info.sample_rate / 1000;
+ f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data());
+
+ s32 delay_samples{};
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ delay_samples =
+ AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]);
+ state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr);
+ work_buffer_ptr += delay_samples + 1;
+
+ delay_samples =
+ AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]);
+ state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
+ work_buffer_ptr += delay_samples + 1;
+
+ delay_samples =
+ AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]);
+ state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
+ work_buffer_ptr += delay_samples + 1;
+ }
+ delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f);
+ state.center_delay_line.Initialize(delay_samples, work_buffer_ptr);
+ work_buffer_ptr += delay_samples + 1;
+
+ delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f);
+ state.early_delay_line.Initialize(delay_samples, work_buffer_ptr);
+
+ UpdateI3dl2Reverb(info, state, true);
+}
+
+void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
+ bool should_clear) {
+
+ state.dry_gain = info.dry_gain;
+ state.shelf_filter.fill(0.0f);
+ state.lowpass_0 = 0.0f;
+ state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f);
+ state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f);
+
+ const auto sample_rate = info.sample_rate / 1000;
+ const f32 hf_gain = Pow10(info.room_hf / 2000.0f);
+ if (hf_gain >= 1.0f) {
+ state.lowpass_2 = 1.0f;
+ state.lowpass_1 = 0.0f;
+ } else {
+ const auto a = 1.0f - hf_gain;
+ const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference /
+ static_cast<f32>(info.sample_rate)));
+ const auto c = std::sqrt(b * b - 4.0f * a * a);
+
+ state.lowpass_1 = (b - c) / (2.0f * a);
+ state.lowpass_2 = 1.0f - state.lowpass_1;
+ }
+ state.early_to_late_taps = AudioCommon::CalculateDelaySamples(
+ sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay));
+
+ state.last_reverb_echo = 0.6f * info.diffusion * 0.01f;
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ const auto length =
+ FDN_MIN_DELAY_LINE_TIMES[i] +
+ (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]);
+ state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length));
+
+ const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() +
+ state.decay_delay_line0[i].GetDelay() +
+ state.decay_delay_line1[i].GetDelay();
+
+ float a = (-60.0f * static_cast<f32>(delay_sample_counts)) /
+ (info.decay_time * static_cast<f32>(info.sample_rate));
+ float b = a / info.hf_decay_ratio;
+ float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) /
+ SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate));
+ float d = Pow10((b - a) / 40.0f);
+ float e = Pow10((b + a) / 40.0f) * 0.7071f;
+
+ state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d);
+ state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d);
+ state.lpf_coefficients[2][i] = (c - d) / (c + d);
+
+ state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo);
+ state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo);
+ }
+
+ if (should_clear) {
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ state.fdn_delay_line[i].Clear();
+ state.decay_delay_line0[i].Clear();
+ state.decay_delay_line1[i].Clear();
+ }
+ state.early_delay_line.Clear();
+ state.center_delay_line.Clear();
+ }
+
+ const auto max_early_delay = state.early_delay_line.GetMaxDelay();
+ const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f);
+ for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) {
+ const auto length = AudioCommon::CalculateDelaySamples(
+ sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]);
+ state.early_tap_steps[tap] = std::min(length, max_early_delay);
+ }
+}
+
+void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
+ s32 channel, s32 node_id) {
+ const auto last = static_cast<s32>(last_volume * 32768.0f);
+ const auto current = static_cast<s32>(current_volume * 32768.0f);
+ const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
+ static_cast<float>(worker_params.sample_count));
+
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
+ "last_volume={}, current_volume={}",
+ node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
+ last_volume, current_volume);
+ }
+ // Apply generic gain on samples
+ ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
+ worker_params.sample_count);
+}
+
+void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
+ const MixVolumeBuffer& last_mix_volumes,
+ VoiceState& dsp_state, s32 mix_buffer_offset,
+ s32 mix_buffer_count, s32 voice_index, s32 node_id) {
+ // Loop all our mix buffers
+ for (s32 i = 0; i < mix_buffer_count; i++) {
+ if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
+ const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
+ static_cast<float>(worker_params.sample_count);
+
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
+ "output={}, last_volume={}, current_volume={}",
+ node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
+ mix_volumes[i]);
+ }
+
+ dsp_state.previous_samples[i] =
+ ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
+ last_mix_volumes[i], delta, worker_params.sample_count);
+ } else {
+ dsp_state.previous_samples[i] = 0;
+ }
+ }
+}
+
+void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
+ if (dumping_frame) {
+ LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
+ }
+ const auto& in_params = mix_info.GetInParams();
+ GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
+ in_params.sample_rate);
+
+ GenerateEffectCommand(mix_info);
+
+ GenerateMixCommands(mix_info);
+}
+
+void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
+ if (!mix_info.HasAnyConnection()) {
+ return;
+ }
+ const auto& in_params = mix_info.GetInParams();
+ if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
+ const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
+ const auto& dest_in_params = dest_mix.GetInParams();
+
+ const auto buffer_count = in_params.buffer_count;
+
+ for (s32 i = 0; i < buffer_count; i++) {
+ for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
+ const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
+ if (mixed_volume != 0.0f) {
+ GenerateMixCommand(dest_in_params.buffer_offset + j,
+ in_params.buffer_offset + i, mixed_volume,
+ in_params.node_id);
+ }
+ }
+ }
+ } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
+ s32 base{};
+ while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
+ if (!destination_data->IsConfigured()) {
+ continue;
+ }
+
+ const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
+ const auto& dest_in_params = dest_mix.GetInParams();
+ const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
+ for (std::size_t i = 0; i < static_cast<std::size_t>(dest_in_params.buffer_count);
+ i++) {
+ const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
+ if (mixed_volume != 0.0f) {
+ GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
+ in_params.node_id);
+ }
+ }
+ }
+ }
+}
+
+void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
+ float volume, s32 node_id) {
+
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
+ node_id, input_offset, output_offset, volume);
+ }
+
+ auto* output = GetMixBuffer(output_offset);
+ const auto* input = GetMixBuffer(input_offset);
+
+ const s32 gain = static_cast<s32>(volume * 32768.0f);
+ // Mix with loop unrolling
+ if (worker_params.sample_count % 4 == 0) {
+ ApplyMix<4>(output, input, gain, worker_params.sample_count);
+ } else if (worker_params.sample_count % 2 == 0) {
+ ApplyMix<2>(output, input, gain, worker_params.sample_count);
+ } else {
+ ApplyMix<1>(output, input, gain, worker_params.sample_count);
+ }
+}
+
+void CommandGenerator::GenerateFinalMixCommand() {
+ if (dumping_frame) {
+ LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
+ }
+ auto& mix_info = mix_context.GetFinalMixInfo();
+ const auto& in_params = mix_info.GetInParams();
+
+ GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
+ in_params.sample_rate);
+
+ GenerateEffectCommand(mix_info);
+
+ for (s32 i = 0; i < in_params.buffer_count; i++) {
+ const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
+ if (dumping_frame) {
+ LOG_DEBUG(
+ Audio,
+ "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
+ in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
+ in_params.volume);
+ }
+ ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
+ GetMixBuffer(in_params.buffer_offset + i), gain,
+ worker_params.sample_count);
+ }
+}
+
+s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
+ s32 sample_count, s32 channel, std::size_t mix_offset) {
+ const auto& in_params = voice_info.GetInParams();
+ const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
+ if (wave_buffer.buffer_address == 0) {
+ return 0;
+ }
+ if (wave_buffer.buffer_size == 0) {
+ return 0;
+ }
+ if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
+ return 0;
+ }
+ const auto samples_remaining =
+ (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
+ const auto start_offset =
+ ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
+ sizeof(s16);
+ const auto buffer_pos = wave_buffer.buffer_address + start_offset;
+ const auto samples_processed = std::min(sample_count, samples_remaining);
+
+ if (in_params.channel_count == 1) {
+ std::vector<s16> buffer(samples_processed);
+ memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
+ for (std::size_t i = 0; i < buffer.size(); i++) {
+ sample_buffer[mix_offset + i] = buffer[i];
+ }
+ } else {
+ const auto channel_count = in_params.channel_count;
+ std::vector<s16> buffer(samples_processed * channel_count);
+ memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
+
+ for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
+ sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
+ }
+ }
+
+ return samples_processed;
+}
+
+s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
+ s32 sample_count, [[maybe_unused]] s32 channel,
+ std::size_t mix_offset) {
+ const auto& in_params = voice_info.GetInParams();
+ const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
+ if (wave_buffer.buffer_address == 0) {
+ return 0;
+ }
+ if (wave_buffer.buffer_size == 0) {
+ return 0;
+ }
+ if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
+ return 0;
+ }
+
+ static constexpr std::array<int, 16> SIGNED_NIBBLES{
+ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
+ };
+
+ constexpr std::size_t FRAME_LEN = 8;
+ constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
+ constexpr std::size_t SAMPLES_PER_FRAME = 14;
+
+ auto frame_header = dsp_state.context.header;
+ s32 idx = (frame_header >> 4) & 0xf;
+ s32 scale = frame_header & 0xf;
+ s16 yn1 = dsp_state.context.yn1;
+ s16 yn2 = dsp_state.context.yn2;
+
+ Codec::ADPCM_Coeff coeffs;
+ memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
+ sizeof(Codec::ADPCM_Coeff));
+
+ s32 coef1 = coeffs[idx * 2];
+ s32 coef2 = coeffs[idx * 2 + 1];
+
+ const auto samples_remaining =
+ (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
+ const auto samples_processed = std::min(sample_count, samples_remaining);
+ const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
+
+ const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
+ auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
+ samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
+
+ const auto decode_sample = [&](const int nibble) -> s16 {
+ const int xn = nibble * (1 << scale);
+ // We first transform everything into 11 bit fixed point, perform the second order
+ // digital filter, then transform back.
+ // 0x400 == 0.5 in 11 bit fixed point.
+ // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
+ int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
+ // Clamp to output range.
+ val = std::clamp<s32>(val, -32768, 32767);
+ // Advance output feedback.
+ yn2 = yn1;
+ yn1 = static_cast<s16>(val);
+ return yn1;
+ };
+
+ std::size_t buffer_offset{};
+ std::vector<u8> buffer(
+ std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
+ memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
+ buffer.size());
+ std::size_t cur_mix_offset = mix_offset;
+
+ auto remaining_samples = samples_processed;
+ while (remaining_samples > 0) {
+ if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
+ // Read header
+ frame_header = buffer[buffer_offset++];
+ idx = (frame_header >> 4) & 0xf;
+ scale = frame_header & 0xf;
+ coef1 = coeffs[idx * 2];
+ coef2 = coeffs[idx * 2 + 1];
+ position_in_frame += 2;
+
+ // Decode entire frame
+ if (remaining_samples >= static_cast<int>(SAMPLES_PER_FRAME)) {
+ for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
+ // Sample 1
+ const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
+ const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
+ const s16 sample_1 = decode_sample(s0);
+ const s16 sample_2 = decode_sample(s1);
+ sample_buffer[cur_mix_offset++] = sample_1;
+ sample_buffer[cur_mix_offset++] = sample_2;
+ }
+ remaining_samples -= static_cast<int>(SAMPLES_PER_FRAME);
+ position_in_frame += SAMPLES_PER_FRAME;
+ continue;
+ }
+ }
+ // Decode mid frame
+ s32 current_nibble = buffer[buffer_offset];
+ if (position_in_frame++ & 0x1) {
+ current_nibble &= 0xf;
+ buffer_offset++;
+ } else {
+ current_nibble >>= 4;
+ }
+ const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
+ sample_buffer[cur_mix_offset++] = sample;
+ remaining_samples--;
+ }
+
+ dsp_state.context.header = frame_header;
+ dsp_state.context.yn1 = yn1;
+ dsp_state.context.yn2 = yn2;
+
+ return samples_processed;
+}
+
+s32* CommandGenerator::GetMixBuffer(std::size_t index) {
+ return mix_buffer.data() + (index * worker_params.sample_count);
+}
+
+const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
+ return mix_buffer.data() + (index * worker_params.sample_count);
+}
+
+std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
+ return worker_params.mix_buffer_count + channel;
+}
+
+std::size_t CommandGenerator::GetTotalMixBufferCount() const {
+ return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
+}
+
+s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
+ return GetMixBuffer(worker_params.mix_buffer_count + channel);
+}
+
+const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
+ return GetMixBuffer(worker_params.mix_buffer_count + channel);
+}
+
+void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
+ VoiceState& dsp_state, s32 channel,
+ s32 target_sample_rate, s32 sample_count,
+ s32 node_id) {
+ const auto& in_params = voice_info.GetInParams();
+ if (dumping_frame) {
+ LOG_DEBUG(Audio,
+ "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
+ "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
+ node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
+ in_params.mix_id, in_params.splitter_info_id);
+ }
+ ASSERT_OR_EXECUTE(output != nullptr, { return; });
+
+ const auto resample_rate = static_cast<s32>(
+ static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
+ static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
+ if (dsp_state.fraction + sample_count * resample_rate >
+ static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) {
+ return;
+ }
+
+ auto min_required_samples =
+ std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
+ if (min_required_samples >= sample_count) {
+ min_required_samples = sample_count;
+ }
+
+ std::size_t temp_mix_offset{};
+ bool is_buffer_completed{false};
+ auto samples_remaining = sample_count;
+ while (samples_remaining > 0 && !is_buffer_completed) {
+ const auto samples_to_output = std::min(samples_remaining, min_required_samples);
+ const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
+
+ if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
+ // Append sample histtory for resampler
+ for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
+ sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
+ }
+ temp_mix_offset += 4;
+ }
+
+ s32 samples_read{};
+ while (samples_read < samples_to_read) {
+ const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
+ // No more data can be read
+ if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
+ is_buffer_completed = true;
+ break;
+ }
+
+ if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
+ wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
+ // TODO(ogniK): ADPCM loop context
+ }
+
+ s32 samples_decoded{0};
+ switch (in_params.sample_format) {
+ case SampleFormat::Pcm16:
+ samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
+ channel, temp_mix_offset);
+ break;
+ case SampleFormat::Adpcm:
+ samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
+ channel, temp_mix_offset);
+ break;
+ default:
+ UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
+ }
+
+ temp_mix_offset += samples_decoded;
+ samples_read += samples_decoded;
+ dsp_state.offset += samples_decoded;
+ dsp_state.played_sample_count += samples_decoded;
+
+ if (dsp_state.offset >=
+ (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
+ samples_decoded == 0) {
+ // Reset our sample offset
+ dsp_state.offset = 0;
+ if (wave_buffer.is_looping) {
+ if (samples_decoded == 0) {
+ // End of our buffer
+ is_buffer_completed = true;
+ break;
+ }
+
+ if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
+ dsp_state.played_sample_count = 0;
+ }
+ } else {
+
+ // Update our wave buffer states
+ dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
+ dsp_state.wave_buffer_consumed++;
+ dsp_state.wave_buffer_index =
+ (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ if (wave_buffer.end_of_stream) {
+ dsp_state.played_sample_count = 0;
+ }
+ }
+ }
+ }
+
+ if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
+ // No need to resample
+ std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32));
+ } else {
+ std::fill(sample_buffer.begin() + temp_mix_offset,
+ sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
+ 0);
+ AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction,
+ samples_to_output);
+ // Resample
+ for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
+ dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
+ }
+ }
+ output += samples_to_output;
+ samples_remaining -= samples_to_output;
+ }
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
new file mode 100644
index 000000000..2ebb755b0
--- /dev/null
+++ b/src/audio_core/command_generator.h
@@ -0,0 +1,107 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "audio_core/common.h"
+#include "audio_core/voice_context.h"
+#include "common/common_types.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace AudioCore {
+class MixContext;
+class SplitterContext;
+class ServerSplitterDestinationData;
+class ServerMixInfo;
+class EffectContext;
+class EffectBase;
+struct AuxInfoDSP;
+struct I3dl2ReverbParams;
+struct I3dl2ReverbState;
+using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
+
+class CommandGenerator {
+public:
+ explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
+ VoiceContext& voice_context_, MixContext& mix_context_,
+ SplitterContext& splitter_context_, EffectContext& effect_context_,
+ Core::Memory::Memory& memory_);
+ ~CommandGenerator();
+
+ void ClearMixBuffers();
+ void GenerateVoiceCommands();
+ void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
+ void GenerateSubMixCommands();
+ void GenerateFinalMixCommands();
+ void PreCommand();
+ void PostCommand();
+
+ [[nodiscard]] s32* GetChannelMixBuffer(s32 channel);
+ [[nodiscard]] const s32* GetChannelMixBuffer(s32 channel) const;
+ [[nodiscard]] s32* GetMixBuffer(std::size_t index);
+ [[nodiscard]] const s32* GetMixBuffer(std::size_t index) const;
+ [[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const;
+
+ [[nodiscard]] std::size_t GetTotalMixBufferCount() const;
+
+private:
+ void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
+ void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
+ s32 mix_buffer_count, s32 channel);
+ void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
+ s32 node_id);
+ void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
+ const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
+ s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
+ s32 node_id);
+ void GenerateSubMixCommand(ServerMixInfo& mix_info);
+ void GenerateMixCommands(ServerMixInfo& mix_info);
+ void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
+ s32 node_id);
+ void GenerateFinalMixCommand();
+ void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
+ std::array<s64, 2>& state, std::size_t input_offset,
+ std::size_t output_offset, s32 sample_count, s32 node_id);
+ void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
+ std::size_t mix_buffer_offset);
+ void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
+ std::size_t mix_buffer_offset, s32 sample_rate);
+ void GenerateEffectCommand(ServerMixInfo& mix_info);
+ void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+ void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+ void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
+ [[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
+
+ s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
+ u32 sample_count, u32 write_offset, u32 write_count);
+ s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
+ u32 sample_count, u32 read_offset, u32 read_count);
+
+ void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
+ std::vector<u8>& work_buffer);
+ void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
+ // DSP Code
+ s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
+ s32 channel, std::size_t mix_offset);
+ s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
+ s32 channel, std::size_t mix_offset);
+ void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
+ s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
+
+ AudioCommon::AudioRendererParameter& worker_params;
+ VoiceContext& voice_context;
+ MixContext& mix_context;
+ SplitterContext& splitter_context;
+ EffectContext& effect_context;
+ Core::Memory::Memory& memory;
+ std::vector<s32> mix_buffer{};
+ std::vector<s32> sample_buffer{};
+ std::vector<s32> depop_buffer{};
+ bool dumping_frame{false};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
index 7bb145c53..fe546c55d 100644
--- a/src/audio_core/common.h
+++ b/src/audio_core/common.h
@@ -3,18 +3,59 @@
// Refer to the license.txt file included.
#pragma once
+
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/result.h"
-namespace AudioCore {
+namespace AudioCommon {
namespace Audren {
constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
-}
+constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
+} // namespace Audren
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
constexpr std::size_t MAX_MIX_BUFFERS = 24;
+constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
+constexpr std::size_t MAX_CHANNEL_COUNT = 6;
+constexpr std::size_t MAX_WAVE_BUFFERS = 4;
+constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
+constexpr u32 STREAM_SAMPLE_RATE = 48000;
+constexpr u32 STREAM_NUM_CHANNELS = 2;
+constexpr s32 NO_SPLITTER = -1;
+constexpr s32 NO_MIX = 0x7fffffff;
+constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
+constexpr s32 FINAL_MIX = 0;
+constexpr s32 NO_EFFECT_ORDER = -1;
+constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
+// Any size checks seem to take the sample history into account
+// and our const ends up being 0x3f04, the 4 bytes are most
+// likely the sample history
+constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
+constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f;
+constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f;
+constexpr std::size_t I3DL2REVERB_TAPS = 20;
+constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4;
+using Fractional = s32;
+
+template <typename T>
+constexpr Fractional ToFractional(T x) {
+ return static_cast<Fractional>(x * static_cast<T>(0x4000));
+}
+
+constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) {
+ return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14);
+}
+
+constexpr s32 FractionalToFixed(Fractional x) {
+ const auto s = x & (1 << 13);
+ return static_cast<s32>(x >> 14) + s;
+}
+
+constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) {
+ return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time)));
+}
static constexpr u32 VersionFromRevision(u32_le rev) {
// "REV7" -> 7
@@ -45,4 +86,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std
return true;
}
-} // namespace AudioCore
+struct UpdateDataSizes {
+ u32_le behavior{};
+ u32_le memory_pool{};
+ u32_le voice{};
+ u32_le voice_channel_resource{};
+ u32_le effect{};
+ u32_le mixer{};
+ u32_le sink{};
+ u32_le performance{};
+ u32_le splitter{};
+ u32_le render_info{};
+ INSERT_PADDING_WORDS(4);
+};
+static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
+
+struct UpdateDataHeader {
+ u32_le revision{};
+ UpdateDataSizes size{};
+ u32_le total_size{};
+};
+static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
+
+struct AudioRendererParameter {
+ u32_le sample_rate;
+ u32_le sample_count;
+ u32_le mix_buffer_count;
+ u32_le submix_count;
+ u32_le voice_count;
+ u32_le sink_count;
+ u32_le effect_count;
+ u32_le performance_frame_count;
+ u8 is_voice_drop_enabled;
+ u8 unknown_21;
+ u8 unknown_22;
+ u8 execution_mode;
+ u32_le splitter_count;
+ u32_le num_splitter_send_channels;
+ u32_le unknown_30;
+ u32_le revision;
+};
+static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
+
+} // namespace AudioCommon
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index c27df946c..043447eaa 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -21,16 +21,27 @@ namespace AudioCore {
class CubebSinkStream final : public SinkStream {
public:
- CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
+ CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
const std::string& name)
- : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate,
- num_channels} {
+ : ctx{ctx_}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
+ num_channels} {
cubeb_stream_params params{};
params.rate = sample_rate;
params.channels = num_channels;
params.format = CUBEB_SAMPLE_S16NE;
- params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO;
+ params.prefs = CUBEB_STREAM_PREF_PERSIST;
+ switch (num_channels) {
+ case 1:
+ params.layout = CUBEB_LAYOUT_MONO;
+ break;
+ case 2:
+ params.layout = CUBEB_LAYOUT_STEREO;
+ break;
+ case 6:
+ params.layout = CUBEB_LAYOUT_3F2_LFE;
+ break;
+ }
u32 minimum_latency{};
if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
@@ -83,8 +94,10 @@ public:
constexpr s32 clev{707}; // center mixing level coefficient
constexpr s32 slev{707}; // surround mixing level coefficient
- buf.push_back(left + (clev * center / 1000) + (slev * surround_left / 1000));
- buf.push_back(right + (clev * center / 1000) + (slev * surround_right / 1000));
+ buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
+ (slev * surround_left / 1000)));
+ buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
+ (slev * surround_right / 1000)));
}
queue.Push(buf);
return;
@@ -180,10 +193,11 @@ SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
return *sink_streams.back();
}
-long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
- void* output_buffer, long num_frames) {
- CubebSinkStream* impl = static_cast<CubebSinkStream*>(user_data);
- u8* buffer = reinterpret_cast<u8*>(output_buffer);
+long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
+ [[maybe_unused]] const void* input_buffer, void* output_buffer,
+ long num_frames) {
+ auto* impl = static_cast<CubebSinkStream*>(user_data);
+ auto* buffer = static_cast<u8*>(output_buffer);
if (!impl) {
return {};
@@ -193,6 +207,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
const std::size_t samples_to_write = num_channels * num_frames;
std::size_t samples_written;
+ /*
if (Settings::values.enable_audio_stretching.GetValue()) {
const std::vector<s16> in{impl->queue.Pop()};
const std::size_t num_in{in.size() / num_channels};
@@ -207,7 +222,8 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
}
} else {
samples_written = impl->queue.Pop(buffer, samples_to_write);
- }
+ }*/
+ samples_written = impl->queue.Pop(buffer, samples_to_write);
if (samples_written >= num_channels) {
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
@@ -222,7 +238,9 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
return num_frames;
}
-void CubebSinkStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {}
+void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream,
+ [[maybe_unused]] void* user_data,
+ [[maybe_unused]] cubeb_state state) {}
std::vector<std::string> ListCubebSinkDevices() {
std::vector<std::string> device_list;
diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp
new file mode 100644
index 000000000..f4e4dd8d2
--- /dev/null
+++ b/src/audio_core/delay_line.cpp
@@ -0,0 +1,104 @@
+#include <cstring>
+#include "audio_core/delay_line.h"
+
+namespace AudioCore {
+DelayLineBase::DelayLineBase() = default;
+DelayLineBase::~DelayLineBase() = default;
+
+void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) {
+ buffer = src_buffer;
+ buffer_end = buffer + max_delay_;
+ max_delay = max_delay_;
+ output = buffer;
+ SetDelay(max_delay_);
+ Clear();
+}
+
+void DelayLineBase::SetDelay(s32 new_delay) {
+ if (max_delay < new_delay) {
+ return;
+ }
+ delay = new_delay;
+ input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1));
+}
+
+s32 DelayLineBase::GetDelay() const {
+ return delay;
+}
+
+s32 DelayLineBase::GetMaxDelay() const {
+ return max_delay;
+}
+
+f32 DelayLineBase::TapOut(s32 last_sample) {
+ const float* ptr = input - (last_sample + 1);
+ if (ptr < buffer) {
+ ptr += (max_delay + 1);
+ }
+
+ return *ptr;
+}
+
+f32 DelayLineBase::Tick(f32 sample) {
+ *(input++) = sample;
+ const auto out_sample = *(output++);
+
+ if (buffer_end < input) {
+ input = buffer;
+ }
+
+ if (buffer_end < output) {
+ output = buffer;
+ }
+
+ return out_sample;
+}
+
+float* DelayLineBase::GetInput() {
+ return input;
+}
+
+const float* DelayLineBase::GetInput() const {
+ return input;
+}
+
+f32 DelayLineBase::GetOutputSample() const {
+ return *output;
+}
+
+void DelayLineBase::Clear() {
+ std::memset(buffer, 0, sizeof(float) * max_delay);
+}
+
+void DelayLineBase::Reset() {
+ buffer = nullptr;
+ buffer_end = nullptr;
+ max_delay = 0;
+ input = nullptr;
+ output = nullptr;
+ delay = 0;
+}
+
+DelayLineAllPass::DelayLineAllPass() = default;
+DelayLineAllPass::~DelayLineAllPass() = default;
+
+void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) {
+ DelayLineBase::Initialize(delay_, src_buffer);
+ SetCoefficient(coeffcient_);
+}
+
+void DelayLineAllPass::SetCoefficient(float coeffcient_) {
+ coefficient = coeffcient_;
+}
+
+f32 DelayLineAllPass::Tick(f32 sample) {
+ const auto temp = sample - coefficient * *output;
+ return coefficient * temp + DelayLineBase::Tick(temp);
+}
+
+void DelayLineAllPass::Reset() {
+ coefficient = 0.0f;
+ DelayLineBase::Reset();
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h
new file mode 100644
index 000000000..cafddd432
--- /dev/null
+++ b/src/audio_core/delay_line.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+class DelayLineBase {
+public:
+ DelayLineBase();
+ ~DelayLineBase();
+
+ void Initialize(s32 max_delay_, float* src_buffer);
+ void SetDelay(s32 new_delay);
+ s32 GetDelay() const;
+ s32 GetMaxDelay() const;
+ f32 TapOut(s32 last_sample);
+ f32 Tick(f32 sample);
+ float* GetInput();
+ const float* GetInput() const;
+ f32 GetOutputSample() const;
+ void Clear();
+ void Reset();
+
+protected:
+ float* buffer{nullptr};
+ float* buffer_end{nullptr};
+ s32 max_delay{};
+ float* input{nullptr};
+ float* output{nullptr};
+ s32 delay{};
+};
+
+class DelayLineAllPass final : public DelayLineBase {
+public:
+ DelayLineAllPass();
+ ~DelayLineAllPass();
+
+ void Initialize(u32 delay, float coeffcient_, f32* src_buffer);
+ void SetCoefficient(float coeffcient_);
+ f32 Tick(f32 sample);
+ void Reset();
+
+private:
+ float coefficient{};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp
new file mode 100644
index 000000000..89e4573c7
--- /dev/null
+++ b/src/audio_core/effect_context.cpp
@@ -0,0 +1,321 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include "audio_core/effect_context.h"
+
+namespace AudioCore {
+namespace {
+bool ValidChannelCountForEffect(s32 channel_count) {
+ return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
+}
+} // namespace
+
+EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) {
+ effects.reserve(effect_count);
+ std::generate_n(std::back_inserter(effects), effect_count,
+ [] { return std::make_unique<EffectStubbed>(); });
+}
+EffectContext::~EffectContext() = default;
+
+std::size_t EffectContext::GetCount() const {
+ return effect_count;
+}
+
+EffectBase* EffectContext::GetInfo(std::size_t i) {
+ return effects.at(i).get();
+}
+
+EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
+ switch (effect) {
+ case EffectType::Invalid:
+ effects[i] = std::make_unique<EffectStubbed>();
+ break;
+ case EffectType::BufferMixer:
+ effects[i] = std::make_unique<EffectBufferMixer>();
+ break;
+ case EffectType::Aux:
+ effects[i] = std::make_unique<EffectAuxInfo>();
+ break;
+ case EffectType::Delay:
+ effects[i] = std::make_unique<EffectDelay>();
+ break;
+ case EffectType::Reverb:
+ effects[i] = std::make_unique<EffectReverb>();
+ break;
+ case EffectType::I3dl2Reverb:
+ effects[i] = std::make_unique<EffectI3dl2Reverb>();
+ break;
+ case EffectType::BiquadFilter:
+ effects[i] = std::make_unique<EffectBiquadFilter>();
+ break;
+ default:
+ UNREACHABLE_MSG("Unimplemented effect {}", effect);
+ effects[i] = std::make_unique<EffectStubbed>();
+ }
+ return GetInfo(i);
+}
+
+const EffectBase* EffectContext::GetInfo(std::size_t i) const {
+ return effects.at(i).get();
+}
+
+EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {}
+EffectStubbed::~EffectStubbed() = default;
+
+void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {}
+void EffectStubbed::UpdateForCommandGeneration() {}
+
+EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {}
+EffectBase::~EffectBase() = default;
+
+UsageState EffectBase::GetUsage() const {
+ return usage;
+}
+
+EffectType EffectBase::GetType() const {
+ return effect_type;
+}
+
+bool EffectBase::IsEnabled() const {
+ return enabled;
+}
+
+s32 EffectBase::GetMixID() const {
+ return mix_id;
+}
+
+s32 EffectBase::GetProcessingOrder() const {
+ return processing_order;
+}
+
+std::vector<u8>& EffectBase::GetWorkBuffer() {
+ return work_buffer;
+}
+
+const std::vector<u8>& EffectBase::GetWorkBuffer() const {
+ return work_buffer;
+}
+
+EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {}
+EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
+
+void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
+ auto& params = GetParams();
+ const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
+ if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
+ UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
+ return;
+ }
+
+ const auto last_status = params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ params = *reverb_params;
+ if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
+ params.channel_count = params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+ if (last_status != ParameterStatus::Updated) {
+ params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ if (!skipped) {
+ auto& cur_work_buffer = GetWorkBuffer();
+ // Has two buffers internally
+ cur_work_buffer.resize(in_params.buffer_size * 2);
+ std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0);
+ }
+ }
+}
+
+void EffectI3dl2Reverb::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+I3dl2ReverbState& EffectI3dl2Reverb::GetState() {
+ return state;
+}
+
+const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const {
+ return state;
+}
+
+EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}
+EffectBiquadFilter::~EffectBiquadFilter() = default;
+
+void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
+ auto& params = GetParams();
+ const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ params = *biquad_params;
+ enabled = in_params.is_enabled;
+}
+
+void EffectBiquadFilter::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {}
+EffectAuxInfo::~EffectAuxInfo() = default;
+
+void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
+ const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ GetParams() = *aux_params;
+ enabled = in_params.is_enabled;
+
+ if (in_params.is_new || skipped) {
+ skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
+ if (skipped) {
+ return;
+ }
+
+ // There's two AuxInfos which are an identical size, the first one is managed by the cpu,
+ // the second is managed by the dsp. All we care about is managing the DSP one
+ send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
+ send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
+
+ recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
+ recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
+ }
+}
+
+void EffectAuxInfo::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+}
+
+VAddr EffectAuxInfo::GetSendInfo() const {
+ return send_info;
+}
+
+VAddr EffectAuxInfo::GetSendBuffer() const {
+ return send_buffer;
+}
+
+VAddr EffectAuxInfo::GetRecvInfo() const {
+ return recv_info;
+}
+
+VAddr EffectAuxInfo::GetRecvBuffer() const {
+ return recv_buffer;
+}
+
+EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {}
+EffectDelay::~EffectDelay() = default;
+
+void EffectDelay::Update(EffectInfo::InParams& in_params) {
+ const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
+ auto& params = GetParams();
+ if (!ValidChannelCountForEffect(delay_params->max_channels)) {
+ return;
+ }
+
+ const auto last_status = params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ params = *delay_params;
+ if (!ValidChannelCountForEffect(delay_params->channels)) {
+ params.channels = params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+
+ if (last_status != ParameterStatus::Updated) {
+ params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ }
+}
+
+void EffectDelay::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {}
+EffectBufferMixer::~EffectBufferMixer() = default;
+
+void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
+ enabled = in_params.is_enabled;
+}
+
+void EffectBufferMixer::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+}
+
+EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {}
+EffectReverb::~EffectReverb() = default;
+
+void EffectReverb::Update(EffectInfo::InParams& in_params) {
+ const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
+ auto& params = GetParams();
+ if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
+ return;
+ }
+
+ const auto last_status = params.status;
+ mix_id = in_params.mix_id;
+ processing_order = in_params.processing_order;
+ params = *reverb_params;
+ if (!ValidChannelCountForEffect(reverb_params->channels)) {
+ params.channels = params.max_channels;
+ }
+ enabled = in_params.is_enabled;
+
+ if (last_status != ParameterStatus::Updated) {
+ params.status = last_status;
+ }
+
+ if (in_params.is_new || skipped) {
+ usage = UsageState::Initialized;
+ params.status = ParameterStatus::Initialized;
+ skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ }
+}
+
+void EffectReverb::UpdateForCommandGeneration() {
+ if (enabled) {
+ usage = UsageState::Running;
+ } else {
+ usage = UsageState::Stopped;
+ }
+ GetParams().status = ParameterStatus::Updated;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
new file mode 100644
index 000000000..5e0655dd7
--- /dev/null
+++ b/src/audio_core/effect_context.h
@@ -0,0 +1,350 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <vector>
+#include "audio_core/common.h"
+#include "audio_core/delay_line.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+enum class EffectType : u8 {
+ Invalid = 0,
+ BufferMixer = 1,
+ Aux = 2,
+ Delay = 3,
+ Reverb = 4,
+ I3dl2Reverb = 5,
+ BiquadFilter = 6,
+};
+
+enum class UsageStatus : u8 {
+ Invalid = 0,
+ New = 1,
+ Initialized = 2,
+ Used = 3,
+ Removed = 4,
+};
+
+enum class UsageState {
+ Invalid = 0,
+ Initialized = 1,
+ Running = 2,
+ Stopped = 3,
+};
+
+enum class ParameterStatus : u8 {
+ Initialized = 0,
+ Updating = 1,
+ Updated = 2,
+};
+
+struct BufferMixerParams {
+ std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
+ std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
+ std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
+ s32_le count{};
+};
+static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
+
+struct AuxInfoDSP {
+ u32_le read_offset{};
+ u32_le write_offset{};
+ u32_le remaining{};
+ INSERT_PADDING_WORDS(13);
+};
+static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
+
+struct AuxInfo {
+ std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
+ std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
+ u32_le count{};
+ s32_le sample_rate{};
+ s32_le sample_count{};
+ s32_le mix_buffer_count{};
+ u64_le send_buffer_info{};
+ u64_le send_buffer_base{};
+
+ u64_le return_buffer_info{};
+ u64_le return_buffer_base{};
+};
+static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
+
+struct I3dl2ReverbParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channel_count{};
+ INSERT_PADDING_BYTES(1);
+ u32_le sample_rate{};
+ f32 room_hf{};
+ f32 hf_reference{};
+ f32 decay_time{};
+ f32 hf_decay_ratio{};
+ f32 room{};
+ f32 reflection{};
+ f32 reverb{};
+ f32 diffusion{};
+ f32 reflection_delay{};
+ f32 reverb_delay{};
+ f32 density{};
+ f32 dry_gain{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
+
+struct BiquadFilterParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ std::array<s16_le, 3> numerator;
+ std::array<s16_le, 2> denominator;
+ s8 channel_count{};
+ ParameterStatus status{};
+};
+static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
+
+struct DelayParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channels{};
+ s32_le max_delay{};
+ s32_le delay{};
+ s32_le sample_rate{};
+ s32_le gain{};
+ s32_le feedback_gain{};
+ s32_le out_gain{};
+ s32_le dry_gain{};
+ s32_le channel_spread{};
+ s32_le low_pass{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
+
+struct ReverbParams {
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
+ u16_le max_channels{};
+ u16_le channels{};
+ s32_le sample_rate{};
+ s32_le mode0{};
+ s32_le mode0_gain{};
+ s32_le pre_delay{};
+ s32_le mode1{};
+ s32_le mode1_gain{};
+ s32_le decay{};
+ s32_le hf_decay_ratio{};
+ s32_le coloration{};
+ s32_le reverb_gain{};
+ s32_le out_gain{};
+ s32_le dry_gain{};
+ ParameterStatus status{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
+
+class EffectInfo {
+public:
+ struct InParams {
+ EffectType type{};
+ u8 is_new{};
+ u8 is_enabled{};
+ INSERT_PADDING_BYTES(1);
+ s32_le mix_id{};
+ u64_le buffer_address{};
+ u64_le buffer_size{};
+ s32_le processing_order{};
+ INSERT_PADDING_BYTES(4);
+ union {
+ std::array<u8, 0xa0> raw;
+ };
+ };
+ static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size");
+
+ struct OutParams {
+ UsageStatus status{};
+ INSERT_PADDING_BYTES(15);
+ };
+ static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
+};
+
+struct AuxAddress {
+ VAddr send_dsp_info{};
+ VAddr send_buffer_base{};
+ VAddr return_dsp_info{};
+ VAddr return_buffer_base{};
+};
+
+class EffectBase {
+public:
+ explicit EffectBase(EffectType effect_type_);
+ virtual ~EffectBase();
+
+ virtual void Update(EffectInfo::InParams& in_params) = 0;
+ virtual void UpdateForCommandGeneration() = 0;
+ [[nodiscard]] UsageState GetUsage() const;
+ [[nodiscard]] EffectType GetType() const;
+ [[nodiscard]] bool IsEnabled() const;
+ [[nodiscard]] s32 GetMixID() const;
+ [[nodiscard]] s32 GetProcessingOrder() const;
+ [[nodiscard]] std::vector<u8>& GetWorkBuffer();
+ [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const;
+
+protected:
+ UsageState usage{UsageState::Invalid};
+ EffectType effect_type{};
+ s32 mix_id{};
+ s32 processing_order{};
+ bool enabled = false;
+ std::vector<u8> work_buffer{};
+};
+
+template <typename T>
+class EffectGeneric : public EffectBase {
+public:
+ explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {}
+
+ T& GetParams() {
+ return internal_params;
+ }
+
+ const T& GetParams() const {
+ return internal_params;
+ }
+
+private:
+ T internal_params{};
+};
+
+class EffectStubbed : public EffectBase {
+public:
+ explicit EffectStubbed();
+ ~EffectStubbed() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+struct I3dl2ReverbState {
+ f32 lowpass_0{};
+ f32 lowpass_1{};
+ f32 lowpass_2{};
+
+ DelayLineBase early_delay_line{};
+ std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{};
+ f32 early_gain{};
+ f32 late_gain{};
+
+ u32 early_to_late_taps{};
+ std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{};
+ std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{};
+ std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{};
+ f32 last_reverb_echo{};
+ DelayLineBase center_delay_line{};
+ std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{};
+ std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{};
+ f32 dry_gain{};
+};
+
+class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
+public:
+ explicit EffectI3dl2Reverb();
+ ~EffectI3dl2Reverb() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+ I3dl2ReverbState& GetState();
+ const I3dl2ReverbState& GetState() const;
+
+private:
+ bool skipped = false;
+ I3dl2ReverbState state{};
+};
+
+class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
+public:
+ explicit EffectBiquadFilter();
+ ~EffectBiquadFilter() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+class EffectAuxInfo : public EffectGeneric<AuxInfo> {
+public:
+ explicit EffectAuxInfo();
+ ~EffectAuxInfo() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+ [[nodiscard]] VAddr GetSendInfo() const;
+ [[nodiscard]] VAddr GetSendBuffer() const;
+ [[nodiscard]] VAddr GetRecvInfo() const;
+ [[nodiscard]] VAddr GetRecvBuffer() const;
+
+private:
+ VAddr send_info{};
+ VAddr send_buffer{};
+ VAddr recv_info{};
+ VAddr recv_buffer{};
+ bool skipped = false;
+ AuxAddress addresses{};
+};
+
+class EffectDelay : public EffectGeneric<DelayParams> {
+public:
+ explicit EffectDelay();
+ ~EffectDelay() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+private:
+ bool skipped = false;
+};
+
+class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
+public:
+ explicit EffectBufferMixer();
+ ~EffectBufferMixer() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+};
+
+class EffectReverb : public EffectGeneric<ReverbParams> {
+public:
+ explicit EffectReverb();
+ ~EffectReverb() override;
+
+ void Update(EffectInfo::InParams& in_params) override;
+ void UpdateForCommandGeneration() override;
+
+private:
+ bool skipped = false;
+};
+
+class EffectContext {
+public:
+ explicit EffectContext(std::size_t effect_count_);
+ ~EffectContext();
+
+ [[nodiscard]] std::size_t GetCount() const;
+ [[nodiscard]] EffectBase* GetInfo(std::size_t i);
+ [[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect);
+ [[nodiscard]] const EffectBase* GetInfo(std::size_t i) const;
+
+private:
+ std::size_t effect_count{};
+ std::vector<std::unique_ptr<EffectBase>> effects;
+};
+} // namespace AudioCore
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
new file mode 100644
index 000000000..d3ac90827
--- /dev/null
+++ b/src/audio_core/info_updater.cpp
@@ -0,0 +1,516 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/behavior_info.h"
+#include "audio_core/effect_context.h"
+#include "audio_core/info_updater.h"
+#include "audio_core/memory_pool.h"
+#include "audio_core/mix_context.h"
+#include "audio_core/sink_context.h"
+#include "audio_core/splitter_context.h"
+#include "audio_core/voice_context.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+InfoUpdater::InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_,
+ BehaviorInfo& behavior_info_)
+ : in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) {
+ ASSERT(
+ AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
+ std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
+ output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
+}
+
+InfoUpdater::~InfoUpdater() = default;
+
+bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
+ if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
+ LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ sizeof(BehaviorInfo::InParams), input_header.size.behavior);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
+ sizeof(BehaviorInfo::InParams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ BehaviorInfo::InParams behavior_in{};
+ std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
+ input_offset += sizeof(BehaviorInfo::InParams);
+
+ // Make sure it's an audio revision we can actually support
+ if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
+ LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
+ return false;
+ }
+
+ // Make sure that our behavior info revision matches the input
+ if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
+ LOG_ERROR(Audio,
+ "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
+ in_behavior_info.GetUserRevision(), behavior_in.revision);
+ return false;
+ }
+
+ // Update behavior info flags
+ in_behavior_info.ClearError();
+ in_behavior_info.UpdateFlags(behavior_in.flags);
+
+ return true;
+}
+
+bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
+ const auto memory_pool_count = memory_pool_info.size();
+ const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
+ const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
+
+ if (input_header.size.memory_pool != total_memory_pool_in) {
+ LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_memory_pool_in, input_header.size.memory_pool);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
+ std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
+
+ std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
+ input_offset += total_memory_pool_in;
+
+ // Update our memory pools
+ for (std::size_t i = 0; i < memory_pool_count; i++) {
+ if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
+ LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
+ return false;
+ }
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
+ sizeof(BehaviorInfo::InParams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
+ output_offset += total_memory_pool_out;
+ output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
+ return true;
+}
+
+bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
+ const auto voice_count = voice_context.GetVoiceCount();
+ const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
+ std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
+
+ if (input_header.size.voice_channel_resource != voice_size) {
+ LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ voice_size, input_header.size.voice_channel_resource);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
+ input_offset += voice_size;
+
+ // Update our channel resources
+ for (std::size_t i = 0; i < voice_count; i++) {
+ // Grab our channel resource
+ auto& resource = voice_context.GetChannelResource(i);
+ resource.Update(resources_in[i]);
+ }
+
+ return true;
+}
+
+bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
+ [[maybe_unused]] std::vector<ServerMemoryPoolInfo>& memory_pool_info,
+ [[maybe_unused]] VAddr audio_codec_dsp_addr) {
+ const auto voice_count = voice_context.GetVoiceCount();
+ std::vector<VoiceInfo::InParams> voice_in(voice_count);
+ std::vector<VoiceInfo::OutParams> voice_out(voice_count);
+
+ const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
+ const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
+
+ if (input_header.size.voice != voice_in_size) {
+ LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
+ voice_in_size, input_header.size.voice);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
+ input_offset += voice_in_size;
+
+ // Set all voices to not be in use
+ for (std::size_t i = 0; i < voice_count; i++) {
+ voice_context.GetInfo(i).GetInParams().in_use = false;
+ }
+
+ // Update our voices
+ for (std::size_t i = 0; i < voice_count; i++) {
+ auto& voice_in_params = voice_in[i];
+ const auto channel_count = static_cast<std::size_t>(voice_in_params.channel_count);
+ // Skip if it's not currently in use
+ if (!voice_in_params.is_in_use) {
+ continue;
+ }
+ // Voice states for each channel
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
+ ASSERT(static_cast<std::size_t>(voice_in_params.id) < voice_count);
+
+ // Grab our current voice info
+ auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(voice_in_params.id));
+
+ ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
+
+ // Get all our channel voice states
+ for (std::size_t channel = 0; channel < channel_count; channel++) {
+ voice_states[channel] =
+ &voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]);
+ }
+
+ if (voice_in_params.is_new) {
+ // Default our values for our voice
+ voice_info.Initialize();
+ if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
+ continue;
+ }
+
+ // Zero out our voice states
+ for (std::size_t channel = 0; channel < channel_count; channel++) {
+ std::memset(voice_states[channel], 0, sizeof(VoiceState));
+ }
+ }
+
+ // Update our voice
+ voice_info.UpdateParameters(voice_in_params, behavior_info);
+ // TODO(ogniK): Handle mapping errors with behavior info based on in params response
+
+ // Update our wave buffers
+ voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info);
+ voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states);
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
+ output_offset += voice_out_size;
+ output_header.size.voice = static_cast<u32>(voice_out_size);
+ return true;
+}
+
+bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
+ const auto effect_count = effect_context.GetCount();
+ std::vector<EffectInfo::InParams> effect_in(effect_count);
+ std::vector<EffectInfo::OutParams> effect_out(effect_count);
+
+ const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
+ const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
+
+ if (input_header.size.effect != total_effect_in) {
+ LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_effect_in, input_header.size.effect);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
+ input_offset += total_effect_in;
+
+ // Update effects
+ for (std::size_t i = 0; i < effect_count; i++) {
+ auto* info = effect_context.GetInfo(i);
+ if (effect_in[i].type != info->GetType()) {
+ info = effect_context.RetargetEffect(i, effect_in[i].type);
+ }
+
+ info->Update(effect_in[i]);
+
+ if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
+ info->GetUsage() == UsageState::Stopped) {
+ effect_out[i].status = UsageStatus::Removed;
+ } else {
+ effect_out[i].status = UsageStatus::Used;
+ }
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
+ output_offset += total_effect_out;
+ output_header.size.effect = static_cast<u32>(total_effect_out);
+
+ return true;
+}
+
+bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
+ std::size_t start_offset = input_offset;
+ std::size_t bytes_read{};
+ // Update splitter context
+ if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
+ LOG_ERROR(Audio, "Failed to update splitter context!");
+ return false;
+ }
+
+ const auto consumed = input_offset - start_offset;
+
+ if (input_header.size.splitter != consumed) {
+ LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ bytes_read, input_header.size.splitter);
+ return false;
+ }
+
+ return true;
+}
+
+ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
+ SplitterContext& splitter_context,
+ EffectContext& effect_context) {
+ std::vector<MixInfo::InParams> mix_in_params;
+
+ if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ // If we're not dirty, get ALL mix in parameters
+ const auto context_mix_count = mix_context.GetCount();
+ const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
+ if (input_header.size.mixer != total_mix_in) {
+ LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_mix_in, input_header.size.mixer);
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ mix_in_params.resize(context_mix_count);
+ std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
+
+ input_offset += total_mix_in;
+ } else {
+ // Only update the "dirty" mixes
+ MixInfo::DirtyHeader dirty_header{};
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
+ sizeof(MixInfo::DirtyHeader))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
+ input_offset += sizeof(MixInfo::DirtyHeader);
+
+ const auto total_mix_in =
+ dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
+
+ if (input_header.size.mixer != total_mix_in) {
+ LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_mix_in, input_header.size.mixer);
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ if (dirty_header.mixer_count != 0) {
+ mix_in_params.resize(dirty_header.mixer_count);
+ std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
+ mix_in_params.size() * sizeof(MixInfo::InParams));
+ input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
+ }
+ }
+
+ // Get our total input count
+ const auto mix_count = mix_in_params.size();
+
+ if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ // Only verify our buffer count if we're not dirty
+ std::size_t total_buffer_count{};
+ for (std::size_t i = 0; i < mix_count; i++) {
+ const auto& in = mix_in_params[i];
+ total_buffer_count += in.buffer_count;
+ if (static_cast<std::size_t>(in.dest_mix_id) > mix_count &&
+ in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) {
+ LOG_ERROR(
+ Audio,
+ "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
+ in.mix_id, in.dest_mix_id, mix_buffer_count);
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+ }
+
+ if (total_buffer_count > mix_buffer_count) {
+ LOG_ERROR(Audio,
+ "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
+ mix_buffer_count, total_buffer_count);
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+ }
+
+ if (mix_buffer_count == 0) {
+ LOG_ERROR(Audio, "No mix buffers!");
+ return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
+ }
+
+ bool should_sort = false;
+ for (std::size_t i = 0; i < mix_count; i++) {
+ const auto& mix_in = mix_in_params[i];
+ std::size_t target_mix{};
+ if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
+ target_mix = mix_in.mix_id;
+ } else {
+ // Non dirty supported games just use i instead of the actual mix_id
+ target_mix = i;
+ }
+ auto& mix_info = mix_context.GetInfo(target_mix);
+ auto& mix_info_params = mix_info.GetInParams();
+ if (mix_info_params.in_use != mix_in.in_use) {
+ mix_info_params.in_use = mix_in.in_use;
+ mix_info.ResetEffectProcessingOrder();
+ should_sort = true;
+ }
+
+ if (mix_in.in_use) {
+ should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
+ splitter_context, effect_context);
+ }
+ }
+
+ if (should_sort && behavior_info.IsSplitterSupported()) {
+ // Sort our splitter data
+ if (!mix_context.TsortInfo(splitter_context)) {
+ return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
+ }
+ }
+
+ // TODO(ogniK): Sort when splitter is suppoorted
+
+ return RESULT_SUCCESS;
+}
+
+bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
+ const auto sink_count = sink_context.GetCount();
+ std::vector<SinkInfo::InParams> sink_in_params(sink_count);
+ const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
+
+ if (input_header.size.sink != total_sink_in) {
+ LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
+ total_sink_in, input_header.size.effect);
+ return false;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
+ input_offset += total_sink_in;
+
+ // TODO(ogniK): Properly update sinks
+ if (!sink_in_params.empty()) {
+ sink_context.UpdateMainSink(sink_in_params[0]);
+ }
+
+ output_header.size.sink = static_cast<u32>(0x20 * sink_count);
+ output_offset += 0x20 * sink_count;
+ return true;
+}
+
+bool InfoUpdater::UpdatePerformanceBuffer() {
+ output_header.size.performance = 0x10;
+ output_offset += 0x10;
+ return true;
+}
+
+bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) {
+ const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
+
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+
+ BehaviorInfo::OutParams behavior_info_out{};
+ behavior_info.CopyErrorInfo(behavior_info_out);
+
+ std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
+ output_offset += total_beahvior_info_out;
+ output_header.size.behavior = total_beahvior_info_out;
+
+ return true;
+}
+
+struct RendererInfo {
+ u64_le elasped_frame_count{};
+ INSERT_PADDING_WORDS(2);
+};
+static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
+
+bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
+ const auto total_renderer_info_out = sizeof(RendererInfo);
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ RendererInfo out{};
+ out.elasped_frame_count = elapsed_frame_count;
+ std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
+ output_offset += total_renderer_info_out;
+ output_header.size.render_info = total_renderer_info_out;
+
+ return true;
+}
+
+bool InfoUpdater::CheckConsumedSize() const {
+ if (output_offset != out_params.size()) {
+ LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
+ output_offset, out_params.size(), out_params.size() - output_offset);
+ return false;
+ }
+ /*if (input_offset != in_params.size()) {
+ LOG_ERROR(Audio, "Input is not consumed!");
+ return false;
+ }*/
+ return true;
+}
+
+bool InfoUpdater::WriteOutputHeader() {
+ if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
+ sizeof(AudioCommon::UpdateDataHeader))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
+ const auto& sz = output_header.size;
+ output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
+ sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
+ sz.performance + sz.splitter + sz.render_info;
+
+ std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
+ return true;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h
new file mode 100644
index 000000000..d315c91ed
--- /dev/null
+++ b/src/audio_core/info_updater.h
@@ -0,0 +1,58 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "audio_core/common.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+class BehaviorInfo;
+class ServerMemoryPoolInfo;
+class VoiceContext;
+class EffectContext;
+class MixContext;
+class SinkContext;
+class SplitterContext;
+
+class InfoUpdater {
+public:
+ // TODO(ogniK): Pass process handle when we support it
+ InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_,
+ BehaviorInfo& behavior_info_);
+ ~InfoUpdater();
+
+ bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
+ bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
+ bool UpdateVoiceChannelResources(VoiceContext& voice_context);
+ bool UpdateVoices(VoiceContext& voice_context,
+ std::vector<ServerMemoryPoolInfo>& memory_pool_info,
+ VAddr audio_codec_dsp_addr);
+ bool UpdateEffects(EffectContext& effect_context, bool is_active);
+ bool UpdateSplitterInfo(SplitterContext& splitter_context);
+ ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
+ SplitterContext& splitter_context, EffectContext& effect_context);
+ bool UpdateSinks(SinkContext& sink_context);
+ bool UpdatePerformanceBuffer();
+ bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
+ bool UpdateRendererInfo(std::size_t elapsed_frame_count);
+ bool CheckConsumedSize() const;
+
+ bool WriteOutputHeader();
+
+private:
+ const std::vector<u8>& in_params;
+ std::vector<u8>& out_params;
+ BehaviorInfo& behavior_info;
+
+ AudioCommon::UpdateDataHeader input_header{};
+ AudioCommon::UpdateDataHeader output_header{};
+
+ std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
+ std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp
new file mode 100644
index 000000000..6b6908d26
--- /dev/null
+++ b/src/audio_core/memory_pool.cpp
@@ -0,0 +1,61 @@
+
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/memory_pool.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
+ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
+
+bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) {
+ // Our state does not need to be changed
+ if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) {
+ return true;
+ }
+
+ // Address or size is null
+ if (in_params.address == 0 || in_params.size == 0) {
+ LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
+ in_params.address, in_params.size);
+ return false;
+ }
+
+ // Address or size is not aligned
+ if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
+ LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
+ in_params.address, in_params.size);
+ return false;
+ }
+
+ if (in_params.state == State::RequestAttach) {
+ cpu_address = in_params.address;
+ size = in_params.size;
+ used = true;
+ out_params.state = State::Attached;
+ } else {
+ // Unexpected address
+ if (cpu_address != in_params.address) {
+ LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
+ cpu_address, in_params.address);
+ return false;
+ }
+
+ if (size != in_params.size) {
+ LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
+ in_params.size);
+ return false;
+ }
+
+ cpu_address = 0;
+ size = 0;
+ used = false;
+ out_params.state = State::Detached;
+ }
+ return true;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h
new file mode 100644
index 000000000..3e9e777ae
--- /dev/null
+++ b/src/audio_core/memory_pool.h
@@ -0,0 +1,52 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+
+class ServerMemoryPoolInfo {
+public:
+ ServerMemoryPoolInfo();
+ ~ServerMemoryPoolInfo();
+
+ enum class State : u32_le {
+ Invalid = 0x0,
+ Aquired = 0x1,
+ RequestDetach = 0x2,
+ Detached = 0x3,
+ RequestAttach = 0x4,
+ Attached = 0x5,
+ Released = 0x6,
+ };
+
+ struct InParams {
+ u64_le address{};
+ u64_le size{};
+ State state{};
+ INSERT_PADDING_WORDS(3);
+ };
+ static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size");
+
+ struct OutParams {
+ State state{};
+ INSERT_PADDING_WORDS(3);
+ };
+ static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size");
+
+ bool Update(const InParams& in_params, OutParams& out_params);
+
+private:
+ // There's another entry here which is the DSP address, however since we're not talking to the
+ // DSP we can just use the same address provided by the guest without needing to remap
+ u64_le cpu_address{};
+ u64_le size{};
+ bool used{};
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp
new file mode 100644
index 000000000..4bca72eb0
--- /dev/null
+++ b/src/audio_core/mix_context.cpp
@@ -0,0 +1,296 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/behavior_info.h"
+#include "audio_core/common.h"
+#include "audio_core/effect_context.h"
+#include "audio_core/mix_context.h"
+#include "audio_core/splitter_context.h"
+
+namespace AudioCore {
+MixContext::MixContext() = default;
+MixContext::~MixContext() = default;
+
+void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
+ std::size_t effect_count) {
+ info_count = mix_count;
+ infos.resize(info_count);
+ auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
+ final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
+ sorted_info.reserve(infos.size());
+ for (auto& info : infos) {
+ sorted_info.push_back(&info);
+ }
+
+ for (auto& info : infos) {
+ info.SetEffectCount(effect_count);
+ }
+
+ // Only initialize our edge matrix and node states if splitters are supported
+ if (behavior_info.IsSplitterSupported()) {
+ node_states.Initialize(mix_count);
+ edge_matrix.Initialize(mix_count);
+ }
+}
+
+void MixContext::UpdateDistancesFromFinalMix() {
+ // Set all distances to be invalid
+ for (std::size_t i = 0; i < info_count; i++) {
+ GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
+ }
+
+ for (std::size_t i = 0; i < info_count; i++) {
+ auto& info = GetInfo(i);
+ auto& in_params = info.GetInParams();
+ // Populate our sorted info
+ sorted_info[i] = &info;
+
+ if (!in_params.in_use) {
+ continue;
+ }
+
+ auto mix_id = in_params.mix_id;
+ // Needs to be referenced out of scope
+ s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
+ for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) {
+ if (mix_id == AudioCommon::FINAL_MIX) {
+ // If we're at the final mix, we're done
+ break;
+ } else if (mix_id == AudioCommon::NO_MIX) {
+ // If we have no more mix ids, we're done
+ distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
+ break;
+ } else {
+ const auto& dest_mix = GetInfo(mix_id);
+ const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
+
+ if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
+ // If our current mix isn't pointing to a final mix, follow through
+ mix_id = dest_mix.GetInParams().dest_mix_id;
+ } else {
+ // Our current mix + 1 = final distance
+ distance_to_final_mix = dest_mix_distance + 1;
+ break;
+ }
+ }
+ }
+
+ // If we're out of range for our distance, mark it as no final mix
+ if (distance_to_final_mix >= static_cast<s32>(info_count)) {
+ distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
+ }
+
+ in_params.final_mix_distance = distance_to_final_mix;
+ }
+}
+
+void MixContext::CalcMixBufferOffset() {
+ s32 offset{};
+ for (std::size_t i = 0; i < info_count; i++) {
+ auto& info = GetSortedInfo(i);
+ auto& in_params = info.GetInParams();
+ if (in_params.in_use) {
+ // Only update if in use
+ in_params.buffer_offset = offset;
+ offset += in_params.buffer_count;
+ }
+ }
+}
+
+void MixContext::SortInfo() {
+ // Get the distance to the final mix
+ UpdateDistancesFromFinalMix();
+
+ // Sort based on the distance to the final mix
+ std::sort(sorted_info.begin(), sorted_info.end(),
+ [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
+ return lhs->GetInParams().final_mix_distance >
+ rhs->GetInParams().final_mix_distance;
+ });
+
+ // Calculate the mix buffer offset
+ CalcMixBufferOffset();
+}
+
+bool MixContext::TsortInfo(SplitterContext& splitter_context) {
+ // If we're not using mixes, just calculate the mix buffer offset
+ if (!splitter_context.UsingSplitter()) {
+ CalcMixBufferOffset();
+ return true;
+ }
+ // Sort our node states
+ if (!node_states.Tsort(edge_matrix)) {
+ return false;
+ }
+
+ // Get our sorted list
+ const auto sorted_list = node_states.GetIndexList();
+ std::size_t info_id{};
+ for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
+ // Set our sorted info
+ sorted_info[info_id++] = &GetInfo(*itr);
+ }
+
+ // Calculate the mix buffer offset
+ CalcMixBufferOffset();
+ return true;
+}
+
+std::size_t MixContext::GetCount() const {
+ return info_count;
+}
+
+ServerMixInfo& MixContext::GetInfo(std::size_t i) {
+ ASSERT(i < info_count);
+ return infos.at(i);
+}
+
+const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
+ ASSERT(i < info_count);
+ return infos.at(i);
+}
+
+ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
+ ASSERT(i < info_count);
+ return *sorted_info.at(i);
+}
+
+const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
+ ASSERT(i < info_count);
+ return *sorted_info.at(i);
+}
+
+ServerMixInfo& MixContext::GetFinalMixInfo() {
+ return infos.at(AudioCommon::FINAL_MIX);
+}
+
+const ServerMixInfo& MixContext::GetFinalMixInfo() const {
+ return infos.at(AudioCommon::FINAL_MIX);
+}
+
+EdgeMatrix& MixContext::GetEdgeMatrix() {
+ return edge_matrix;
+}
+
+const EdgeMatrix& MixContext::GetEdgeMatrix() const {
+ return edge_matrix;
+}
+
+ServerMixInfo::ServerMixInfo() {
+ Cleanup();
+}
+ServerMixInfo::~ServerMixInfo() = default;
+
+const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
+ return in_params;
+}
+
+ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
+ return in_params;
+}
+
+bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
+ BehaviorInfo& behavior_info, SplitterContext& splitter_context,
+ EffectContext& effect_context) {
+ in_params.volume = mix_in.volume;
+ in_params.sample_rate = mix_in.sample_rate;
+ in_params.buffer_count = mix_in.buffer_count;
+ in_params.in_use = mix_in.in_use;
+ in_params.mix_id = mix_in.mix_id;
+ in_params.node_id = mix_in.node_id;
+ for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
+ std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
+ in_params.mix_volume[i].begin());
+ }
+
+ bool require_sort = false;
+
+ if (behavior_info.IsSplitterSupported()) {
+ require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
+ } else {
+ in_params.dest_mix_id = mix_in.dest_mix_id;
+ in_params.splitter_id = AudioCommon::NO_SPLITTER;
+ }
+
+ ResetEffectProcessingOrder();
+ const auto effect_count = effect_context.GetCount();
+ for (std::size_t i = 0; i < effect_count; i++) {
+ auto* effect_info = effect_context.GetInfo(i);
+ if (effect_info->GetMixID() == in_params.mix_id) {
+ effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
+ }
+ }
+
+ // TODO(ogniK): Update effect processing order
+ return require_sort;
+}
+
+bool ServerMixInfo::HasAnyConnection() const {
+ return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
+ in_params.mix_id != AudioCommon::NO_MIX;
+}
+
+void ServerMixInfo::Cleanup() {
+ in_params.volume = 0.0f;
+ in_params.sample_rate = 0;
+ in_params.buffer_count = 0;
+ in_params.in_use = false;
+ in_params.mix_id = AudioCommon::NO_MIX;
+ in_params.node_id = 0;
+ in_params.buffer_offset = 0;
+ in_params.dest_mix_id = AudioCommon::NO_MIX;
+ in_params.splitter_id = AudioCommon::NO_SPLITTER;
+ std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
+}
+
+void ServerMixInfo::SetEffectCount(std::size_t count) {
+ effect_processing_order.resize(count);
+ ResetEffectProcessingOrder();
+}
+
+void ServerMixInfo::ResetEffectProcessingOrder() {
+ for (auto& order : effect_processing_order) {
+ order = AudioCommon::NO_EFFECT_ORDER;
+ }
+}
+
+s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
+ return effect_processing_order.at(i);
+}
+
+bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
+ SplitterContext& splitter_context) {
+ // Mixes are identical
+ if (in_params.dest_mix_id == mix_in.dest_mix_id &&
+ in_params.splitter_id == mix_in.splitter_id &&
+ ((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
+ !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
+ return false;
+ }
+ // Remove current edges for mix id
+ edge_matrix.RemoveEdges(in_params.mix_id);
+ if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
+ // If we have a valid destination mix id, set our edge matrix
+ edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
+ } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
+ // Recurse our splitter linked and set our edges
+ auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
+ const auto length = splitter_info.GetLength();
+ for (s32 i = 0; i < length; i++) {
+ const auto* splitter_destination =
+ splitter_context.GetDestinationData(mix_in.splitter_id, i);
+ if (splitter_destination == nullptr) {
+ continue;
+ }
+ if (splitter_destination->ValidMixId()) {
+ edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
+ }
+ }
+ }
+ in_params.dest_mix_id = mix_in.dest_mix_id;
+ in_params.splitter_id = mix_in.splitter_id;
+ return true;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h
new file mode 100644
index 000000000..68bc673c6
--- /dev/null
+++ b/src/audio_core/mix_context.h
@@ -0,0 +1,114 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include "audio_core/common.h"
+#include "audio_core/splitter_context.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace AudioCore {
+class BehaviorInfo;
+class EffectContext;
+
+class MixInfo {
+public:
+ struct DirtyHeader {
+ u32_le magic{};
+ u32_le mixer_count{};
+ INSERT_PADDING_BYTES(0x18);
+ };
+ static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
+
+ struct InParams {
+ float_le volume{};
+ s32_le sample_rate{};
+ s32_le buffer_count{};
+ bool in_use{};
+ INSERT_PADDING_BYTES(3);
+ s32_le mix_id{};
+ s32_le effect_count{};
+ u32_le node_id{};
+ INSERT_PADDING_WORDS(2);
+ std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
+ mix_volume{};
+ s32_le dest_mix_id{};
+ s32_le splitter_id{};
+ INSERT_PADDING_WORDS(1);
+ };
+ static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
+};
+
+class ServerMixInfo {
+public:
+ struct InParams {
+ float volume{};
+ s32 sample_rate{};
+ s32 buffer_count{};
+ bool in_use{};
+ s32 mix_id{};
+ u32 node_id{};
+ std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
+ mix_volume{};
+ s32 dest_mix_id{};
+ s32 splitter_id{};
+ s32 buffer_offset{};
+ s32 final_mix_distance{};
+ };
+ ServerMixInfo();
+ ~ServerMixInfo();
+
+ [[nodiscard]] const ServerMixInfo::InParams& GetInParams() const;
+ [[nodiscard]] ServerMixInfo::InParams& GetInParams();
+
+ bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
+ BehaviorInfo& behavior_info, SplitterContext& splitter_context,
+ EffectContext& effect_context);
+ [[nodiscard]] bool HasAnyConnection() const;
+ void Cleanup();
+ void SetEffectCount(std::size_t count);
+ void ResetEffectProcessingOrder();
+ [[nodiscard]] s32 GetEffectOrder(std::size_t i) const;
+
+private:
+ std::vector<s32> effect_processing_order;
+ InParams in_params{};
+ bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
+ SplitterContext& splitter_context);
+};
+
+class MixContext {
+public:
+ MixContext();
+ ~MixContext();
+
+ void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
+ std::size_t effect_count);
+ void SortInfo();
+ bool TsortInfo(SplitterContext& splitter_context);
+
+ [[nodiscard]] std::size_t GetCount() const;
+ [[nodiscard]] ServerMixInfo& GetInfo(std::size_t i);
+ [[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const;
+ [[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i);
+ [[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const;
+ [[nodiscard]] ServerMixInfo& GetFinalMixInfo();
+ [[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const;
+ [[nodiscard]] EdgeMatrix& GetEdgeMatrix();
+ [[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const;
+
+private:
+ void CalcMixBufferOffset();
+ void UpdateDistancesFromFinalMix();
+
+ NodeStates node_states{};
+ EdgeMatrix edge_matrix{};
+ std::size_t info_count{};
+ std::vector<ServerMixInfo> infos{};
+ std::vector<ServerMixInfo*> sorted_info{};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp
new file mode 100644
index 000000000..a69543696
--- /dev/null
+++ b/src/audio_core/sink_context.cpp
@@ -0,0 +1,45 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/sink_context.h"
+
+namespace AudioCore {
+SinkContext::SinkContext(std::size_t sink_count_) : sink_count{sink_count_} {}
+SinkContext::~SinkContext() = default;
+
+std::size_t SinkContext::GetCount() const {
+ return sink_count;
+}
+
+void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) {
+ ASSERT(in.type == SinkTypes::Device);
+
+ has_downmix_coefs = in.device.down_matrix_enabled;
+ if (has_downmix_coefs) {
+ downmix_coefficients = in.device.down_matrix_coef;
+ }
+ in_use = in.in_use;
+ use_count = in.device.input_count;
+ buffers = in.device.input;
+}
+
+bool SinkContext::InUse() const {
+ return in_use;
+}
+
+std::vector<u8> SinkContext::OutputBuffers() const {
+ std::vector<u8> buffer_ret(use_count);
+ std::memcpy(buffer_ret.data(), buffers.data(), use_count);
+ return buffer_ret;
+}
+
+bool SinkContext::HasDownMixingCoefficients() const {
+ return has_downmix_coefs;
+}
+
+const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const {
+ return downmix_coefficients;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h
new file mode 100644
index 000000000..66ee4e8a0
--- /dev/null
+++ b/src/audio_core/sink_context.h
@@ -0,0 +1,96 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "audio_core/common.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+
+using DownmixCoefficients = std::array<float_le, 4>;
+
+enum class SinkTypes : u8 {
+ Invalid = 0,
+ Device = 1,
+ Circular = 2,
+};
+
+enum class SinkSampleFormat : u32_le {
+ None = 0,
+ Pcm8 = 1,
+ Pcm16 = 2,
+ Pcm24 = 3,
+ Pcm32 = 4,
+ PcmFloat = 5,
+ Adpcm = 6,
+};
+
+class SinkInfo {
+public:
+ struct CircularBufferIn {
+ u64_le address;
+ u32_le size;
+ u32_le input_count;
+ u32_le sample_count;
+ u32_le previous_position;
+ SinkSampleFormat sample_format;
+ std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
+ bool in_use;
+ INSERT_PADDING_BYTES_NOINIT(5);
+ };
+ static_assert(sizeof(CircularBufferIn) == 0x28,
+ "SinkInfo::CircularBufferIn is in invalid size");
+
+ struct DeviceIn {
+ std::array<u8, 255> device_name;
+ INSERT_PADDING_BYTES_NOINIT(1);
+ s32_le input_count;
+ std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
+ INSERT_PADDING_BYTES_NOINIT(1);
+ bool down_matrix_enabled;
+ DownmixCoefficients down_matrix_coef;
+ };
+ static_assert(sizeof(DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
+
+ struct InParams {
+ SinkTypes type{};
+ bool in_use{};
+ INSERT_PADDING_BYTES(2);
+ u32_le node_id{};
+ INSERT_PADDING_WORDS(6);
+ union {
+ // std::array<u8, 0x120> raw{};
+ DeviceIn device;
+ CircularBufferIn circular_buffer;
+ };
+ };
+ static_assert(sizeof(InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
+};
+
+class SinkContext {
+public:
+ explicit SinkContext(std::size_t sink_count_);
+ ~SinkContext();
+
+ [[nodiscard]] std::size_t GetCount() const;
+
+ void UpdateMainSink(const SinkInfo::InParams& in);
+ [[nodiscard]] bool InUse() const;
+ [[nodiscard]] std::vector<u8> OutputBuffers() const;
+
+ [[nodiscard]] bool HasDownMixingCoefficients() const;
+ [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const;
+
+private:
+ bool in_use{false};
+ s32 use_count{};
+ std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
+ std::size_t sink_count{};
+ bool has_downmix_coefs{false};
+ DownmixCoefficients downmix_coefficients{};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp
new file mode 100644
index 000000000..f4bcd0391
--- /dev/null
+++ b/src/audio_core/splitter_context.cpp
@@ -0,0 +1,617 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/behavior_info.h"
+#include "audio_core/splitter_context.h"
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+
+namespace AudioCore {
+
+ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id_) : id{id_} {}
+ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
+
+void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
+ // Log error as these are not actually failure states
+ if (header.magic != SplitterMagic::DataHeader) {
+ LOG_ERROR(Audio, "Splitter destination header is invalid!");
+ return;
+ }
+
+ // Incorrect splitter id
+ if (header.splitter_id != id) {
+ LOG_ERROR(Audio, "Splitter destination ids do not match!");
+ return;
+ }
+
+ mix_id = header.mix_id;
+ // Copy our mix volumes
+ std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
+ if (!in_use && header.in_use) {
+ // Update mix volumes
+ std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
+ needs_update = false;
+ }
+ in_use = header.in_use;
+}
+
+ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
+ return next;
+}
+
+const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
+ return next;
+}
+
+void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
+ next = dest;
+}
+
+bool ServerSplitterDestinationData::ValidMixId() const {
+ return GetMixId() != AudioCommon::NO_MIX;
+}
+
+s32 ServerSplitterDestinationData::GetMixId() const {
+ return mix_id;
+}
+
+bool ServerSplitterDestinationData::IsConfigured() const {
+ return in_use && ValidMixId();
+}
+
+float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
+ ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
+ return current_mix_volumes.at(i);
+}
+
+const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
+ServerSplitterDestinationData::CurrentMixVolumes() const {
+ return current_mix_volumes;
+}
+
+const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
+ServerSplitterDestinationData::LastMixVolumes() const {
+ return last_mix_volumes;
+}
+
+void ServerSplitterDestinationData::MarkDirty() {
+ needs_update = true;
+}
+
+void ServerSplitterDestinationData::UpdateInternalState() {
+ if (in_use && needs_update) {
+ std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
+ }
+ needs_update = false;
+}
+
+ServerSplitterInfo::ServerSplitterInfo(s32 id_) : id(id_) {}
+ServerSplitterInfo::~ServerSplitterInfo() = default;
+
+void ServerSplitterInfo::InitializeInfos() {
+ send_length = 0;
+ head = nullptr;
+ new_connection = true;
+}
+
+void ServerSplitterInfo::ClearNewConnectionFlag() {
+ new_connection = false;
+}
+
+std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
+ if (header.send_id != id) {
+ return 0;
+ }
+
+ sample_rate = header.sample_rate;
+ new_connection = true;
+ // We need to update the size here due to the splitter bug being present and providing an
+ // incorrect size. We're suppose to also update the header here but we just ignore and continue
+ return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
+}
+
+ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
+ return head;
+}
+
+const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
+ return head;
+}
+
+ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
+ auto* current_head = head;
+ for (std::size_t i = 0; i < depth; i++) {
+ if (current_head == nullptr) {
+ return nullptr;
+ }
+ current_head = current_head->GetNextDestination();
+ }
+ return current_head;
+}
+
+const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
+ auto* current_head = head;
+ for (std::size_t i = 0; i < depth; i++) {
+ if (current_head == nullptr) {
+ return nullptr;
+ }
+ current_head = current_head->GetNextDestination();
+ }
+ return current_head;
+}
+
+bool ServerSplitterInfo::HasNewConnection() const {
+ return new_connection;
+}
+
+s32 ServerSplitterInfo::GetLength() const {
+ return send_length;
+}
+
+void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
+ head = new_head;
+}
+
+void ServerSplitterInfo::SetHeadDepth(s32 length) {
+ send_length = length;
+}
+
+SplitterContext::SplitterContext() = default;
+SplitterContext::~SplitterContext() = default;
+
+void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
+ std::size_t _data_count) {
+ if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
+ Setup(0, 0, false);
+ return;
+ }
+ // Only initialize if we're using splitters
+ Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
+}
+
+bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read) {
+ const auto UpdateOffsets = [&](std::size_t read) {
+ input_offset += read;
+ bytes_read += read;
+ };
+
+ if (info_count == 0 || data_count == 0) {
+ bytes_read = 0;
+ return true;
+ }
+
+ if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
+ sizeof(SplitterInfo::InHeader))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ SplitterInfo::InHeader header{};
+ std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
+ UpdateOffsets(sizeof(SplitterInfo::InHeader));
+
+ if (header.magic != SplitterMagic::SplitterHeader) {
+ LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
+ SplitterMagic::SplitterHeader, header.magic);
+ return false;
+ }
+
+ // Clear all connections
+ for (auto& info : infos) {
+ info.ClearNewConnectionFlag();
+ }
+
+ UpdateInfo(input, input_offset, bytes_read, header.info_count);
+ UpdateData(input, input_offset, bytes_read, header.data_count);
+ const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
+ input_offset += aligned_bytes_read - bytes_read;
+ bytes_read = aligned_bytes_read;
+ return true;
+}
+
+bool SplitterContext::UsingSplitter() const {
+ return info_count > 0 && data_count > 0;
+}
+
+ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
+ ASSERT(i < info_count);
+ return infos.at(i);
+}
+
+const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
+ ASSERT(i < info_count);
+ return infos.at(i);
+}
+
+ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
+ ASSERT(i < data_count);
+ return datas.at(i);
+}
+
+const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
+ ASSERT(i < data_count);
+ return datas.at(i);
+}
+
+ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
+ std::size_t data) {
+ ASSERT(info < info_count);
+ auto& cur_info = GetInfo(info);
+ return cur_info.GetData(data);
+}
+
+const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
+ std::size_t data) const {
+ ASSERT(info < info_count);
+ const auto& cur_info = GetInfo(info);
+ return cur_info.GetData(data);
+}
+
+void SplitterContext::UpdateInternalState() {
+ if (data_count == 0) {
+ return;
+ }
+
+ for (auto& data : datas) {
+ data.UpdateInternalState();
+ }
+}
+
+std::size_t SplitterContext::GetInfoCount() const {
+ return info_count;
+}
+
+std::size_t SplitterContext::GetDataCount() const {
+ return data_count;
+}
+
+void SplitterContext::Setup(std::size_t info_count_, std::size_t data_count_,
+ bool is_splitter_bug_fixed) {
+
+ info_count = info_count_;
+ data_count = data_count_;
+
+ for (std::size_t i = 0; i < info_count; i++) {
+ auto& splitter = infos.emplace_back(static_cast<s32>(i));
+ splitter.InitializeInfos();
+ }
+ for (std::size_t i = 0; i < data_count; i++) {
+ datas.emplace_back(static_cast<s32>(i));
+ }
+
+ bug_fixed = is_splitter_bug_fixed;
+}
+
+bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read, s32 in_splitter_count) {
+ const auto UpdateOffsets = [&](std::size_t read) {
+ input_offset += read;
+ bytes_read += read;
+ };
+
+ for (s32 i = 0; i < in_splitter_count; i++) {
+ if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
+ sizeof(SplitterInfo::InInfoPrams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ SplitterInfo::InInfoPrams header{};
+ std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
+
+ // Logged as warning as these don't actually cause a bailout for some reason
+ if (header.magic != SplitterMagic::InfoHeader) {
+ LOG_ERROR(Audio, "Bad splitter data header");
+ break;
+ }
+
+ if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) {
+ LOG_ERROR(Audio, "Bad splitter data id");
+ break;
+ }
+
+ UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
+ auto& info = GetInfo(header.send_id);
+ if (!RecomposeDestination(info, header, input, input_offset)) {
+ LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
+ return false;
+ }
+ const std::size_t read = info.Update(header);
+ bytes_read += read;
+ input_offset += read;
+ }
+ return true;
+}
+
+bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read, s32 in_data_count) {
+ const auto UpdateOffsets = [&](std::size_t read) {
+ input_offset += read;
+ bytes_read += read;
+ };
+
+ for (s32 i = 0; i < in_data_count; i++) {
+ if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
+ sizeof(SplitterInfo::InDestinationParams))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ SplitterInfo::InDestinationParams header{};
+ std::memcpy(&header, input.data() + input_offset,
+ sizeof(SplitterInfo::InDestinationParams));
+ UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
+
+ // Logged as warning as these don't actually cause a bailout for some reason
+ if (header.magic != SplitterMagic::DataHeader) {
+ LOG_ERROR(Audio, "Bad splitter data header");
+ break;
+ }
+
+ if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) {
+ LOG_ERROR(Audio, "Bad splitter data id");
+ break;
+ }
+ GetData(header.splitter_id).Update(header);
+ }
+ return true;
+}
+
+bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
+ SplitterInfo::InInfoPrams& header,
+ const std::vector<u8>& input,
+ const std::size_t& input_offset) {
+ // Clear our current destinations
+ auto* current_head = info.GetHead();
+ while (current_head != nullptr) {
+ auto* next_head = current_head->GetNextDestination();
+ current_head->SetNextDestination(nullptr);
+ current_head = next_head;
+ }
+ info.SetHead(nullptr);
+
+ s32 size = header.length;
+ // If the splitter bug is present, calculate fixed size
+ if (!bug_fixed) {
+ if (info_count > 0) {
+ const auto factor = data_count / info_count;
+ size = std::min(header.length, static_cast<s32>(factor));
+ } else {
+ size = 0;
+ }
+ }
+
+ if (size < 1) {
+ LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
+ return true;
+ }
+
+ auto* start_head = &GetData(header.resource_id_base);
+ current_head = start_head;
+ std::vector<s32_le> resource_ids(size - 1);
+ if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
+ resource_ids.size() * sizeof(s32_le))) {
+ LOG_ERROR(Audio, "Buffer is an invalid size!");
+ return false;
+ }
+ std::memcpy(resource_ids.data(), input.data() + input_offset,
+ resource_ids.size() * sizeof(s32_le));
+
+ for (auto resource_id : resource_ids) {
+ auto* head = &GetData(resource_id);
+ current_head->SetNextDestination(head);
+ current_head = head;
+ }
+
+ info.SetHead(start_head);
+ info.SetHeadDepth(size);
+
+ return true;
+}
+
+NodeStates::NodeStates() = default;
+NodeStates::~NodeStates() = default;
+
+void NodeStates::Initialize(std::size_t node_count_) {
+ // Setup our work parameters
+ node_count = node_count_;
+ was_node_found.resize(node_count);
+ was_node_completed.resize(node_count);
+ index_list.resize(node_count);
+ index_stack.Reset(node_count * node_count);
+}
+
+bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
+ return DepthFirstSearch(edge_matrix);
+}
+
+std::size_t NodeStates::GetIndexPos() const {
+ return index_pos;
+}
+
+const std::vector<s32>& NodeStates::GetIndexList() const {
+ return index_list;
+}
+
+void NodeStates::PushTsortResult(s32 index) {
+ ASSERT(index < static_cast<s32>(node_count));
+ index_list[index_pos++] = index;
+}
+
+bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
+ ResetState();
+ for (std::size_t i = 0; i < node_count; i++) {
+ const auto node_id = static_cast<s32>(i);
+
+ // If we don't have a state, send to our index stack for work
+ if (GetState(i) == NodeStates::State::NoState) {
+ index_stack.push(node_id);
+ }
+
+ // While we have work to do in our stack
+ while (index_stack.Count() > 0) {
+ // Get the current node
+ const auto current_stack_index = index_stack.top();
+ // Check if we've seen the node yet
+ const auto index_state = GetState(current_stack_index);
+ if (index_state == NodeStates::State::NoState) {
+ // Mark the node as seen
+ UpdateState(NodeStates::State::InFound, current_stack_index);
+ } else if (index_state == NodeStates::State::InFound) {
+ // We've seen this node before, mark it as completed
+ UpdateState(NodeStates::State::InCompleted, current_stack_index);
+ // Update our index list
+ PushTsortResult(current_stack_index);
+ // Pop the stack
+ index_stack.pop();
+ continue;
+ } else if (index_state == NodeStates::State::InCompleted) {
+ // If our node is already sorted, clear it
+ index_stack.pop();
+ continue;
+ }
+
+ const auto edge_node_count = edge_matrix.GetNodeCount();
+ for (s32 j = 0; j < static_cast<s32>(edge_node_count); j++) {
+ // Check if our node is connected to our edge matrix
+ if (!edge_matrix.Connected(current_stack_index, j)) {
+ continue;
+ }
+
+ // Check if our node exists
+ const auto node_state = GetState(j);
+ if (node_state == NodeStates::State::NoState) {
+ // Add more work
+ index_stack.push(j);
+ } else if (node_state == NodeStates::State::InFound) {
+ UNREACHABLE_MSG("Node start marked as found");
+ ResetState();
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+void NodeStates::ResetState() {
+ // Reset to the start of our index stack
+ index_pos = 0;
+ for (std::size_t i = 0; i < node_count; i++) {
+ // Mark all nodes as not found
+ was_node_found[i] = false;
+ // Mark all nodes as uncompleted
+ was_node_completed[i] = false;
+ // Mark all indexes as invalid
+ index_list[i] = -1;
+ }
+}
+
+void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
+ switch (state) {
+ case NodeStates::State::NoState:
+ was_node_found[i] = false;
+ was_node_completed[i] = false;
+ break;
+ case NodeStates::State::InFound:
+ was_node_found[i] = true;
+ was_node_completed[i] = false;
+ break;
+ case NodeStates::State::InCompleted:
+ was_node_found[i] = false;
+ was_node_completed[i] = true;
+ break;
+ }
+}
+
+NodeStates::State NodeStates::GetState(std::size_t i) {
+ ASSERT(i < node_count);
+ if (was_node_found[i]) {
+ // If our node exists in our found list
+ return NodeStates::State::InFound;
+ } else if (was_node_completed[i]) {
+ // If node is in the completed list
+ return NodeStates::State::InCompleted;
+ } else {
+ // If in neither
+ return NodeStates::State::NoState;
+ }
+}
+
+NodeStates::Stack::Stack() = default;
+NodeStates::Stack::~Stack() = default;
+
+void NodeStates::Stack::Reset(std::size_t size) {
+ // Mark our stack as empty
+ stack.resize(size);
+ stack_size = size;
+ stack_pos = 0;
+ std::fill(stack.begin(), stack.end(), 0);
+}
+
+void NodeStates::Stack::push(s32 val) {
+ ASSERT(stack_pos < stack_size);
+ stack[stack_pos++] = val;
+}
+
+std::size_t NodeStates::Stack::Count() const {
+ return stack_pos;
+}
+
+s32 NodeStates::Stack::top() const {
+ ASSERT(stack_pos > 0);
+ return stack[stack_pos - 1];
+}
+
+s32 NodeStates::Stack::pop() {
+ ASSERT(stack_pos > 0);
+ stack_pos--;
+ return stack[stack_pos];
+}
+
+EdgeMatrix::EdgeMatrix() = default;
+EdgeMatrix::~EdgeMatrix() = default;
+
+void EdgeMatrix::Initialize(std::size_t _node_count) {
+ node_count = _node_count;
+ edge_matrix.resize(node_count * node_count);
+}
+
+bool EdgeMatrix::Connected(s32 a, s32 b) {
+ return GetState(a, b);
+}
+
+void EdgeMatrix::Connect(s32 a, s32 b) {
+ SetState(a, b, true);
+}
+
+void EdgeMatrix::Disconnect(s32 a, s32 b) {
+ SetState(a, b, false);
+}
+
+void EdgeMatrix::RemoveEdges(s32 edge) {
+ for (std::size_t i = 0; i < node_count; i++) {
+ SetState(edge, static_cast<s32>(i), false);
+ }
+}
+
+std::size_t EdgeMatrix::GetNodeCount() const {
+ return node_count;
+}
+
+void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
+ ASSERT(InRange(a, b));
+ edge_matrix.at(a * node_count + b) = state;
+}
+
+bool EdgeMatrix::GetState(s32 a, s32 b) {
+ ASSERT(InRange(a, b));
+ return edge_matrix.at(a * node_count + b);
+}
+
+bool EdgeMatrix::InRange(s32 a, s32 b) const {
+ const std::size_t pos = a * node_count + b;
+ return pos < (node_count * node_count);
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h
new file mode 100644
index 000000000..b490627f5
--- /dev/null
+++ b/src/audio_core/splitter_context.h
@@ -0,0 +1,219 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <stack>
+#include <vector>
+#include "audio_core/common.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace AudioCore {
+class BehaviorInfo;
+
+class EdgeMatrix {
+public:
+ EdgeMatrix();
+ ~EdgeMatrix();
+
+ void Initialize(std::size_t _node_count);
+ bool Connected(s32 a, s32 b);
+ void Connect(s32 a, s32 b);
+ void Disconnect(s32 a, s32 b);
+ void RemoveEdges(s32 edge);
+ std::size_t GetNodeCount() const;
+
+private:
+ void SetState(s32 a, s32 b, bool state);
+ bool GetState(s32 a, s32 b);
+
+ bool InRange(s32 a, s32 b) const;
+ std::vector<bool> edge_matrix{};
+ std::size_t node_count{};
+};
+
+class NodeStates {
+public:
+ enum class State {
+ NoState = 0,
+ InFound = 1,
+ InCompleted = 2,
+ };
+
+ // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
+ class Stack {
+ public:
+ Stack();
+ ~Stack();
+
+ void Reset(std::size_t size);
+ void push(s32 val);
+ std::size_t Count() const;
+ s32 top() const;
+ s32 pop();
+
+ private:
+ std::vector<s32> stack{};
+ std::size_t stack_size{};
+ std::size_t stack_pos{};
+ };
+ NodeStates();
+ ~NodeStates();
+
+ void Initialize(std::size_t node_count_);
+ bool Tsort(EdgeMatrix& edge_matrix);
+ std::size_t GetIndexPos() const;
+ const std::vector<s32>& GetIndexList() const;
+
+private:
+ void PushTsortResult(s32 index);
+ bool DepthFirstSearch(EdgeMatrix& edge_matrix);
+ void ResetState();
+ void UpdateState(State state, std::size_t i);
+ State GetState(std::size_t i);
+
+ std::size_t node_count{};
+ std::vector<bool> was_node_found{};
+ std::vector<bool> was_node_completed{};
+ std::size_t index_pos{};
+ std::vector<s32> index_list{};
+ Stack index_stack{};
+};
+
+enum class SplitterMagic : u32_le {
+ SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
+ DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
+ InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
+};
+
+class SplitterInfo {
+public:
+ struct InHeader {
+ SplitterMagic magic{};
+ s32_le info_count{};
+ s32_le data_count{};
+ INSERT_PADDING_WORDS(5);
+ };
+ static_assert(sizeof(InHeader) == 0x20, "SplitterInfo::InHeader is an invalid size");
+
+ struct InInfoPrams {
+ SplitterMagic magic{};
+ s32_le send_id{};
+ s32_le sample_rate{};
+ s32_le length{};
+ s32_le resource_id_base{};
+ };
+ static_assert(sizeof(InInfoPrams) == 0x14, "SplitterInfo::InInfoPrams is an invalid size");
+
+ struct InDestinationParams {
+ SplitterMagic magic{};
+ s32_le splitter_id{};
+ std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
+ s32_le mix_id{};
+ bool in_use{};
+ INSERT_PADDING_BYTES(3);
+ };
+ static_assert(sizeof(InDestinationParams) == 0x70,
+ "SplitterInfo::InDestinationParams is an invalid size");
+};
+
+class ServerSplitterDestinationData {
+public:
+ explicit ServerSplitterDestinationData(s32 id_);
+ ~ServerSplitterDestinationData();
+
+ void Update(SplitterInfo::InDestinationParams& header);
+
+ ServerSplitterDestinationData* GetNextDestination();
+ const ServerSplitterDestinationData* GetNextDestination() const;
+ void SetNextDestination(ServerSplitterDestinationData* dest);
+ bool ValidMixId() const;
+ s32 GetMixId() const;
+ bool IsConfigured() const;
+ float GetMixVolume(std::size_t i) const;
+ const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
+ const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
+ void MarkDirty();
+ void UpdateInternalState();
+
+private:
+ bool needs_update{};
+ bool in_use{};
+ s32 id{};
+ s32 mix_id{};
+ std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
+ std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
+ ServerSplitterDestinationData* next = nullptr;
+};
+
+class ServerSplitterInfo {
+public:
+ explicit ServerSplitterInfo(s32 id_);
+ ~ServerSplitterInfo();
+
+ void InitializeInfos();
+ void ClearNewConnectionFlag();
+ std::size_t Update(SplitterInfo::InInfoPrams& header);
+
+ ServerSplitterDestinationData* GetHead();
+ const ServerSplitterDestinationData* GetHead() const;
+ ServerSplitterDestinationData* GetData(std::size_t depth);
+ const ServerSplitterDestinationData* GetData(std::size_t depth) const;
+
+ bool HasNewConnection() const;
+ s32 GetLength() const;
+
+ void SetHead(ServerSplitterDestinationData* new_head);
+ void SetHeadDepth(s32 length);
+
+private:
+ s32 sample_rate{};
+ s32 id{};
+ s32 send_length{};
+ ServerSplitterDestinationData* head = nullptr;
+ bool new_connection{};
+};
+
+class SplitterContext {
+public:
+ SplitterContext();
+ ~SplitterContext();
+
+ void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
+ std::size_t data_count);
+
+ bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
+ bool UsingSplitter() const;
+
+ ServerSplitterInfo& GetInfo(std::size_t i);
+ const ServerSplitterInfo& GetInfo(std::size_t i) const;
+ ServerSplitterDestinationData& GetData(std::size_t i);
+ const ServerSplitterDestinationData& GetData(std::size_t i) const;
+ ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
+ const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
+ std::size_t data) const;
+ void UpdateInternalState();
+
+ std::size_t GetInfoCount() const;
+ std::size_t GetDataCount() const;
+
+private:
+ void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
+ bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read, s32 in_splitter_count);
+ bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
+ std::size_t& bytes_read, s32 in_data_count);
+ bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
+ const std::vector<u8>& input, const std::size_t& input_offset);
+
+ std::vector<ServerSplitterInfo> infos{};
+ std::vector<ServerSplitterDestinationData> datas{};
+
+ std::size_t info_count{};
+ std::size_t data_count{};
+ bool bug_fixed{};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 7be5d5087..b0f6f0c34 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -12,7 +12,6 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core_timing.h"
-#include "core/core_timing_util.h"
#include "core/settings.h"
namespace AudioCore {
@@ -32,10 +31,10 @@ u32 Stream::GetNumChannels() const {
return {};
}
-Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format format,
- ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_)
- : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
- sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} {
+Stream::Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_,
+ ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_)
+ : sample_rate{sample_rate_}, format{format_}, release_callback{std::move(release_callback_)},
+ sink_stream{sink_stream_}, core_timing{core_timing_}, name{std::move(name_)} {
release_event =
Core::Timing::CreateEvent(name, [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
ReleaseActiveBuffer(ns_late);
@@ -52,6 +51,14 @@ void Stream::Stop() {
UNIMPLEMENTED();
}
+bool Stream::Flush() {
+ const bool had_buffers = !queued_buffers.empty();
+ while (!queued_buffers.empty()) {
+ queued_buffers.pop();
+ }
+ return had_buffers;
+}
+
void Stream::SetVolume(float volume) {
game_volume = volume;
}
@@ -104,11 +111,14 @@ void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
- const auto time_stretch_delta = Settings::values.enable_audio_stretching.GetValue()
- ? std::chrono::nanoseconds::zero()
- : ns_late;
- const auto future_time = GetBufferReleaseNS(*active_buffer) - time_stretch_delta;
- core_timing.ScheduleEvent(future_time, release_event, {});
+ const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer);
+
+ // If ns_late is higher than the update rate ignore the delay
+ if (ns_late > buffer_release_ns) {
+ ns_late = {};
+ }
+
+ core_timing.ScheduleEvent(buffer_release_ns - ns_late, release_event, {});
}
void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {
@@ -127,7 +137,7 @@ bool Stream::QueueBuffer(BufferPtr&& buffer) {
return false;
}
-bool Stream::ContainsBuffer(Buffer::Tag tag) const {
+bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const {
UNIMPLEMENTED();
return {};
}
@@ -135,7 +145,25 @@ bool Stream::ContainsBuffer(Buffer::Tag tag) const {
std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) {
std::vector<Buffer::Tag> tags;
for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) {
- tags.push_back(released_buffers.front()->GetTag());
+ if (released_buffers.front()) {
+ tags.push_back(released_buffers.front()->GetTag());
+ } else {
+ ASSERT_MSG(false, "Invalid tag in released_buffers!");
+ }
+ released_buffers.pop();
+ }
+ return tags;
+}
+
+std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers() {
+ std::vector<Buffer::Tag> tags;
+ tags.reserve(released_buffers.size());
+ while (!released_buffers.empty()) {
+ if (released_buffers.front()) {
+ tags.push_back(released_buffers.front()->GetTag());
+ } else {
+ ASSERT_MSG(false, "Invalid tag in released_buffers!");
+ }
released_buffers.pop();
}
return tags;
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
index 6437b8591..559844b9b 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -44,8 +44,8 @@ public:
/// Callback function type, used to change guest state on a buffer being released
using ReleaseCallback = std::function<void()>;
- Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format format,
- ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_);
+ Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_,
+ ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_);
/// Plays the audio stream
void Play();
@@ -56,38 +56,44 @@ public:
/// Queues a buffer into the audio stream, returns true on success
bool QueueBuffer(BufferPtr&& buffer);
+ /// Flush audio buffers
+ bool Flush();
+
/// Returns true if the audio stream contains a buffer with the specified tag
- bool ContainsBuffer(Buffer::Tag tag) const;
+ [[nodiscard]] bool ContainsBuffer(Buffer::Tag tag) const;
/// Returns a vector of recently released buffers specified by tag
- std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count);
+ [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count);
+
+ /// Returns a vector of all recently released buffers specified by tag
+ [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers();
void SetVolume(float volume);
- float GetVolume() const {
+ [[nodiscard]] float GetVolume() const {
return game_volume;
}
/// Returns true if the stream is currently playing
- bool IsPlaying() const {
+ [[nodiscard]] bool IsPlaying() const {
return state == State::Playing;
}
/// Returns the number of queued buffers
- std::size_t GetQueueSize() const {
+ [[nodiscard]] std::size_t GetQueueSize() const {
return queued_buffers.size();
}
/// Gets the sample rate
- u32 GetSampleRate() const {
+ [[nodiscard]] u32 GetSampleRate() const {
return sample_rate;
}
/// Gets the number of channels
- u32 GetNumChannels() const;
+ [[nodiscard]] u32 GetNumChannels() const;
/// Get the state
- State GetState() const;
+ [[nodiscard]] State GetState() const;
private:
/// Plays the next queued buffer in the audio stream, starting playback if necessary
@@ -97,7 +103,7 @@ private:
void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {});
/// Gets the number of core cycles when the specified buffer will be released
- std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const;
+ [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const;
u32 sample_rate; ///< Sample rate of the stream
Format format; ///< Format of the stream
diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp
new file mode 100644
index 000000000..867b8fc6b
--- /dev/null
+++ b/src/audio_core/voice_context.cpp
@@ -0,0 +1,530 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/behavior_info.h"
+#include "audio_core/voice_context.h"
+#include "core/memory.h"
+
+namespace AudioCore {
+
+ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id_) : id(id_) {}
+ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
+
+bool ServerVoiceChannelResource::InUse() const {
+ return in_use;
+}
+
+float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
+ ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
+ return mix_volume.at(i);
+}
+
+float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
+ ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
+ return last_mix_volume.at(i);
+}
+
+void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
+ in_use = in_params.in_use;
+ // Update our mix volumes only if it's in use
+ if (in_params.in_use) {
+ mix_volume = in_params.mix_volume;
+ }
+}
+
+void ServerVoiceChannelResource::UpdateLastMixVolumes() {
+ last_mix_volume = mix_volume;
+}
+
+const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
+ServerVoiceChannelResource::GetCurrentMixVolume() const {
+ return mix_volume;
+}
+
+const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
+ServerVoiceChannelResource::GetLastMixVolume() const {
+ return last_mix_volume;
+}
+
+ServerVoiceInfo::ServerVoiceInfo() {
+ Initialize();
+}
+ServerVoiceInfo::~ServerVoiceInfo() = default;
+
+void ServerVoiceInfo::Initialize() {
+ in_params.in_use = false;
+ in_params.node_id = 0;
+ in_params.id = 0;
+ in_params.current_playstate = ServerPlayState::Stop;
+ in_params.priority = 255;
+ in_params.sample_rate = 0;
+ in_params.sample_format = SampleFormat::Invalid;
+ in_params.channel_count = 0;
+ in_params.pitch = 0.0f;
+ in_params.volume = 0.0f;
+ in_params.last_volume = 0.0f;
+ in_params.biquad_filter.fill({});
+ in_params.wave_buffer_count = 0;
+ in_params.wave_bufffer_head = 0;
+ in_params.mix_id = AudioCommon::NO_MIX;
+ in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
+ in_params.additional_params_address = 0;
+ in_params.additional_params_size = 0;
+ in_params.is_new = false;
+ out_params.played_sample_count = 0;
+ out_params.wave_buffer_consumed = 0;
+ in_params.voice_drop_flag = false;
+ in_params.buffer_mapped = false;
+ in_params.wave_buffer_flush_request_count = 0;
+ in_params.was_biquad_filter_enabled.fill(false);
+
+ for (auto& wave_buffer : in_params.wave_buffer) {
+ wave_buffer.start_sample_offset = 0;
+ wave_buffer.end_sample_offset = 0;
+ wave_buffer.is_looping = false;
+ wave_buffer.end_of_stream = false;
+ wave_buffer.buffer_address = 0;
+ wave_buffer.buffer_size = 0;
+ wave_buffer.context_address = 0;
+ wave_buffer.context_size = 0;
+ wave_buffer.sent_to_dsp = true;
+ }
+
+ stored_samples.clear();
+}
+
+void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
+ BehaviorInfo& behavior_info) {
+ in_params.in_use = voice_in.is_in_use;
+ in_params.id = voice_in.id;
+ in_params.node_id = voice_in.node_id;
+ in_params.last_playstate = in_params.current_playstate;
+ switch (voice_in.play_state) {
+ case PlayState::Paused:
+ in_params.current_playstate = ServerPlayState::Paused;
+ break;
+ case PlayState::Stopped:
+ if (in_params.current_playstate != ServerPlayState::Stop) {
+ in_params.current_playstate = ServerPlayState::RequestStop;
+ }
+ break;
+ case PlayState::Started:
+ in_params.current_playstate = ServerPlayState::Play;
+ break;
+ default:
+ UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
+ break;
+ }
+
+ in_params.priority = voice_in.priority;
+ in_params.sorting_order = voice_in.sorting_order;
+ in_params.sample_rate = voice_in.sample_rate;
+ in_params.sample_format = voice_in.sample_format;
+ in_params.channel_count = voice_in.channel_count;
+ in_params.pitch = voice_in.pitch;
+ in_params.volume = voice_in.volume;
+ in_params.biquad_filter = voice_in.biquad_filter;
+ in_params.wave_buffer_count = voice_in.wave_buffer_count;
+ in_params.wave_bufffer_head = voice_in.wave_buffer_head;
+ if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
+ const auto in_request_count = in_params.wave_buffer_flush_request_count;
+ const auto voice_request_count = voice_in.wave_buffer_flush_request_count;
+ in_params.wave_buffer_flush_request_count =
+ static_cast<u8>(in_request_count + voice_request_count);
+ }
+ in_params.mix_id = voice_in.mix_id;
+ if (behavior_info.IsSplitterSupported()) {
+ in_params.splitter_info_id = voice_in.splitter_info_id;
+ } else {
+ in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
+ }
+
+ std::memcpy(in_params.voice_channel_resource_id.data(),
+ voice_in.voice_channel_resource_ids.data(),
+ sizeof(s32) * in_params.voice_channel_resource_id.size());
+
+ if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
+ in_params.behavior_flags.is_played_samples_reset_at_loop_point =
+ voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
+ } else {
+ in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
+ }
+ if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
+ in_params.behavior_flags.is_pitch_and_src_skipped =
+ voice_in.behavior_flags.is_pitch_and_src_skipped;
+ } else {
+ in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
+ }
+
+ if (voice_in.is_voice_drop_flag_clear_requested) {
+ in_params.voice_drop_flag = false;
+ }
+
+ if (in_params.additional_params_address != voice_in.additional_params_address ||
+ in_params.additional_params_size != voice_in.additional_params_size) {
+ in_params.additional_params_address = voice_in.additional_params_address;
+ in_params.additional_params_size = voice_in.additional_params_size;
+ // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
+ // our context is new
+ }
+}
+
+void ServerVoiceInfo::UpdateWaveBuffers(
+ const VoiceInfo::InParams& voice_in,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
+ BehaviorInfo& behavior_info) {
+ if (voice_in.is_new) {
+ // Initialize our wave buffers
+ for (auto& wave_buffer : in_params.wave_buffer) {
+ wave_buffer.start_sample_offset = 0;
+ wave_buffer.end_sample_offset = 0;
+ wave_buffer.is_looping = false;
+ wave_buffer.end_of_stream = false;
+ wave_buffer.buffer_address = 0;
+ wave_buffer.buffer_size = 0;
+ wave_buffer.context_address = 0;
+ wave_buffer.context_size = 0;
+ wave_buffer.sent_to_dsp = true;
+ }
+
+ // Mark all our wave buffers as invalid
+ for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
+ channel++) {
+ for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
+ is_valid = false;
+ }
+ }
+ }
+
+ // Update our wave buffers
+ for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
+ // Assume that we have at least 1 channel voice state
+ const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
+
+ UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
+ have_valid_wave_buffer, behavior_info);
+ }
+}
+
+void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
+ const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
+ bool is_buffer_valid,
+ [[maybe_unused]] BehaviorInfo& behavior_info) {
+ if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
+ out_wavebuffer.buffer_address = 0;
+ out_wavebuffer.buffer_size = 0;
+ }
+
+ if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
+ // Validate sample offset sizings
+ if (sample_format == SampleFormat::Pcm16) {
+ const auto buffer_size = in_wave_buffer.buffer_size;
+ if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
+ (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
+ (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
+ // TODO(ogniK): Write error info
+ return;
+ }
+ }
+ // TODO(ogniK): ADPCM Size error
+
+ out_wavebuffer.sent_to_dsp = false;
+ out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
+ out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
+ out_wavebuffer.is_looping = in_wave_buffer.is_looping;
+ out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
+
+ out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
+ out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
+ out_wavebuffer.context_address = in_wave_buffer.context_address;
+ out_wavebuffer.context_size = in_wave_buffer.context_size;
+ in_params.buffer_mapped =
+ in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
+ // TODO(ogniK): Pool mapper attachment
+ // TODO(ogniK): IsAdpcmLoopContextBugFixed
+ }
+}
+
+void ServerVoiceInfo::WriteOutStatus(
+ VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
+ if (voice_in.is_new) {
+ in_params.is_new = true;
+ voice_out.wave_buffer_consumed = 0;
+ voice_out.played_sample_count = 0;
+ voice_out.voice_dropped = false;
+ } else if (!in_params.is_new) {
+ voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
+ voice_out.played_sample_count = voice_states[0]->played_sample_count;
+ voice_out.voice_dropped = in_params.voice_drop_flag;
+ } else {
+ voice_out.wave_buffer_consumed = 0;
+ voice_out.played_sample_count = 0;
+ voice_out.voice_dropped = false;
+ }
+}
+
+const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
+ return in_params;
+}
+
+ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
+ return in_params;
+}
+
+const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
+ return out_params;
+}
+
+ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
+ return out_params;
+}
+
+bool ServerVoiceInfo::ShouldSkip() const {
+ // TODO(ogniK): Handle unmapped wave buffers or parameters
+ return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
+}
+
+bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
+ if (in_params.is_new) {
+ ResetResources(voice_context);
+ in_params.last_volume = in_params.volume;
+ in_params.is_new = false;
+ }
+
+ const s32 channel_count = in_params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ const auto channel_resource = in_params.voice_channel_resource_id[i];
+ dsp_voice_states[i] =
+ &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
+ }
+ return UpdateParametersForCommandGeneration(dsp_voice_states);
+}
+
+void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
+ const s32 channel_count = in_params.channel_count;
+ for (s32 i = 0; i < channel_count; i++) {
+ const auto channel_resource = in_params.voice_channel_resource_id[i];
+ auto& dsp_state =
+ voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
+ dsp_state = {};
+ voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
+ .UpdateLastMixVolumes();
+ }
+}
+
+bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
+ const s32 channel_count = in_params.channel_count;
+ if (in_params.wave_buffer_flush_request_count > 0) {
+ FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
+ channel_count);
+ in_params.wave_buffer_flush_request_count = 0;
+ }
+
+ switch (in_params.current_playstate) {
+ case ServerPlayState::Play: {
+ for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
+ if (!in_params.wave_buffer[i].sent_to_dsp) {
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
+ }
+ in_params.wave_buffer[i].sent_to_dsp = true;
+ }
+ }
+ in_params.should_depop = false;
+ return HasValidWaveBuffer(dsp_voice_states[0]);
+ }
+ case ServerPlayState::Paused:
+ case ServerPlayState::Stop: {
+ in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
+ return in_params.should_depop;
+ }
+ case ServerPlayState::RequestStop: {
+ for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
+ in_params.wave_buffer[i].sent_to_dsp = true;
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ auto* dsp_state = dsp_voice_states[channel];
+
+ if (dsp_state->is_wave_buffer_valid[i]) {
+ dsp_state->wave_buffer_index =
+ (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ dsp_state->wave_buffer_consumed++;
+ }
+
+ dsp_state->is_wave_buffer_valid[i] = false;
+ }
+ }
+
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ auto* dsp_state = dsp_voice_states[channel];
+ dsp_state->offset = 0;
+ dsp_state->played_sample_count = 0;
+ dsp_state->fraction = 0;
+ dsp_state->sample_history.fill(0);
+ dsp_state->context = {};
+ }
+
+ in_params.current_playstate = ServerPlayState::Stop;
+ in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
+ return in_params.should_depop;
+ }
+ default:
+ UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
+ }
+
+ return false;
+}
+
+void ServerVoiceInfo::FlushWaveBuffers(
+ u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
+ s32 channel_count) {
+ auto wave_head = in_params.wave_bufffer_head;
+
+ for (u8 i = 0; i < flush_count; i++) {
+ in_params.wave_buffer[wave_head].sent_to_dsp = true;
+ for (s32 channel = 0; channel < channel_count; channel++) {
+ auto* dsp_state = dsp_voice_states[channel];
+ dsp_state->wave_buffer_consumed++;
+ dsp_state->is_wave_buffer_valid[wave_head] = false;
+ dsp_state->wave_buffer_index =
+ (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ }
+ wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
+ }
+}
+
+bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
+ const auto& valid_wb = state->is_wave_buffer_valid;
+ return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
+}
+
+VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} {
+ for (std::size_t i = 0; i < voice_count; i++) {
+ voice_channel_resources.emplace_back(static_cast<s32>(i));
+ sorted_voice_info.push_back(&voice_info.emplace_back());
+ voice_states.emplace_back();
+ dsp_voice_states.emplace_back();
+ }
+}
+
+VoiceContext::~VoiceContext() {
+ sorted_voice_info.clear();
+}
+
+std::size_t VoiceContext::GetVoiceCount() const {
+ return voice_count;
+}
+
+ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
+ ASSERT(i < voice_count);
+ return voice_channel_resources.at(i);
+}
+
+const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return voice_channel_resources.at(i);
+}
+
+VoiceState& VoiceContext::GetState(std::size_t i) {
+ ASSERT(i < voice_count);
+ return voice_states.at(i);
+}
+
+const VoiceState& VoiceContext::GetState(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return voice_states.at(i);
+}
+
+VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
+ ASSERT(i < voice_count);
+ return dsp_voice_states.at(i);
+}
+
+const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return dsp_voice_states.at(i);
+}
+
+ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
+ ASSERT(i < voice_count);
+ return voice_info.at(i);
+}
+
+const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return voice_info.at(i);
+}
+
+ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
+ ASSERT(i < voice_count);
+ return *sorted_voice_info.at(i);
+}
+
+const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
+ ASSERT(i < voice_count);
+ return *sorted_voice_info.at(i);
+}
+
+s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
+ s32 channel_count, s32 buffer_offset, s32 sample_count,
+ Core::Memory::Memory& memory) {
+ if (wave_buffer->buffer_address == 0) {
+ return 0;
+ }
+ if (wave_buffer->buffer_size == 0) {
+ return 0;
+ }
+ if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
+ return 0;
+ }
+
+ const auto samples_remaining =
+ (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
+ const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
+ const auto buffer_pos = wave_buffer->buffer_address + start_offset;
+
+ s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
+
+ const auto samples_processed = std::min(sample_count, samples_remaining);
+
+ // Fast path
+ if (channel_count == 1) {
+ for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
+ output_buffer[i] = buffer_data[i];
+ }
+ } else {
+ for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
+ output_buffer[i] = buffer_data[i * channel_count + channel];
+ }
+ }
+
+ return samples_processed;
+}
+
+void VoiceContext::SortInfo() {
+ for (std::size_t i = 0; i < voice_count; i++) {
+ sorted_voice_info[i] = &voice_info[i];
+ }
+
+ std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
+ [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
+ const auto& lhs_in = lhs->GetInParams();
+ const auto& rhs_in = rhs->GetInParams();
+ // Sort by priority
+ if (lhs_in.priority != rhs_in.priority) {
+ return lhs_in.priority > rhs_in.priority;
+ } else {
+ // If the priorities match, sort by sorting order
+ return lhs_in.sorting_order > rhs_in.sorting_order;
+ }
+ });
+}
+
+void VoiceContext::UpdateStateByDspShared() {
+ voice_states = dsp_voice_states;
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h
new file mode 100644
index 000000000..70359cadb
--- /dev/null
+++ b/src/audio_core/voice_context.h
@@ -0,0 +1,296 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "audio_core/algorithm/interpolate.h"
+#include "audio_core/codec.h"
+#include "audio_core/common.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace AudioCore {
+
+class BehaviorInfo;
+class VoiceContext;
+
+enum class SampleFormat : u8 {
+ Invalid = 0,
+ Pcm8 = 1,
+ Pcm16 = 2,
+ Pcm24 = 3,
+ Pcm32 = 4,
+ PcmFloat = 5,
+ Adpcm = 6,
+};
+
+enum class PlayState : u8 {
+ Started = 0,
+ Stopped = 1,
+ Paused = 2,
+};
+
+enum class ServerPlayState {
+ Play = 0,
+ Stop = 1,
+ RequestStop = 2,
+ Paused = 3,
+};
+
+struct BiquadFilterParameter {
+ bool enabled{};
+ INSERT_PADDING_BYTES(1);
+ std::array<s16, 3> numerator{};
+ std::array<s16, 2> denominator{};
+};
+static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
+
+struct WaveBuffer {
+ u64_le buffer_address{};
+ u64_le buffer_size{};
+ s32_le start_sample_offset{};
+ s32_le end_sample_offset{};
+ u8 is_looping{};
+ u8 end_of_stream{};
+ u8 sent_to_server{};
+ INSERT_PADDING_BYTES(5);
+ u64 context_address{};
+ u64 context_size{};
+ INSERT_PADDING_BYTES(8);
+};
+static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
+
+struct ServerWaveBuffer {
+ VAddr buffer_address{};
+ std::size_t buffer_size{};
+ s32 start_sample_offset{};
+ s32 end_sample_offset{};
+ bool is_looping{};
+ bool end_of_stream{};
+ VAddr context_address{};
+ std::size_t context_size{};
+ bool sent_to_dsp{true};
+};
+
+struct BehaviorFlags {
+ BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
+ BitField<1, 1, u16> is_pitch_and_src_skipped;
+};
+static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
+
+struct ADPCMContext {
+ u16 header;
+ s16 yn1;
+ s16 yn2;
+};
+static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size");
+
+struct VoiceState {
+ s64 played_sample_count;
+ s32 offset;
+ s32 wave_buffer_index;
+ std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid;
+ s32 wave_buffer_consumed;
+ std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history;
+ s32 fraction;
+ VAddr context_address;
+ Codec::ADPCM_Coeff coeff;
+ ADPCMContext context;
+ std::array<s64, 2> biquad_filter_state;
+ std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples;
+ u32 external_context_size;
+ bool is_external_context_used;
+ bool voice_dropped;
+};
+
+class VoiceChannelResource {
+public:
+ struct InParams {
+ s32_le id{};
+ std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
+ bool in_use{};
+ INSERT_PADDING_BYTES(11);
+ };
+ static_assert(sizeof(InParams) == 0x70, "InParams is an invalid size");
+};
+
+class ServerVoiceChannelResource {
+public:
+ explicit ServerVoiceChannelResource(s32 id_);
+ ~ServerVoiceChannelResource();
+
+ bool InUse() const;
+ float GetCurrentMixVolumeAt(std::size_t i) const;
+ float GetLastMixVolumeAt(std::size_t i) const;
+ void Update(VoiceChannelResource::InParams& in_params);
+ void UpdateLastMixVolumes();
+
+ const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
+ const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
+
+private:
+ s32 id{};
+ std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
+ std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
+ bool in_use{};
+};
+
+class VoiceInfo {
+public:
+ struct InParams {
+ s32_le id{};
+ u32_le node_id{};
+ u8 is_new{};
+ u8 is_in_use{};
+ PlayState play_state{};
+ SampleFormat sample_format{};
+ s32_le sample_rate{};
+ s32_le priority{};
+ s32_le sorting_order{};
+ s32_le channel_count{};
+ float_le pitch{};
+ float_le volume{};
+ std::array<BiquadFilterParameter, 2> biquad_filter{};
+ s32_le wave_buffer_count{};
+ s16_le wave_buffer_head{};
+ INSERT_PADDING_BYTES(6);
+ u64_le additional_params_address{};
+ u64_le additional_params_size{};
+ s32_le mix_id{};
+ s32_le splitter_info_id{};
+ std::array<WaveBuffer, 4> wave_buffer{};
+ std::array<u32_le, 6> voice_channel_resource_ids{};
+ // TODO(ogniK): Remaining flags
+ u8 is_voice_drop_flag_clear_requested{};
+ u8 wave_buffer_flush_request_count{};
+ INSERT_PADDING_BYTES(2);
+ BehaviorFlags behavior_flags{};
+ INSERT_PADDING_BYTES(16);
+ };
+ static_assert(sizeof(InParams) == 0x170, "InParams is an invalid size");
+
+ struct OutParams {
+ u64_le played_sample_count{};
+ u32_le wave_buffer_consumed{};
+ u8 voice_dropped{};
+ INSERT_PADDING_BYTES(3);
+ };
+ static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
+};
+
+class ServerVoiceInfo {
+public:
+ struct InParams {
+ bool in_use{};
+ bool is_new{};
+ bool should_depop{};
+ SampleFormat sample_format{};
+ s32 sample_rate{};
+ s32 channel_count{};
+ s32 id{};
+ s32 node_id{};
+ s32 mix_id{};
+ ServerPlayState current_playstate{};
+ ServerPlayState last_playstate{};
+ s32 priority{};
+ s32 sorting_order{};
+ float pitch{};
+ float volume{};
+ float last_volume{};
+ std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
+ s32 wave_buffer_count{};
+ s16 wave_bufffer_head{};
+ INSERT_PADDING_BYTES(2);
+ BehaviorFlags behavior_flags{};
+ VAddr additional_params_address{};
+ std::size_t additional_params_size{};
+ std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
+ std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
+ s32 splitter_info_id{};
+ u8 wave_buffer_flush_request_count{};
+ bool voice_drop_flag{};
+ bool buffer_mapped{};
+ std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
+ };
+
+ struct OutParams {
+ s64 played_sample_count{};
+ s32 wave_buffer_consumed{};
+ };
+
+ ServerVoiceInfo();
+ ~ServerVoiceInfo();
+ void Initialize();
+ void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
+ void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
+ BehaviorInfo& behavior_info);
+ void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
+ SampleFormat sample_format, bool is_buffer_valid,
+ BehaviorInfo& behavior_info);
+ void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
+
+ const InParams& GetInParams() const;
+ InParams& GetInParams();
+
+ const OutParams& GetOutParams() const;
+ OutParams& GetOutParams();
+
+ bool ShouldSkip() const;
+ bool UpdateForCommandGeneration(VoiceContext& voice_context);
+ void ResetResources(VoiceContext& voice_context);
+ bool UpdateParametersForCommandGeneration(
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
+ void FlushWaveBuffers(u8 flush_count,
+ std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
+ s32 channel_count);
+
+private:
+ std::vector<s16> stored_samples;
+ InParams in_params{};
+ OutParams out_params{};
+
+ bool HasValidWaveBuffer(const VoiceState* state) const;
+};
+
+class VoiceContext {
+public:
+ explicit VoiceContext(std::size_t voice_count_);
+ ~VoiceContext();
+
+ std::size_t GetVoiceCount() const;
+ ServerVoiceChannelResource& GetChannelResource(std::size_t i);
+ const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
+ VoiceState& GetState(std::size_t i);
+ const VoiceState& GetState(std::size_t i) const;
+ VoiceState& GetDspSharedState(std::size_t i);
+ const VoiceState& GetDspSharedState(std::size_t i) const;
+ ServerVoiceInfo& GetInfo(std::size_t i);
+ const ServerVoiceInfo& GetInfo(std::size_t i) const;
+ ServerVoiceInfo& GetSortedInfo(std::size_t i);
+ const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
+
+ s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
+ s32 channel_count, s32 buffer_offset, s32 sample_count,
+ Core::Memory::Memory& memory);
+ void SortInfo();
+ void UpdateStateByDspShared();
+
+private:
+ std::size_t voice_count{};
+ std::vector<ServerVoiceChannelResource> voice_channel_resources{};
+ std::vector<VoiceState> voice_states{};
+ std::vector<VoiceState> dsp_voice_states{};
+ std::vector<ServerVoiceInfo> voice_info{};
+ std::vector<ServerVoiceInfo*> sorted_voice_info{};
+};
+
+} // namespace AudioCore
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 5d54516eb..263c457cd 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -98,19 +98,20 @@ add_library(common STATIC
algorithm.h
alignment.h
assert.h
- atomic_ops.cpp
atomic_ops.h
detached_tasks.cpp
detached_tasks.h
+ bit_cast.h
bit_field.h
+ bit_set.h
bit_util.h
cityhash.cpp
cityhash.h
- color.h
common_funcs.h
common_paths.h
common_types.h
concepts.h
+ div_ceil.h
dynamic_library.cpp
dynamic_library.h
fiber.cpp
@@ -120,6 +121,7 @@ add_library(common STATIC
hash.h
hex_util.cpp
hex_util.h
+ intrusive_red_black_tree.h
logging/backend.cpp
logging/backend.h
logging/filter.cpp
@@ -132,17 +134,17 @@ add_library(common STATIC
math_util.h
memory_detect.cpp
memory_detect.h
- memory_hook.cpp
- memory_hook.h
microprofile.cpp
microprofile.h
microprofileui.h
misc.cpp
- multi_level_queue.h
+ nvidia_flags.cpp
+ nvidia_flags.h
page_table.cpp
page_table.h
param_package.cpp
param_package.h
+ parent_of_member.h
quaternion.h
ring_buffer.h
scm_rev.cpp
@@ -150,6 +152,8 @@ add_library(common STATIC
scope_exit.h
spin_lock.cpp
spin_lock.h
+ stream.cpp
+ stream.h
string_util.cpp
string_util.h
swap.h
@@ -158,11 +162,12 @@ add_library(common STATIC
thread.cpp
thread.h
thread_queue_list.h
+ thread_worker.cpp
+ thread_worker.h
threadsafe_queue.h
time_zone.cpp
time_zone.h
- timer.cpp
- timer.h
+ tree.h
uint128.cpp
uint128.h
uuid.cpp
@@ -188,8 +193,30 @@ if(ARCHITECTURE_x86_64)
)
endif()
+if (MSVC)
+ target_compile_definitions(common PRIVATE
+ # The standard library doesn't provide any replacement for codecvt yet
+ # so we can disable this deprecation warning for the time being.
+ _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
+ )
+ target_compile_options(common PRIVATE
+ /W4
+ /WX
+ )
+else()
+ target_compile_options(common PRIVATE
+ -Werror
+
+ $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
+ )
+endif()
+
create_target_directory_groups(common)
-find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
-target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak)
+target_link_libraries(common PRIVATE lz4::lz4 xbyak)
+if (MSVC)
+ target_link_libraries(common PRIVATE zstd::zstd)
+else()
+ target_link_libraries(common PRIVATE zstd)
+endif()
diff --git a/src/common/alignment.h b/src/common/alignment.h
index 5040043de..fb81f10d8 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -9,50 +9,45 @@
namespace Common {
template <typename T>
-[[nodiscard]] constexpr T AlignUp(T value, std::size_t size) {
- static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
+requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignUp(T value, size_t size) {
auto mod{static_cast<T>(value % size)};
value -= mod;
return static_cast<T>(mod == T{0} ? value : value + size);
}
template <typename T>
-[[nodiscard]] constexpr T AlignDown(T value, std::size_t size) {
- static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
- return static_cast<T>(value - value % size);
+requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignUpLog2(T value, size_t align_log2) {
+ return static_cast<T>((value + ((1ULL << align_log2) - 1)) >> align_log2 << align_log2);
}
template <typename T>
-[[nodiscard]] constexpr T AlignBits(T value, std::size_t align) {
- static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
- return static_cast<T>((value + ((1ULL << align) - 1)) >> align << align);
+requires std::is_unsigned_v<T>[[nodiscard]] constexpr T AlignDown(T value, size_t size) {
+ return static_cast<T>(value - value % size);
}
template <typename T>
-[[nodiscard]] constexpr bool Is4KBAligned(T value) {
- static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
+requires std::is_unsigned_v<T>[[nodiscard]] constexpr bool Is4KBAligned(T value) {
return (value & 0xFFF) == 0;
}
template <typename T>
-[[nodiscard]] constexpr bool IsWordAligned(T value) {
- static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
+requires std::is_unsigned_v<T>[[nodiscard]] constexpr bool IsWordAligned(T value) {
return (value & 0b11) == 0;
}
template <typename T>
-[[nodiscard]] constexpr bool IsAligned(T value, std::size_t alignment) {
- using U = typename std::make_unsigned<T>::type;
+requires std::is_integral_v<T>[[nodiscard]] constexpr bool IsAligned(T value, size_t alignment) {
+ using U = typename std::make_unsigned_t<T>;
const U mask = static_cast<U>(alignment - 1);
return (value & mask) == 0;
}
-template <typename T, std::size_t Align = 16>
+template <typename T, size_t Align = 16>
class AlignmentAllocator {
public:
using value_type = T;
- using size_type = std::size_t;
- using difference_type = std::ptrdiff_t;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
diff --git a/src/common/atomic_ops.cpp b/src/common/atomic_ops.cpp
deleted file mode 100644
index 1612d0e67..000000000
--- a/src/common/atomic_ops.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cstring>
-
-#include "common/atomic_ops.h"
-
-#if _MSC_VER
-#include <intrin.h>
-#endif
-
-namespace Common {
-
-#if _MSC_VER
-
-bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
- const u8 result =
- _InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected);
- return result == expected;
-}
-
-bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
- const u16 result =
- _InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected);
- return result == expected;
-}
-
-bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
- const u32 result =
- _InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected);
- return result == expected;
-}
-
-bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
- const u64 result = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer),
- value, expected);
- return result == expected;
-}
-
-bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
- return _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1],
- value[0],
- reinterpret_cast<__int64*>(expected.data())) != 0;
-}
-
-#else
-
-bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
- return __sync_bool_compare_and_swap(pointer, expected, value);
-}
-
-bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
- return __sync_bool_compare_and_swap(pointer, expected, value);
-}
-
-bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
- return __sync_bool_compare_and_swap(pointer, expected, value);
-}
-
-bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
- return __sync_bool_compare_and_swap(pointer, expected, value);
-}
-
-bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
- unsigned __int128 value_a;
- unsigned __int128 expected_a;
- std::memcpy(&value_a, value.data(), sizeof(u128));
- std::memcpy(&expected_a, expected.data(), sizeof(u128));
- return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
-}
-
-#endif
-
-} // namespace Common
diff --git a/src/common/atomic_ops.h b/src/common/atomic_ops.h
index b46888589..2b1f515e8 100644
--- a/src/common/atomic_ops.h
+++ b/src/common/atomic_ops.h
@@ -4,14 +4,75 @@
#pragma once
+#include <cstring>
+#include <memory>
+
#include "common/common_types.h"
+#if _MSC_VER
+#include <intrin.h>
+#endif
+
namespace Common {
-[[nodiscard]] bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected);
-[[nodiscard]] bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected);
-[[nodiscard]] bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected);
-[[nodiscard]] bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected);
-[[nodiscard]] bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected);
+#if _MSC_VER
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
+ const u8 result =
+ _InterlockedCompareExchange8(reinterpret_cast<volatile char*>(pointer), value, expected);
+ return result == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
+ const u16 result =
+ _InterlockedCompareExchange16(reinterpret_cast<volatile short*>(pointer), value, expected);
+ return result == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
+ const u32 result =
+ _InterlockedCompareExchange(reinterpret_cast<volatile long*>(pointer), value, expected);
+ return result == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
+ const u64 result = _InterlockedCompareExchange64(reinterpret_cast<volatile __int64*>(pointer),
+ value, expected);
+ return result == expected;
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
+ return _InterlockedCompareExchange128(reinterpret_cast<volatile __int64*>(pointer), value[1],
+ value[0],
+ reinterpret_cast<__int64*>(expected.data())) != 0;
+}
+
+#else
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u8* pointer, u8 value, u8 expected) {
+ return __sync_bool_compare_and_swap(pointer, expected, value);
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u16* pointer, u16 value, u16 expected) {
+ return __sync_bool_compare_and_swap(pointer, expected, value);
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u32* pointer, u32 value, u32 expected) {
+ return __sync_bool_compare_and_swap(pointer, expected, value);
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u64 value, u64 expected) {
+ return __sync_bool_compare_and_swap(pointer, expected, value);
+}
+
+[[nodiscard]] inline bool AtomicCompareAndSwap(volatile u64* pointer, u128 value, u128 expected) {
+ unsigned __int128 value_a;
+ unsigned __int128 expected_a;
+ std::memcpy(&value_a, value.data(), sizeof(u128));
+ std::memcpy(&expected_a, expected.data(), sizeof(u128));
+ return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
+}
+
+#endif
} // namespace Common
diff --git a/src/common/bit_cast.h b/src/common/bit_cast.h
new file mode 100644
index 000000000..a32a063d1
--- /dev/null
+++ b/src/common/bit_cast.h
@@ -0,0 +1,22 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstring>
+#include <type_traits>
+
+namespace Common {
+
+template <typename To, typename From>
+[[nodiscard]] std::enable_if_t<sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From> &&
+ std::is_trivially_copyable_v<To>,
+ To>
+BitCast(const From& src) noexcept {
+ To dst;
+ std::memcpy(&dst, &src, sizeof(To));
+ return dst;
+}
+
+} // namespace Common
diff --git a/src/common/bit_set.h b/src/common/bit_set.h
new file mode 100644
index 000000000..9235ad412
--- /dev/null
+++ b/src/common/bit_set.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2018-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <array>
+#include <bit>
+
+#include "common/alignment.h"
+#include "common/bit_util.h"
+#include "common/common_types.h"
+
+namespace Common {
+
+namespace impl {
+
+template <typename Storage, size_t N>
+class BitSet {
+
+public:
+ constexpr BitSet() = default;
+
+ constexpr void SetBit(size_t i) {
+ this->words[i / FlagsPerWord] |= GetBitMask(i % FlagsPerWord);
+ }
+
+ constexpr void ClearBit(size_t i) {
+ this->words[i / FlagsPerWord] &= ~GetBitMask(i % FlagsPerWord);
+ }
+
+ constexpr size_t CountLeadingZero() const {
+ for (size_t i = 0; i < NumWords; i++) {
+ if (this->words[i]) {
+ return FlagsPerWord * i + CountLeadingZeroImpl(this->words[i]);
+ }
+ }
+ return FlagsPerWord * NumWords;
+ }
+
+ constexpr size_t GetNextSet(size_t n) const {
+ for (size_t i = (n + 1) / FlagsPerWord; i < NumWords; i++) {
+ Storage word = this->words[i];
+ if (!IsAligned(n + 1, FlagsPerWord)) {
+ word &= GetBitMask(n % FlagsPerWord) - 1;
+ }
+ if (word) {
+ return FlagsPerWord * i + CountLeadingZeroImpl(word);
+ }
+ }
+ return FlagsPerWord * NumWords;
+ }
+
+private:
+ static_assert(std::is_unsigned_v<Storage>);
+ static_assert(sizeof(Storage) <= sizeof(u64));
+
+ static constexpr size_t FlagsPerWord = BitSize<Storage>();
+ static constexpr size_t NumWords = AlignUp(N, FlagsPerWord) / FlagsPerWord;
+
+ static constexpr auto CountLeadingZeroImpl(Storage word) {
+ return std::countl_zero(static_cast<unsigned long long>(word)) -
+ (BitSize<unsigned long long>() - FlagsPerWord);
+ }
+
+ static constexpr Storage GetBitMask(size_t bit) {
+ return Storage(1) << (FlagsPerWord - 1 - bit);
+ }
+
+ std::array<Storage, NumWords> words{};
+};
+
+} // namespace impl
+
+template <size_t N>
+using BitSet8 = impl::BitSet<u8, N>;
+
+template <size_t N>
+using BitSet16 = impl::BitSet<u16, N>;
+
+template <size_t N>
+using BitSet32 = impl::BitSet<u32, N>;
+
+template <size_t N>
+using BitSet64 = impl::BitSet<u64, N>;
+
+} // namespace Common
diff --git a/src/common/bit_util.h b/src/common/bit_util.h
index 29f59a9a3..64520ca4e 100644
--- a/src/common/bit_util.h
+++ b/src/common/bit_util.h
@@ -4,13 +4,10 @@
#pragma once
+#include <bit>
#include <climits>
#include <cstddef>
-#ifdef _MSC_VER
-#include <intrin.h>
-#endif
-
#include "common/common_types.h"
namespace Common {
@@ -21,124 +18,30 @@ template <typename T>
return sizeof(T) * CHAR_BIT;
}
-#ifdef _MSC_VER
-[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) {
- unsigned long leading_zero = 0;
-
- if (_BitScanReverse(&leading_zero, value) != 0) {
- return 31 - leading_zero;
- }
-
- return 32;
-}
-
-[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) {
- unsigned long leading_zero = 0;
-
- if (_BitScanReverse64(&leading_zero, value) != 0) {
- return 63 - leading_zero;
- }
-
- return 64;
-}
-#else
-[[nodiscard]] inline u32 CountLeadingZeroes32(u32 value) {
- if (value == 0) {
- return 32;
- }
-
- return static_cast<u32>(__builtin_clz(value));
-}
-
-[[nodiscard]] inline u32 CountLeadingZeroes64(u64 value) {
- if (value == 0) {
- return 64;
- }
-
- return static_cast<u32>(__builtin_clzll(value));
-}
-#endif
-
-#ifdef _MSC_VER
-[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) {
- unsigned long trailing_zero = 0;
-
- if (_BitScanForward(&trailing_zero, value) != 0) {
- return trailing_zero;
- }
-
- return 32;
-}
-
-[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) {
- unsigned long trailing_zero = 0;
-
- if (_BitScanForward64(&trailing_zero, value) != 0) {
- return trailing_zero;
- }
-
- return 64;
-}
-#else
-[[nodiscard]] inline u32 CountTrailingZeroes32(u32 value) {
- if (value == 0) {
- return 32;
- }
-
- return static_cast<u32>(__builtin_ctz(value));
-}
-
-[[nodiscard]] inline u32 CountTrailingZeroes64(u64 value) {
- if (value == 0) {
- return 64;
- }
-
- return static_cast<u32>(__builtin_ctzll(value));
+[[nodiscard]] constexpr u32 MostSignificantBit32(const u32 value) {
+ return 31U - static_cast<u32>(std::countl_zero(value));
}
-#endif
-
-#ifdef _MSC_VER
-[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) {
- unsigned long result;
- _BitScanReverse(&result, value);
- return static_cast<u32>(result);
+[[nodiscard]] constexpr u32 MostSignificantBit64(const u64 value) {
+ return 63U - static_cast<u32>(std::countl_zero(value));
}
-[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) {
- unsigned long result;
- _BitScanReverse64(&result, value);
- return static_cast<u32>(result);
-}
-
-#else
-
-[[nodiscard]] inline u32 MostSignificantBit32(const u32 value) {
- return 31U - static_cast<u32>(__builtin_clz(value));
-}
-
-[[nodiscard]] inline u32 MostSignificantBit64(const u64 value) {
- return 63U - static_cast<u32>(__builtin_clzll(value));
-}
-
-#endif
-
-[[nodiscard]] inline u32 Log2Floor32(const u32 value) {
+[[nodiscard]] constexpr u32 Log2Floor32(const u32 value) {
return MostSignificantBit32(value);
}
-[[nodiscard]] inline u32 Log2Ceil32(const u32 value) {
- const u32 log2_f = Log2Floor32(value);
- return log2_f + ((value ^ (1U << log2_f)) != 0U);
+[[nodiscard]] constexpr u32 Log2Floor64(const u64 value) {
+ return MostSignificantBit64(value);
}
-[[nodiscard]] inline u32 Log2Floor64(const u64 value) {
- return MostSignificantBit64(value);
+[[nodiscard]] constexpr u32 Log2Ceil32(const u32 value) {
+ const u32 log2_f = Log2Floor32(value);
+ return log2_f + static_cast<u32>((value ^ (1U << log2_f)) != 0U);
}
-[[nodiscard]] inline u32 Log2Ceil64(const u64 value) {
- const u64 log2_f = static_cast<u64>(Log2Floor64(value));
- return static_cast<u32>(log2_f + ((value ^ (1ULL << log2_f)) != 0ULL));
+[[nodiscard]] constexpr u32 Log2Ceil64(const u64 value) {
+ const u64 log2_f = Log2Floor64(value);
+ return static_cast<u32>(log2_f + static_cast<u64>((value ^ (1ULL << log2_f)) != 0ULL));
}
} // namespace Common
diff --git a/src/common/color.h b/src/common/color.h
deleted file mode 100644
index bbcac858e..000000000
--- a/src/common/color.h
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstring>
-
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "common/vector_math.h"
-
-namespace Common::Color {
-
-/// Convert a 1-bit color component to 8 bit
-[[nodiscard]] constexpr u8 Convert1To8(u8 value) {
- return value * 255;
-}
-
-/// Convert a 4-bit color component to 8 bit
-[[nodiscard]] constexpr u8 Convert4To8(u8 value) {
- return (value << 4) | value;
-}
-
-/// Convert a 5-bit color component to 8 bit
-[[nodiscard]] constexpr u8 Convert5To8(u8 value) {
- return (value << 3) | (value >> 2);
-}
-
-/// Convert a 6-bit color component to 8 bit
-[[nodiscard]] constexpr u8 Convert6To8(u8 value) {
- return (value << 2) | (value >> 4);
-}
-
-/// Convert a 8-bit color component to 1 bit
-[[nodiscard]] constexpr u8 Convert8To1(u8 value) {
- return value >> 7;
-}
-
-/// Convert a 8-bit color component to 4 bit
-[[nodiscard]] constexpr u8 Convert8To4(u8 value) {
- return value >> 4;
-}
-
-/// Convert a 8-bit color component to 5 bit
-[[nodiscard]] constexpr u8 Convert8To5(u8 value) {
- return value >> 3;
-}
-
-/// Convert a 8-bit color component to 6 bit
-[[nodiscard]] constexpr u8 Convert8To6(u8 value) {
- return value >> 2;
-}
-
-/**
- * Decode a color stored in RGBA8 format
- * @param bytes Pointer to encoded source color
- * @return Result color decoded as Common::Vec4<u8>
- */
-[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
- return {bytes[3], bytes[2], bytes[1], bytes[0]};
-}
-
-/**
- * Decode a color stored in RGB8 format
- * @param bytes Pointer to encoded source color
- * @return Result color decoded as Common::Vec4<u8>
- */
-[[nodiscard]] inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
- return {bytes[2], bytes[1], bytes[0], 255};
-}
-
-/**
- * Decode a color stored in RG8 (aka HILO8) format
- * @param bytes Pointer to encoded source color
- * @return Result color decoded as Common::Vec4<u8>
- */
-[[nodiscard]] inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
- return {bytes[1], bytes[0], 0, 255};
-}
-
-/**
- * Decode a color stored in RGB565 format
- * @param bytes Pointer to encoded source color
- * @return Result color decoded as Common::Vec4<u8>
- */
-[[nodiscard]] inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
- u16_le pixel;
- std::memcpy(&pixel, bytes, sizeof(pixel));
- return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
- Convert5To8(pixel & 0x1F), 255};
-}
-
-/**
- * Decode a color stored in RGB5A1 format
- * @param bytes Pointer to encoded source color
- * @return Result color decoded as Common::Vec4<u8>
- */
-[[nodiscard]] inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
- u16_le pixel;
- std::memcpy(&pixel, bytes, sizeof(pixel));
- return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
- Convert5To8((pixel >> 1) & 0x1F), Convert1To8(pixel & 0x1)};
-}
-
-/**
- * Decode a color stored in RGBA4 format
- * @param bytes Pointer to encoded source color
- * @return Result color decoded as Common::Vec4<u8>
- */
-[[nodiscard]] inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
- u16_le pixel;
- std::memcpy(&pixel, bytes, sizeof(pixel));
- return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
- Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)};
-}
-
-/**
- * Decode a depth value stored in D16 format
- * @param bytes Pointer to encoded source value
- * @return Depth value as an u32
- */
-[[nodiscard]] inline u32 DecodeD16(const u8* bytes) {
- u16_le data;
- std::memcpy(&data, bytes, sizeof(data));
- return data;
-}
-
-/**
- * Decode a depth value stored in D24 format
- * @param bytes Pointer to encoded source value
- * @return Depth value as an u32
- */
-[[nodiscard]] inline u32 DecodeD24(const u8* bytes) {
- return (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
-}
-
-/**
- * Decode a depth value and a stencil value stored in D24S8 format
- * @param bytes Pointer to encoded source values
- * @return Resulting values stored as a Common::Vec2
- */
-[[nodiscard]] inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) {
- return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]};
-}
-
-/**
- * Encode a color as RGBA8 format
- * @param color Source color to encode
- * @param bytes Destination pointer to store encoded color
- */
-inline void EncodeRGBA8(const Common::Vec4<u8>& color, u8* bytes) {
- bytes[3] = color.r();
- bytes[2] = color.g();
- bytes[1] = color.b();
- bytes[0] = color.a();
-}
-
-/**
- * Encode a color as RGB8 format
- * @param color Source color to encode
- * @param bytes Destination pointer to store encoded color
- */
-inline void EncodeRGB8(const Common::Vec4<u8>& color, u8* bytes) {
- bytes[2] = color.r();
- bytes[1] = color.g();
- bytes[0] = color.b();
-}
-
-/**
- * Encode a color as RG8 (aka HILO8) format
- * @param color Source color to encode
- * @param bytes Destination pointer to store encoded color
- */
-inline void EncodeRG8(const Common::Vec4<u8>& color, u8* bytes) {
- bytes[1] = color.r();
- bytes[0] = color.g();
-}
-/**
- * Encode a color as RGB565 format
- * @param color Source color to encode
- * @param bytes Destination pointer to store encoded color
- */
-inline void EncodeRGB565(const Common::Vec4<u8>& color, u8* bytes) {
- const u16_le data =
- (Convert8To5(color.r()) << 11) | (Convert8To6(color.g()) << 5) | Convert8To5(color.b());
-
- std::memcpy(bytes, &data, sizeof(data));
-}
-
-/**
- * Encode a color as RGB5A1 format
- * @param color Source color to encode
- * @param bytes Destination pointer to store encoded color
- */
-inline void EncodeRGB5A1(const Common::Vec4<u8>& color, u8* bytes) {
- const u16_le data = (Convert8To5(color.r()) << 11) | (Convert8To5(color.g()) << 6) |
- (Convert8To5(color.b()) << 1) | Convert8To1(color.a());
-
- std::memcpy(bytes, &data, sizeof(data));
-}
-
-/**
- * Encode a color as RGBA4 format
- * @param color Source color to encode
- * @param bytes Destination pointer to store encoded color
- */
-inline void EncodeRGBA4(const Common::Vec4<u8>& color, u8* bytes) {
- const u16 data = (Convert8To4(color.r()) << 12) | (Convert8To4(color.g()) << 8) |
- (Convert8To4(color.b()) << 4) | Convert8To4(color.a());
-
- std::memcpy(bytes, &data, sizeof(data));
-}
-
-/**
- * Encode a 16 bit depth value as D16 format
- * @param value 16 bit source depth value to encode
- * @param bytes Pointer where to store the encoded value
- */
-inline void EncodeD16(u32 value, u8* bytes) {
- const u16_le data = static_cast<u16>(value);
- std::memcpy(bytes, &data, sizeof(data));
-}
-
-/**
- * Encode a 24 bit depth value as D24 format
- * @param value 24 bit source depth value to encode
- * @param bytes Pointer where to store the encoded value
- */
-inline void EncodeD24(u32 value, u8* bytes) {
- bytes[0] = value & 0xFF;
- bytes[1] = (value >> 8) & 0xFF;
- bytes[2] = (value >> 16) & 0xFF;
-}
-
-/**
- * Encode a 24 bit depth and 8 bit stencil values as D24S8 format
- * @param depth 24 bit source depth value to encode
- * @param stencil 8 bit source stencil value to encode
- * @param bytes Pointer where to store the encoded value
- */
-inline void EncodeD24S8(u32 depth, u8 stencil, u8* bytes) {
- bytes[0] = depth & 0xFF;
- bytes[1] = (depth >> 8) & 0xFF;
- bytes[2] = (depth >> 16) & 0xFF;
- bytes[3] = stencil;
-}
-
-/**
- * Encode a 24 bit depth value as D24X8 format (32 bits per pixel with 8 bits unused)
- * @param depth 24 bit source depth value to encode
- * @param bytes Pointer where to store the encoded value
- * @note unused bits will not be modified
- */
-inline void EncodeD24X8(u32 depth, u8* bytes) {
- bytes[0] = depth & 0xFF;
- bytes[1] = (depth >> 8) & 0xFF;
- bytes[2] = (depth >> 16) & 0xFF;
-}
-
-/**
- * Encode an 8 bit stencil value as X24S8 format (32 bits per pixel with 24 bits unused)
- * @param stencil 8 bit source stencil value to encode
- * @param bytes Pointer where to store the encoded value
- * @note unused bits will not be modified
- */
-inline void EncodeX24S8(u8 stencil, u8* bytes) {
- bytes[3] = stencil;
-}
-
-} // namespace Common::Color
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 98421bced..71b64e32a 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -24,10 +24,10 @@
#define INSERT_PADDING_WORDS(num_words) \
std::array<u32, num_words> CONCAT2(pad, __LINE__) {}
-/// These are similar to the INSERT_PADDING_* macros, but are needed for padding unions. This is
-/// because unions can only be initialized by one member.
-#define INSERT_UNION_PADDING_BYTES(num_bytes) std::array<u8, num_bytes> CONCAT2(pad, __LINE__)
-#define INSERT_UNION_PADDING_WORDS(num_words) std::array<u32, num_words> CONCAT2(pad, __LINE__)
+/// These are similar to the INSERT_PADDING_* macros but do not zero-initialize the contents.
+/// This keeps the structure trivial to construct.
+#define INSERT_PADDING_BYTES_NOINIT(num_bytes) std::array<u8, num_bytes> CONCAT2(pad, __LINE__)
+#define INSERT_PADDING_WORDS_NOINIT(num_words) std::array<u32, num_words> CONCAT2(pad, __LINE__)
#ifndef _MSC_VER
@@ -64,14 +64,20 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
- constexpr type& operator|=(type& a, type b) noexcept { \
+ [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
- a = static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
+ return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
+ } \
+ constexpr type& operator|=(type& a, type b) noexcept { \
+ a = a | b; \
return a; \
} \
constexpr type& operator&=(type& a, type b) noexcept { \
- using T = std::underlying_type_t<type>; \
- a = static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
+ a = a & b; \
+ return a; \
+ } \
+ constexpr type& operator^=(type& a, type b) noexcept { \
+ a = a ^ b; \
return a; \
} \
[[nodiscard]] constexpr type operator~(type key) noexcept { \
@@ -87,6 +93,31 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
return static_cast<T>(key) == 0; \
}
+/// Evaluates a boolean expression, and returns a result unless that expression is true.
+#define R_UNLESS(expr, res) \
+ { \
+ if (!(expr)) { \
+ if (res.IsError()) { \
+ LOG_ERROR(Kernel, "Failed with result: {}", res.raw); \
+ } \
+ return res; \
+ } \
+ }
+
+#define R_SUCCEEDED(res) (res.IsSuccess())
+
+/// Evaluates an expression that returns a result, and returns the result if it would fail.
+#define R_TRY(res_expr) \
+ { \
+ const auto _tmp_r_try_rc = (res_expr); \
+ if (_tmp_r_try_rc.IsError()) { \
+ return _tmp_r_try_rc; \
+ } \
+ }
+
+/// Evaluates a boolean expression, and succeeds if that expression is true.
+#define R_SUCCEED_IF(expr) R_UNLESS(!(expr), RESULT_SUCCESS)
+
namespace Common {
[[nodiscard]] constexpr u32 MakeMagic(char a, char b, char c, char d) {
diff --git a/src/common/concepts.h b/src/common/concepts.h
index 5bef3ad67..aa08065a7 100644
--- a/src/common/concepts.h
+++ b/src/common/concepts.h
@@ -31,4 +31,8 @@ concept DerivedFrom = requires {
std::is_convertible_v<const volatile Derived*, const volatile Base*>;
};
+// TODO: Replace with std::convertible_to when libc++ implements it.
+template <typename From, typename To>
+concept ConvertibleTo = std::is_convertible_v<From, To>;
+
} // namespace Common
diff --git a/src/common/div_ceil.h b/src/common/div_ceil.h
new file mode 100644
index 000000000..95e1489a9
--- /dev/null
+++ b/src/common/div_ceil.h
@@ -0,0 +1,26 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+
+namespace Common {
+
+/// Ceiled integer division.
+template <typename N, typename D>
+requires std::is_integral_v<N>&& std::is_unsigned_v<D>[[nodiscard]] constexpr N DivCeil(N number,
+ D divisor) {
+ return static_cast<N>((static_cast<D>(number) + divisor - 1) / divisor);
+}
+
+/// Ceiled integer division with logarithmic divisor in base 2
+template <typename N, typename D>
+requires std::is_integral_v<N>&& std::is_unsigned_v<D>[[nodiscard]] constexpr N DivCeilLog2(
+ N value, D alignment_log2) {
+ return static_cast<N>((static_cast<D>(value) + (D(1) << alignment_log2) - 1) >> alignment_log2);
+}
+
+} // namespace Common
diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp
index 1c1d09ccb..3c1eefcb7 100644
--- a/src/common/fiber.cpp
+++ b/src/common/fiber.cpp
@@ -4,129 +4,51 @@
#include "common/assert.h"
#include "common/fiber.h"
-#if defined(_WIN32) || defined(WIN32)
-#include <windows.h>
-#else
+#include "common/spin_lock.h"
+#include "common/virtual_buffer.h"
+
#include <boost/context/detail/fcontext.hpp>
-#endif
namespace Common {
-constexpr std::size_t default_stack_size = 256 * 1024; // 256kb
-
-#if defined(_WIN32) || defined(WIN32)
+constexpr std::size_t default_stack_size = 256 * 1024;
struct Fiber::FiberImpl {
- LPVOID handle = nullptr;
- LPVOID rewind_handle = nullptr;
+ FiberImpl() : stack{default_stack_size}, rewind_stack{default_stack_size} {}
+
+ VirtualBuffer<u8> stack;
+ VirtualBuffer<u8> rewind_stack;
+
+ SpinLock guard{};
+ std::function<void(void*)> entry_point;
+ std::function<void(void*)> rewind_point;
+ void* rewind_parameter{};
+ void* start_parameter{};
+ std::shared_ptr<Fiber> previous_fiber;
+ bool is_thread_fiber{};
+ bool released{};
+
+ u8* stack_limit{};
+ u8* rewind_stack_limit{};
+ boost::context::detail::fcontext_t context{};
+ boost::context::detail::fcontext_t rewind_context{};
};
-void Fiber::Start() {
- ASSERT(previous_fiber != nullptr);
- previous_fiber->guard.unlock();
- previous_fiber.reset();
- entry_point(start_parameter);
- UNREACHABLE();
-}
-
-void Fiber::OnRewind() {
- ASSERT(impl->handle != nullptr);
- DeleteFiber(impl->handle);
- impl->handle = impl->rewind_handle;
- impl->rewind_handle = nullptr;
- rewind_point(rewind_parameter);
- UNREACHABLE();
-}
-
-void Fiber::FiberStartFunc(void* fiber_parameter) {
- auto fiber = static_cast<Fiber*>(fiber_parameter);
- fiber->Start();
-}
-
-void Fiber::RewindStartFunc(void* fiber_parameter) {
- auto fiber = static_cast<Fiber*>(fiber_parameter);
- fiber->OnRewind();
-}
-
-Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
- : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
- impl = std::make_unique<FiberImpl>();
- impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this);
-}
-
-Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
-
-Fiber::~Fiber() {
- if (released) {
- return;
- }
- // Make sure the Fiber is not being used
- const bool locked = guard.try_lock();
- ASSERT_MSG(locked, "Destroying a fiber that's still running");
- if (locked) {
- guard.unlock();
- }
- DeleteFiber(impl->handle);
-}
-
-void Fiber::Exit() {
- ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
- if (!is_thread_fiber) {
- return;
- }
- ConvertFiberToThread();
- guard.unlock();
- released = true;
-}
-
-void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
- rewind_point = std::move(rewind_func);
- rewind_parameter = start_parameter;
-}
-
-void Fiber::Rewind() {
- ASSERT(rewind_point);
- ASSERT(impl->rewind_handle == nullptr);
- impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this);
- SwitchToFiber(impl->rewind_handle);
-}
-
-void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
- ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
- ASSERT_MSG(to != nullptr, "Next fiber is null!");
- to->guard.lock();
- to->previous_fiber = from;
- SwitchToFiber(to->impl->handle);
- ASSERT(from->previous_fiber != nullptr);
- from->previous_fiber->guard.unlock();
- from->previous_fiber.reset();
+void Fiber::SetStartParameter(void* new_parameter) {
+ impl->start_parameter = new_parameter;
}
-std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
- std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
- fiber->guard.lock();
- fiber->impl->handle = ConvertThreadToFiber(nullptr);
- fiber->is_thread_fiber = true;
- return fiber;
+void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param) {
+ impl->rewind_point = std::move(rewind_func);
+ impl->rewind_parameter = rewind_param;
}
-#else
-
-struct Fiber::FiberImpl {
- alignas(64) std::array<u8, default_stack_size> stack;
- alignas(64) std::array<u8, default_stack_size> rewind_stack;
- u8* stack_limit;
- u8* rewind_stack_limit;
- boost::context::detail::fcontext_t context;
- boost::context::detail::fcontext_t rewind_context;
-};
-
void Fiber::Start(boost::context::detail::transfer_t& transfer) {
- ASSERT(previous_fiber != nullptr);
- previous_fiber->impl->context = transfer.fctx;
- previous_fiber->guard.unlock();
- previous_fiber.reset();
- entry_point(start_parameter);
+ ASSERT(impl->previous_fiber != nullptr);
+ impl->previous_fiber->impl->context = transfer.fctx;
+ impl->previous_fiber->impl->guard.unlock();
+ impl->previous_fiber.reset();
+ impl->entry_point(impl->start_parameter);
UNREACHABLE();
}
@@ -137,23 +59,24 @@ void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transf
u8* tmp = impl->stack_limit;
impl->stack_limit = impl->rewind_stack_limit;
impl->rewind_stack_limit = tmp;
- rewind_point(rewind_parameter);
+ impl->rewind_point(impl->rewind_parameter);
UNREACHABLE();
}
void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) {
- auto fiber = static_cast<Fiber*>(transfer.data);
+ auto* fiber = static_cast<Fiber*>(transfer.data);
fiber->Start(transfer);
}
void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) {
- auto fiber = static_cast<Fiber*>(transfer.data);
+ auto* fiber = static_cast<Fiber*>(transfer.data);
fiber->OnRewind(transfer);
}
Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
- : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
- impl = std::make_unique<FiberImpl>();
+ : impl{std::make_unique<FiberImpl>()} {
+ impl->entry_point = std::move(entry_point_func);
+ impl->start_parameter = start_parameter;
impl->stack_limit = impl->stack.data();
impl->rewind_stack_limit = impl->rewind_stack.data();
u8* stack_base = impl->stack_limit + default_stack_size;
@@ -161,37 +84,31 @@ Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_paramete
boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc);
}
-void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
- rewind_point = std::move(rewind_func);
- rewind_parameter = start_parameter;
-}
-
Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
Fiber::~Fiber() {
- if (released) {
+ if (impl->released) {
return;
}
// Make sure the Fiber is not being used
- const bool locked = guard.try_lock();
+ const bool locked = impl->guard.try_lock();
ASSERT_MSG(locked, "Destroying a fiber that's still running");
if (locked) {
- guard.unlock();
+ impl->guard.unlock();
}
}
void Fiber::Exit() {
-
- ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
- if (!is_thread_fiber) {
+ ASSERT_MSG(impl->is_thread_fiber, "Exitting non main thread fiber");
+ if (!impl->is_thread_fiber) {
return;
}
- guard.unlock();
- released = true;
+ impl->guard.unlock();
+ impl->released = true;
}
void Fiber::Rewind() {
- ASSERT(rewind_point);
+ ASSERT(impl->rewind_point);
ASSERT(impl->rewind_context == nullptr);
u8* stack_base = impl->rewind_stack_limit + default_stack_size;
impl->rewind_context =
@@ -199,24 +116,23 @@ void Fiber::Rewind() {
boost::context::detail::jump_fcontext(impl->rewind_context, this);
}
-void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
+void Fiber::YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to) {
ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
ASSERT_MSG(to != nullptr, "Next fiber is null!");
- to->guard.lock();
- to->previous_fiber = from;
+ to->impl->guard.lock();
+ to->impl->previous_fiber = from;
auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get());
- ASSERT(from->previous_fiber != nullptr);
- from->previous_fiber->impl->context = transfer.fctx;
- from->previous_fiber->guard.unlock();
- from->previous_fiber.reset();
+ ASSERT(from->impl->previous_fiber != nullptr);
+ from->impl->previous_fiber->impl->context = transfer.fctx;
+ from->impl->previous_fiber->impl->guard.unlock();
+ from->impl->previous_fiber.reset();
}
std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
- fiber->guard.lock();
- fiber->is_thread_fiber = true;
+ fiber->impl->guard.lock();
+ fiber->impl->is_thread_fiber = true;
return fiber;
}
-#endif
} // namespace Common
diff --git a/src/common/fiber.h b/src/common/fiber.h
index 89dde5e36..f7f587f8c 100644
--- a/src/common/fiber.h
+++ b/src/common/fiber.h
@@ -7,14 +7,9 @@
#include <functional>
#include <memory>
-#include "common/common_types.h"
-#include "common/spin_lock.h"
-
-#if !defined(_WIN32) && !defined(WIN32)
namespace boost::context::detail {
struct transfer_t;
}
-#endif
namespace Common {
@@ -46,10 +41,10 @@ public:
/// Yields control from Fiber 'from' to Fiber 'to'
/// Fiber 'from' must be the currently running fiber.
- static void YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to);
+ static void YieldTo(std::shared_ptr<Fiber> from, std::shared_ptr<Fiber> to);
[[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
- void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter);
+ void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* rewind_param);
void Rewind();
@@ -57,36 +52,18 @@ public:
void Exit();
/// Changes the start parameter of the fiber. Has no effect if the fiber already started
- void SetStartParameter(void* new_parameter) {
- start_parameter = new_parameter;
- }
+ void SetStartParameter(void* new_parameter);
private:
Fiber();
-#if defined(_WIN32) || defined(WIN32)
- void OnRewind();
- void Start();
- static void FiberStartFunc(void* fiber_parameter);
- static void RewindStartFunc(void* fiber_parameter);
-#else
void OnRewind(boost::context::detail::transfer_t& transfer);
void Start(boost::context::detail::transfer_t& transfer);
static void FiberStartFunc(boost::context::detail::transfer_t transfer);
static void RewindStartFunc(boost::context::detail::transfer_t transfer);
-#endif
struct FiberImpl;
-
- SpinLock guard{};
- std::function<void(void*)> entry_point;
- std::function<void(void*)> rewind_point;
- void* rewind_parameter{};
- void* start_parameter{};
- std::shared_ptr<Fiber> previous_fiber;
std::unique_ptr<FiberImpl> impl;
- bool is_thread_fiber{};
- bool released{};
};
} // namespace Common
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 16c3713e0..18fbfa25b 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -472,13 +472,14 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
}
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
- const auto callback = [recursion](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
- std::string new_path = directory + DIR_SEP_CHR + virtual_name;
+ const auto callback = [recursion](u64*, const std::string& directory,
+ const std::string& virtual_name) {
+ const std::string new_path = directory + DIR_SEP_CHR + virtual_name;
if (IsDirectory(new_path)) {
- if (recursion == 0)
+ if (recursion == 0) {
return false;
+ }
return DeleteDirRecursively(new_path, recursion - 1);
}
return Delete(new_path);
@@ -492,7 +493,8 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion)
return true;
}
-void CopyDir(const std::string& source_path, const std::string& dest_path) {
+void CopyDir([[maybe_unused]] const std::string& source_path,
+ [[maybe_unused]] const std::string& dest_path) {
#ifndef _WIN32
if (source_path == dest_path) {
return;
@@ -553,7 +555,7 @@ std::optional<std::string> GetCurrentDir() {
std::string strDir = dir;
#endif
free(dir);
- return std::move(strDir);
+ return strDir;
}
bool SetCurrentDir(const std::string& directory) {
@@ -772,21 +774,23 @@ std::size_t ReadFileToString(bool text_file, const std::string& filename, std::s
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
std::array<char, 4>& extension) {
- const std::string forbidden_characters = ".\"/\\[]:;=, ";
+ static constexpr std::string_view forbidden_characters = ".\"/\\[]:;=, ";
// On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces.
short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}};
extension = {{' ', ' ', ' ', '\0'}};
- std::string::size_type point = filename.rfind('.');
- if (point == filename.size() - 1)
+ auto point = filename.rfind('.');
+ if (point == filename.size() - 1) {
point = filename.rfind('.', point);
+ }
// Get short name.
int j = 0;
for (char letter : filename.substr(0, point)) {
- if (forbidden_characters.find(letter, 0) != std::string::npos)
+ if (forbidden_characters.find(letter, 0) != std::string::npos) {
continue;
+ }
if (j == 8) {
// TODO(Link Mauve): also do that for filenames containing a space.
// TODO(Link Mauve): handle multiple files having the same short name.
@@ -794,14 +798,15 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
short_name[7] = '1';
break;
}
- short_name[j++] = toupper(letter);
+ short_name[j++] = static_cast<char>(std::toupper(letter));
}
// Get extension.
if (point != std::string::npos) {
j = 0;
- for (char letter : filename.substr(point + 1, 3))
- extension[j++] = toupper(letter);
+ for (char letter : filename.substr(point + 1, 3)) {
+ extension[j++] = static_cast<char>(std::toupper(letter));
+ }
}
}
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 8b587320f..840cde2a6 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -232,7 +232,7 @@ public:
void Swap(IOFile& other) noexcept;
- [[nodiscard]] bool Open(const std::string& filename, const char openmode[], int flags = 0);
+ bool Open(const std::string& filename, const char openmode[], int flags = 0);
bool Close();
template <typename T>
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index 120f1a5e6..a8d414fb8 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -16,14 +16,14 @@ namespace Common {
[[nodiscard]] constexpr u8 ToHexNibble(char c) {
if (c >= 65 && c <= 70) {
- return c - 55;
+ return static_cast<u8>(c - 55);
}
if (c >= 97 && c <= 102) {
- return c - 87;
+ return static_cast<u8>(c - 87);
}
- return c - 48;
+ return static_cast<u8>(c - 48);
}
[[nodiscard]] std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
@@ -33,11 +33,11 @@ template <std::size_t Size, bool le = false>
std::array<u8, Size> out{};
if constexpr (le) {
for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) {
- out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
+ out[i / 2] = static_cast<u8>((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]));
}
} else {
for (std::size_t i = 0; i < 2 * Size; i += 2) {
- out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
+ out[i / 2] = static_cast<u8>((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]));
}
}
return out;
diff --git a/src/common/intrusive_red_black_tree.h b/src/common/intrusive_red_black_tree.h
new file mode 100644
index 000000000..c0bbcd457
--- /dev/null
+++ b/src/common/intrusive_red_black_tree.h
@@ -0,0 +1,602 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/parent_of_member.h"
+#include "common/tree.h"
+
+namespace Common {
+
+namespace impl {
+
+class IntrusiveRedBlackTreeImpl;
+
+}
+
+struct IntrusiveRedBlackTreeNode {
+public:
+ using EntryType = RBEntry<IntrusiveRedBlackTreeNode>;
+
+ constexpr IntrusiveRedBlackTreeNode() = default;
+
+ void SetEntry(const EntryType& new_entry) {
+ entry = new_entry;
+ }
+
+ [[nodiscard]] EntryType& GetEntry() {
+ return entry;
+ }
+
+ [[nodiscard]] const EntryType& GetEntry() const {
+ return entry;
+ }
+
+private:
+ EntryType entry{};
+
+ friend class impl::IntrusiveRedBlackTreeImpl;
+
+ template <class, class, class>
+ friend class IntrusiveRedBlackTree;
+};
+
+template <class T, class Traits, class Comparator>
+class IntrusiveRedBlackTree;
+
+namespace impl {
+
+class IntrusiveRedBlackTreeImpl {
+private:
+ template <class, class, class>
+ friend class ::Common::IntrusiveRedBlackTree;
+
+ using RootType = RBHead<IntrusiveRedBlackTreeNode>;
+ RootType root;
+
+public:
+ template <bool Const>
+ class Iterator;
+
+ using value_type = IntrusiveRedBlackTreeNode;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using iterator = Iterator<false>;
+ using const_iterator = Iterator<true>;
+
+ template <bool Const>
+ class Iterator {
+ public:
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type = typename IntrusiveRedBlackTreeImpl::value_type;
+ using difference_type = typename IntrusiveRedBlackTreeImpl::difference_type;
+ using pointer = std::conditional_t<Const, IntrusiveRedBlackTreeImpl::const_pointer,
+ IntrusiveRedBlackTreeImpl::pointer>;
+ using reference = std::conditional_t<Const, IntrusiveRedBlackTreeImpl::const_reference,
+ IntrusiveRedBlackTreeImpl::reference>;
+
+ private:
+ pointer node;
+
+ public:
+ explicit Iterator(pointer n) : node(n) {}
+
+ bool operator==(const Iterator& rhs) const {
+ return this->node == rhs.node;
+ }
+
+ bool operator!=(const Iterator& rhs) const {
+ return !(*this == rhs);
+ }
+
+ pointer operator->() const {
+ return this->node;
+ }
+
+ reference operator*() const {
+ return *this->node;
+ }
+
+ Iterator& operator++() {
+ this->node = GetNext(this->node);
+ return *this;
+ }
+
+ Iterator& operator--() {
+ this->node = GetPrev(this->node);
+ return *this;
+ }
+
+ Iterator operator++(int) {
+ const Iterator it{*this};
+ ++(*this);
+ return it;
+ }
+
+ Iterator operator--(int) {
+ const Iterator it{*this};
+ --(*this);
+ return it;
+ }
+
+ operator Iterator<true>() const {
+ return Iterator<true>(this->node);
+ }
+ };
+
+private:
+ // Define accessors using RB_* functions.
+ bool EmptyImpl() const {
+ return root.IsEmpty();
+ }
+
+ IntrusiveRedBlackTreeNode* GetMinImpl() const {
+ return RB_MIN(const_cast<RootType*>(&root));
+ }
+
+ IntrusiveRedBlackTreeNode* GetMaxImpl() const {
+ return RB_MAX(const_cast<RootType*>(&root));
+ }
+
+ IntrusiveRedBlackTreeNode* RemoveImpl(IntrusiveRedBlackTreeNode* node) {
+ return RB_REMOVE(&root, node);
+ }
+
+public:
+ static IntrusiveRedBlackTreeNode* GetNext(IntrusiveRedBlackTreeNode* node) {
+ return RB_NEXT(node);
+ }
+
+ static IntrusiveRedBlackTreeNode* GetPrev(IntrusiveRedBlackTreeNode* node) {
+ return RB_PREV(node);
+ }
+
+ static const IntrusiveRedBlackTreeNode* GetNext(const IntrusiveRedBlackTreeNode* node) {
+ return static_cast<const IntrusiveRedBlackTreeNode*>(
+ GetNext(const_cast<IntrusiveRedBlackTreeNode*>(node)));
+ }
+
+ static const IntrusiveRedBlackTreeNode* GetPrev(const IntrusiveRedBlackTreeNode* node) {
+ return static_cast<const IntrusiveRedBlackTreeNode*>(
+ GetPrev(const_cast<IntrusiveRedBlackTreeNode*>(node)));
+ }
+
+public:
+ constexpr IntrusiveRedBlackTreeImpl() {}
+
+ // Iterator accessors.
+ iterator begin() {
+ return iterator(this->GetMinImpl());
+ }
+
+ const_iterator begin() const {
+ return const_iterator(this->GetMinImpl());
+ }
+
+ iterator end() {
+ return iterator(static_cast<IntrusiveRedBlackTreeNode*>(nullptr));
+ }
+
+ const_iterator end() const {
+ return const_iterator(static_cast<const IntrusiveRedBlackTreeNode*>(nullptr));
+ }
+
+ const_iterator cbegin() const {
+ return this->begin();
+ }
+
+ const_iterator cend() const {
+ return this->end();
+ }
+
+ iterator iterator_to(reference ref) {
+ return iterator(&ref);
+ }
+
+ const_iterator iterator_to(const_reference ref) const {
+ return const_iterator(&ref);
+ }
+
+ // Content management.
+ bool empty() const {
+ return this->EmptyImpl();
+ }
+
+ reference back() {
+ return *this->GetMaxImpl();
+ }
+
+ const_reference back() const {
+ return *this->GetMaxImpl();
+ }
+
+ reference front() {
+ return *this->GetMinImpl();
+ }
+
+ const_reference front() const {
+ return *this->GetMinImpl();
+ }
+
+ iterator erase(iterator it) {
+ auto cur = std::addressof(*it);
+ auto next = GetNext(cur);
+ this->RemoveImpl(cur);
+ return iterator(next);
+ }
+};
+
+} // namespace impl
+
+template <typename T>
+concept HasLightCompareType = requires {
+ { std::is_same<typename T::LightCompareType, void>::value }
+ ->std::convertible_to<bool>;
+};
+
+namespace impl {
+
+template <typename T, typename Default>
+consteval auto* GetLightCompareType() {
+ if constexpr (HasLightCompareType<T>) {
+ return static_cast<typename T::LightCompareType*>(nullptr);
+ } else {
+ return static_cast<Default*>(nullptr);
+ }
+}
+
+} // namespace impl
+
+template <typename T, typename Default>
+using LightCompareType = std::remove_pointer_t<decltype(impl::GetLightCompareType<T, Default>())>;
+
+template <class T, class Traits, class Comparator>
+class IntrusiveRedBlackTree {
+
+public:
+ using ImplType = impl::IntrusiveRedBlackTreeImpl;
+
+private:
+ ImplType impl{};
+
+public:
+ template <bool Const>
+ class Iterator;
+
+ using value_type = T;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = const T&;
+ using iterator = Iterator<false>;
+ using const_iterator = Iterator<true>;
+
+ using light_value_type = LightCompareType<Comparator, value_type>;
+ using const_light_pointer = const light_value_type*;
+ using const_light_reference = const light_value_type&;
+
+ template <bool Const>
+ class Iterator {
+ public:
+ friend class IntrusiveRedBlackTree<T, Traits, Comparator>;
+
+ using ImplIterator =
+ std::conditional_t<Const, ImplType::const_iterator, ImplType::iterator>;
+
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type = typename IntrusiveRedBlackTree::value_type;
+ using difference_type = typename IntrusiveRedBlackTree::difference_type;
+ using pointer = std::conditional_t<Const, IntrusiveRedBlackTree::const_pointer,
+ IntrusiveRedBlackTree::pointer>;
+ using reference = std::conditional_t<Const, IntrusiveRedBlackTree::const_reference,
+ IntrusiveRedBlackTree::reference>;
+
+ private:
+ ImplIterator iterator;
+
+ private:
+ explicit Iterator(ImplIterator it) : iterator(it) {}
+
+ explicit Iterator(typename std::conditional<Const, ImplType::const_iterator,
+ ImplType::iterator>::type::pointer ptr)
+ : iterator(ptr) {}
+
+ ImplIterator GetImplIterator() const {
+ return this->iterator;
+ }
+
+ public:
+ bool operator==(const Iterator& rhs) const {
+ return this->iterator == rhs.iterator;
+ }
+
+ bool operator!=(const Iterator& rhs) const {
+ return !(*this == rhs);
+ }
+
+ pointer operator->() const {
+ return Traits::GetParent(std::addressof(*this->iterator));
+ }
+
+ reference operator*() const {
+ return *Traits::GetParent(std::addressof(*this->iterator));
+ }
+
+ Iterator& operator++() {
+ ++this->iterator;
+ return *this;
+ }
+
+ Iterator& operator--() {
+ --this->iterator;
+ return *this;
+ }
+
+ Iterator operator++(int) {
+ const Iterator it{*this};
+ ++this->iterator;
+ return it;
+ }
+
+ Iterator operator--(int) {
+ const Iterator it{*this};
+ --this->iterator;
+ return it;
+ }
+
+ operator Iterator<true>() const {
+ return Iterator<true>(this->iterator);
+ }
+ };
+
+private:
+ static int CompareImpl(const IntrusiveRedBlackTreeNode* lhs,
+ const IntrusiveRedBlackTreeNode* rhs) {
+ return Comparator::Compare(*Traits::GetParent(lhs), *Traits::GetParent(rhs));
+ }
+
+ static int LightCompareImpl(const void* elm, const IntrusiveRedBlackTreeNode* rhs) {
+ return Comparator::Compare(*static_cast<const_light_pointer>(elm), *Traits::GetParent(rhs));
+ }
+
+ // Define accessors using RB_* functions.
+ IntrusiveRedBlackTreeNode* InsertImpl(IntrusiveRedBlackTreeNode* node) {
+ return RB_INSERT(&impl.root, node, CompareImpl);
+ }
+
+ IntrusiveRedBlackTreeNode* FindImpl(const IntrusiveRedBlackTreeNode* node) const {
+ return RB_FIND(const_cast<ImplType::RootType*>(&impl.root),
+ const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl);
+ }
+
+ IntrusiveRedBlackTreeNode* NFindImpl(const IntrusiveRedBlackTreeNode* node) const {
+ return RB_NFIND(const_cast<ImplType::RootType*>(&impl.root),
+ const_cast<IntrusiveRedBlackTreeNode*>(node), CompareImpl);
+ }
+
+ IntrusiveRedBlackTreeNode* FindLightImpl(const_light_pointer lelm) const {
+ return RB_FIND_LIGHT(const_cast<ImplType::RootType*>(&impl.root),
+ static_cast<const void*>(lelm), LightCompareImpl);
+ }
+
+ IntrusiveRedBlackTreeNode* NFindLightImpl(const_light_pointer lelm) const {
+ return RB_NFIND_LIGHT(const_cast<ImplType::RootType*>(&impl.root),
+ static_cast<const void*>(lelm), LightCompareImpl);
+ }
+
+public:
+ constexpr IntrusiveRedBlackTree() = default;
+
+ // Iterator accessors.
+ iterator begin() {
+ return iterator(this->impl.begin());
+ }
+
+ const_iterator begin() const {
+ return const_iterator(this->impl.begin());
+ }
+
+ iterator end() {
+ return iterator(this->impl.end());
+ }
+
+ const_iterator end() const {
+ return const_iterator(this->impl.end());
+ }
+
+ const_iterator cbegin() const {
+ return this->begin();
+ }
+
+ const_iterator cend() const {
+ return this->end();
+ }
+
+ iterator iterator_to(reference ref) {
+ return iterator(this->impl.iterator_to(*Traits::GetNode(std::addressof(ref))));
+ }
+
+ const_iterator iterator_to(const_reference ref) const {
+ return const_iterator(this->impl.iterator_to(*Traits::GetNode(std::addressof(ref))));
+ }
+
+ // Content management.
+ bool empty() const {
+ return this->impl.empty();
+ }
+
+ reference back() {
+ return *Traits::GetParent(std::addressof(this->impl.back()));
+ }
+
+ const_reference back() const {
+ return *Traits::GetParent(std::addressof(this->impl.back()));
+ }
+
+ reference front() {
+ return *Traits::GetParent(std::addressof(this->impl.front()));
+ }
+
+ const_reference front() const {
+ return *Traits::GetParent(std::addressof(this->impl.front()));
+ }
+
+ iterator erase(iterator it) {
+ return iterator(this->impl.erase(it.GetImplIterator()));
+ }
+
+ iterator insert(reference ref) {
+ ImplType::pointer node = Traits::GetNode(std::addressof(ref));
+ this->InsertImpl(node);
+ return iterator(node);
+ }
+
+ iterator find(const_reference ref) const {
+ return iterator(this->FindImpl(Traits::GetNode(std::addressof(ref))));
+ }
+
+ iterator nfind(const_reference ref) const {
+ return iterator(this->NFindImpl(Traits::GetNode(std::addressof(ref))));
+ }
+
+ iterator find_light(const_light_reference ref) const {
+ return iterator(this->FindLightImpl(std::addressof(ref)));
+ }
+
+ iterator nfind_light(const_light_reference ref) const {
+ return iterator(this->NFindLightImpl(std::addressof(ref)));
+ }
+};
+
+template <auto T, class Derived = impl::GetParentType<T>>
+class IntrusiveRedBlackTreeMemberTraits;
+
+template <class Parent, IntrusiveRedBlackTreeNode Parent::*Member, class Derived>
+class IntrusiveRedBlackTreeMemberTraits<Member, Derived> {
+public:
+ template <class Comparator>
+ using TreeType = IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeMemberTraits, Comparator>;
+ using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl;
+
+private:
+ template <class, class, class>
+ friend class IntrusiveRedBlackTree;
+
+ friend class impl::IntrusiveRedBlackTreeImpl;
+
+ static constexpr IntrusiveRedBlackTreeNode* GetNode(Derived* parent) {
+ return std::addressof(parent->*Member);
+ }
+
+ static constexpr IntrusiveRedBlackTreeNode const* GetNode(Derived const* parent) {
+ return std::addressof(parent->*Member);
+ }
+
+ static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) {
+ return GetParentPointer<Member, Derived>(node);
+ }
+
+ static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) {
+ return GetParentPointer<Member, Derived>(node);
+ }
+
+private:
+ static constexpr TypedStorage<Derived> DerivedStorage = {};
+ static_assert(GetParent(GetNode(GetPointer(DerivedStorage))) == GetPointer(DerivedStorage));
+};
+
+template <auto T, class Derived = impl::GetParentType<T>>
+class IntrusiveRedBlackTreeMemberTraitsDeferredAssert;
+
+template <class Parent, IntrusiveRedBlackTreeNode Parent::*Member, class Derived>
+class IntrusiveRedBlackTreeMemberTraitsDeferredAssert<Member, Derived> {
+public:
+ template <class Comparator>
+ using TreeType =
+ IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeMemberTraitsDeferredAssert, Comparator>;
+ using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl;
+
+ static constexpr bool IsValid() {
+ TypedStorage<Derived> DerivedStorage = {};
+ return GetParent(GetNode(GetPointer(DerivedStorage))) == GetPointer(DerivedStorage);
+ }
+
+private:
+ template <class, class, class>
+ friend class IntrusiveRedBlackTree;
+
+ friend class impl::IntrusiveRedBlackTreeImpl;
+
+ static constexpr IntrusiveRedBlackTreeNode* GetNode(Derived* parent) {
+ return std::addressof(parent->*Member);
+ }
+
+ static constexpr IntrusiveRedBlackTreeNode const* GetNode(Derived const* parent) {
+ return std::addressof(parent->*Member);
+ }
+
+ static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) {
+ return GetParentPointer<Member, Derived>(node);
+ }
+
+ static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) {
+ return GetParentPointer<Member, Derived>(node);
+ }
+};
+
+template <class Derived>
+class IntrusiveRedBlackTreeBaseNode : public IntrusiveRedBlackTreeNode {
+public:
+ constexpr Derived* GetPrev() {
+ return static_cast<Derived*>(impl::IntrusiveRedBlackTreeImpl::GetPrev(this));
+ }
+ constexpr const Derived* GetPrev() const {
+ return static_cast<const Derived*>(impl::IntrusiveRedBlackTreeImpl::GetPrev(this));
+ }
+
+ constexpr Derived* GetNext() {
+ return static_cast<Derived*>(impl::IntrusiveRedBlackTreeImpl::GetNext(this));
+ }
+ constexpr const Derived* GetNext() const {
+ return static_cast<const Derived*>(impl::IntrusiveRedBlackTreeImpl::GetNext(this));
+ }
+};
+
+template <class Derived>
+class IntrusiveRedBlackTreeBaseTraits {
+public:
+ template <class Comparator>
+ using TreeType = IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeBaseTraits, Comparator>;
+ using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl;
+
+private:
+ template <class, class, class>
+ friend class IntrusiveRedBlackTree;
+
+ friend class impl::IntrusiveRedBlackTreeImpl;
+
+ static constexpr IntrusiveRedBlackTreeNode* GetNode(Derived* parent) {
+ return static_cast<IntrusiveRedBlackTreeNode*>(parent);
+ }
+
+ static constexpr IntrusiveRedBlackTreeNode const* GetNode(Derived const* parent) {
+ return static_cast<const IntrusiveRedBlackTreeNode*>(parent);
+ }
+
+ static constexpr Derived* GetParent(IntrusiveRedBlackTreeNode* node) {
+ return static_cast<Derived*>(node);
+ }
+
+ static constexpr Derived const* GetParent(const IntrusiveRedBlackTreeNode* node) {
+ return static_cast<const Derived*>(node);
+ }
+};
+
+} // namespace Common
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 62cfde397..2d4d2e9e7 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -23,6 +23,7 @@
#include "common/logging/text_formatter.h"
#include "common/string_util.h"
#include "common/threadsafe_queue.h"
+#include "core/settings.h"
namespace Log {
@@ -144,18 +145,35 @@ void ColorConsoleBackend::Write(const Entry& entry) {
PrintColoredMessage(entry);
}
-// _SH_DENYWR allows read only access to the file for other programs.
-// It is #defined to 0 on other platforms
-FileBackend::FileBackend(const std::string& filename)
- : file(filename, "w", _SH_DENYWR), bytes_written(0) {}
+FileBackend::FileBackend(const std::string& filename) : bytes_written(0) {
+ if (Common::FS::Exists(filename + ".old.txt")) {
+ Common::FS::Delete(filename + ".old.txt");
+ }
+ if (Common::FS::Exists(filename)) {
+ Common::FS::Rename(filename, filename + ".old.txt");
+ }
+
+ // _SH_DENYWR allows read only access to the file for other programs.
+ // It is #defined to 0 on other platforms
+ file = Common::FS::IOFile(filename, "w", _SH_DENYWR);
+}
void FileBackend::Write(const Entry& entry) {
// prevent logs from going over the maximum size (in case its spamming and the user doesn't
// know)
- constexpr std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L;
- if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
+ constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024;
+ constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024;
+
+ if (!file.IsOpen()) {
return;
}
+
+ if (Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN_EXTENDED) {
+ return;
+ } else if (!Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN) {
+ return;
+ }
+
bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
if (entry.log_level >= Level::Error) {
file.Flush();
@@ -222,6 +240,7 @@ void DebuggerBackend::Write(const Entry& entry) {
SUB(Service, NPNS) \
SUB(Service, NS) \
SUB(Service, NVDRV) \
+ SUB(Service, OLSC) \
SUB(Service, PCIE) \
SUB(Service, PCTL) \
SUB(Service, PCV) \
@@ -274,7 +293,6 @@ const char* GetLogClassName(Class log_class) {
case Class::Count:
break;
}
- UNREACHABLE();
return "Invalid";
}
@@ -293,7 +311,6 @@ const char* GetLevelName(Level log_level) {
break;
}
#undef LVL
- UNREACHABLE();
return "Invalid";
}
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 13a4f1e30..835894918 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -95,6 +95,7 @@ enum class Class : ClassType {
Service_NPNS, ///< The NPNS service
Service_NS, ///< The NS services
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
+ Service_OLSC, ///< The OLSC service
Service_PCIE, ///< The PCIe service
Service_PCTL, ///< The PCTL (Parental control) service
Service_PCV, ///< The PCV service
diff --git a/src/common/math_util.h b/src/common/math_util.h
index cc35c90ee..4c38d8040 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -9,7 +9,7 @@
namespace Common {
-constexpr float PI = 3.14159265f;
+constexpr float PI = 3.1415926535f;
template <class T>
struct Rectangle {
@@ -20,14 +20,14 @@ struct Rectangle {
constexpr Rectangle() = default;
- constexpr Rectangle(T left, T top, T right, T bottom)
- : left(left), top(top), right(right), bottom(bottom) {}
+ constexpr Rectangle(T left_, T top_, T right_, T bottom_)
+ : left(left_), top(top_), right(right_), bottom(bottom_) {}
[[nodiscard]] T GetWidth() const {
if constexpr (std::is_floating_point_v<T>) {
return std::abs(right - left);
} else {
- return std::abs(static_cast<std::make_signed_t<T>>(right - left));
+ return static_cast<T>(std::abs(static_cast<std::make_signed_t<T>>(right - left)));
}
}
@@ -35,7 +35,7 @@ struct Rectangle {
if constexpr (std::is_floating_point_v<T>) {
return std::abs(bottom - top);
} else {
- return std::abs(static_cast<std::make_signed_t<T>>(bottom - top));
+ return static_cast<T>(std::abs(static_cast<std::make_signed_t<T>>(bottom - top)));
}
}
diff --git a/src/common/memory_hook.cpp b/src/common/memory_hook.cpp
deleted file mode 100644
index 3986986d6..000000000
--- a/src/common/memory_hook.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2018 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/memory_hook.h"
-
-namespace Common {
-
-MemoryHook::~MemoryHook() = default;
-
-} // namespace Common
diff --git a/src/common/memory_hook.h b/src/common/memory_hook.h
deleted file mode 100644
index adaa4c2c5..000000000
--- a/src/common/memory_hook.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <optional>
-
-#include "common/common_types.h"
-
-namespace Common {
-
-/**
- * Memory hooks have two purposes:
- * 1. To allow reads and writes to a region of memory to be intercepted. This is used to implement
- * texture forwarding and memory breakpoints for debugging.
- * 2. To allow for the implementation of MMIO devices.
- *
- * A hook may be mapped to multiple regions of memory.
- *
- * If a std::nullopt or false is returned from a function, the read/write request is passed through
- * to the underlying memory region.
- */
-class MemoryHook {
-public:
- virtual ~MemoryHook();
-
- virtual std::optional<bool> IsValidAddress(VAddr addr) = 0;
-
- virtual std::optional<u8> Read8(VAddr addr) = 0;
- virtual std::optional<u16> Read16(VAddr addr) = 0;
- virtual std::optional<u32> Read32(VAddr addr) = 0;
- virtual std::optional<u64> Read64(VAddr addr) = 0;
-
- virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0;
-
- virtual bool Write8(VAddr addr, u8 data) = 0;
- virtual bool Write16(VAddr addr, u16 data) = 0;
- virtual bool Write32(VAddr addr, u32 data) = 0;
- virtual bool Write64(VAddr addr, u64 data) = 0;
-
- virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) = 0;
-};
-
-using MemoryHookPointer = std::shared_ptr<MemoryHook>;
-} // namespace Common
diff --git a/src/common/misc.cpp b/src/common/misc.cpp
index 68cb86cd1..1d5393597 100644
--- a/src/common/misc.cpp
+++ b/src/common/misc.cpp
@@ -16,16 +16,23 @@
// Call directly after the command or use the error num.
// This function might change the error code.
std::string GetLastErrorMsg() {
- static const std::size_t buff_size = 255;
+ static constexpr std::size_t buff_size = 255;
char err_str[buff_size];
#ifdef _WIN32
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr);
+ return std::string(err_str, buff_size);
+#elif defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600))
+ // Thread safe (GNU-specific)
+ const char* str = strerror_r(errno, err_str, buff_size);
+ return std::string(str);
#else
// Thread safe (XSI-compliant)
- strerror_r(errno, err_str, buff_size);
+ const int success = strerror_r(errno, err_str, buff_size);
+ if (success != 0) {
+ return {};
+ }
+ return std::string(err_str);
#endif
-
- return std::string(err_str, buff_size);
}
diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h
deleted file mode 100644
index 4b305bf40..000000000
--- a/src/common/multi_level_queue.h
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright 2019 TuxSH
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <iterator>
-#include <list>
-#include <utility>
-
-#include "common/bit_util.h"
-#include "common/common_types.h"
-
-namespace Common {
-
-/**
- * A MultiLevelQueue is a type of priority queue which has the following characteristics:
- * - iteratable through each of its elements.
- * - back can be obtained.
- * - O(1) add, lookup (both front and back)
- * - discrete priorities and a max of 64 priorities (limited domain)
- * This type of priority queue is normaly used for managing threads within an scheduler
- */
-template <typename T, std::size_t Depth>
-class MultiLevelQueue {
-public:
- using value_type = T;
- using reference = value_type&;
- using const_reference = const value_type&;
- using pointer = value_type*;
- using const_pointer = const value_type*;
-
- using difference_type = typename std::pointer_traits<pointer>::difference_type;
- using size_type = std::size_t;
-
- template <bool is_constant>
- class iterator_impl {
- public:
- using iterator_category = std::bidirectional_iterator_tag;
- using value_type = T;
- using pointer = std::conditional_t<is_constant, T*, const T*>;
- using reference = std::conditional_t<is_constant, const T&, T&>;
- using difference_type = typename std::pointer_traits<pointer>::difference_type;
-
- friend bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) {
- if (lhs.IsEnd() && rhs.IsEnd())
- return true;
- return std::tie(lhs.current_priority, lhs.it) == std::tie(rhs.current_priority, rhs.it);
- }
-
- friend bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) {
- return !operator==(lhs, rhs);
- }
-
- reference operator*() const {
- return *it;
- }
-
- pointer operator->() const {
- return it.operator->();
- }
-
- iterator_impl& operator++() {
- if (IsEnd()) {
- return *this;
- }
-
- ++it;
-
- if (it == GetEndItForPrio()) {
- u64 prios = mlq.used_priorities;
- prios &= ~((1ULL << (current_priority + 1)) - 1);
- if (prios == 0) {
- current_priority = static_cast<u32>(mlq.depth());
- } else {
- current_priority = CountTrailingZeroes64(prios);
- it = GetBeginItForPrio();
- }
- }
- return *this;
- }
-
- iterator_impl& operator--() {
- if (IsEnd()) {
- if (mlq.used_priorities != 0) {
- current_priority = 63 - CountLeadingZeroes64(mlq.used_priorities);
- it = GetEndItForPrio();
- --it;
- }
- } else if (it == GetBeginItForPrio()) {
- u64 prios = mlq.used_priorities;
- prios &= (1ULL << current_priority) - 1;
- if (prios != 0) {
- current_priority = CountTrailingZeroes64(prios);
- it = GetEndItForPrio();
- --it;
- }
- } else {
- --it;
- }
- return *this;
- }
-
- iterator_impl operator++(int) {
- const iterator_impl v{*this};
- ++(*this);
- return v;
- }
-
- iterator_impl operator--(int) {
- const iterator_impl v{*this};
- --(*this);
- return v;
- }
-
- // allow implicit const->non-const
- iterator_impl(const iterator_impl<false>& other)
- : mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
-
- iterator_impl(const iterator_impl<true>& other)
- : mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
-
- iterator_impl& operator=(const iterator_impl<false>& other) {
- mlq = other.mlq;
- it = other.it;
- current_priority = other.current_priority;
- return *this;
- }
-
- friend class iterator_impl<true>;
- iterator_impl() = default;
-
- private:
- friend class MultiLevelQueue;
- using container_ref =
- std::conditional_t<is_constant, const MultiLevelQueue&, MultiLevelQueue&>;
- using list_iterator = std::conditional_t<is_constant, typename std::list<T>::const_iterator,
- typename std::list<T>::iterator>;
-
- explicit iterator_impl(container_ref mlq, list_iterator it, u32 current_priority)
- : mlq(mlq), it(it), current_priority(current_priority) {}
- explicit iterator_impl(container_ref mlq, u32 current_priority)
- : mlq(mlq), it(), current_priority(current_priority) {}
-
- bool IsEnd() const {
- return current_priority == mlq.depth();
- }
-
- list_iterator GetBeginItForPrio() const {
- return mlq.levels[current_priority].begin();
- }
-
- list_iterator GetEndItForPrio() const {
- return mlq.levels[current_priority].end();
- }
-
- container_ref mlq;
- list_iterator it;
- u32 current_priority;
- };
-
- using iterator = iterator_impl<false>;
- using const_iterator = iterator_impl<true>;
-
- void add(const T& element, u32 priority, bool send_back = true) {
- if (send_back)
- levels[priority].push_back(element);
- else
- levels[priority].push_front(element);
- used_priorities |= 1ULL << priority;
- }
-
- void remove(const T& element, u32 priority) {
- auto it = ListIterateTo(levels[priority], element);
- if (it == levels[priority].end())
- return;
- levels[priority].erase(it);
- if (levels[priority].empty()) {
- used_priorities &= ~(1ULL << priority);
- }
- }
-
- void adjust(const T& element, u32 old_priority, u32 new_priority, bool adjust_front = false) {
- remove(element, old_priority);
- add(element, new_priority, !adjust_front);
- }
- void adjust(const_iterator it, u32 old_priority, u32 new_priority, bool adjust_front = false) {
- adjust(*it, old_priority, new_priority, adjust_front);
- }
-
- void transfer_to_front(const T& element, u32 priority, MultiLevelQueue& other) {
- ListSplice(other.levels[priority], other.levels[priority].begin(), levels[priority],
- ListIterateTo(levels[priority], element));
-
- other.used_priorities |= 1ULL << priority;
-
- if (levels[priority].empty()) {
- used_priorities &= ~(1ULL << priority);
- }
- }
-
- void transfer_to_front(const_iterator it, u32 priority, MultiLevelQueue& other) {
- transfer_to_front(*it, priority, other);
- }
-
- void transfer_to_back(const T& element, u32 priority, MultiLevelQueue& other) {
- ListSplice(other.levels[priority], other.levels[priority].end(), levels[priority],
- ListIterateTo(levels[priority], element));
-
- other.used_priorities |= 1ULL << priority;
-
- if (levels[priority].empty()) {
- used_priorities &= ~(1ULL << priority);
- }
- }
-
- void transfer_to_back(const_iterator it, u32 priority, MultiLevelQueue& other) {
- transfer_to_back(*it, priority, other);
- }
-
- void yield(u32 priority, std::size_t n = 1) {
- ListShiftForward(levels[priority], n);
- }
-
- [[nodiscard]] std::size_t depth() const {
- return Depth;
- }
-
- [[nodiscard]] std::size_t size(u32 priority) const {
- return levels[priority].size();
- }
-
- [[nodiscard]] std::size_t size() const {
- u64 priorities = used_priorities;
- std::size_t size = 0;
- while (priorities != 0) {
- const u64 current_priority = CountTrailingZeroes64(priorities);
- size += levels[current_priority].size();
- priorities &= ~(1ULL << current_priority);
- }
- return size;
- }
-
- [[nodiscard]] bool empty() const {
- return used_priorities == 0;
- }
-
- [[nodiscard]] bool empty(u32 priority) const {
- return (used_priorities & (1ULL << priority)) == 0;
- }
-
- [[nodiscard]] u32 highest_priority_set(u32 max_priority = 0) const {
- const u64 priorities =
- max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1));
- return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities));
- }
-
- [[nodiscard]] u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
- const u64 priorities = min_priority >= Depth - 1
- ? used_priorities
- : (used_priorities & ((1ULL << (min_priority + 1)) - 1));
- return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities);
- }
-
- [[nodiscard]] const_iterator cbegin(u32 max_prio = 0) const {
- const u32 priority = highest_priority_set(max_prio);
- return priority == Depth ? cend()
- : const_iterator{*this, levels[priority].cbegin(), priority};
- }
- [[nodiscard]] const_iterator begin(u32 max_prio = 0) const {
- return cbegin(max_prio);
- }
- [[nodiscard]] iterator begin(u32 max_prio = 0) {
- const u32 priority = highest_priority_set(max_prio);
- return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
- }
-
- [[nodiscard]] const_iterator cend(u32 min_prio = Depth - 1) const {
- return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
- }
- [[nodiscard]] const_iterator end(u32 min_prio = Depth - 1) const {
- return cend(min_prio);
- }
- [[nodiscard]] iterator end(u32 min_prio = Depth - 1) {
- return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
- }
-
- [[nodiscard]] T& front(u32 max_priority = 0) {
- const u32 priority = highest_priority_set(max_priority);
- return levels[priority == Depth ? 0 : priority].front();
- }
- [[nodiscard]] const T& front(u32 max_priority = 0) const {
- const u32 priority = highest_priority_set(max_priority);
- return levels[priority == Depth ? 0 : priority].front();
- }
-
- [[nodiscard]] T& back(u32 min_priority = Depth - 1) {
- const u32 priority = lowest_priority_set(min_priority); // intended
- return levels[priority == Depth ? 63 : priority].back();
- }
- [[nodiscard]] const T& back(u32 min_priority = Depth - 1) const {
- const u32 priority = lowest_priority_set(min_priority); // intended
- return levels[priority == Depth ? 63 : priority].back();
- }
-
- void clear() {
- used_priorities = 0;
- for (std::size_t i = 0; i < Depth; i++) {
- levels[i].clear();
- }
- }
-
-private:
- using const_list_iterator = typename std::list<T>::const_iterator;
-
- static void ListShiftForward(std::list<T>& list, const std::size_t shift = 1) {
- if (shift >= list.size()) {
- return;
- }
-
- const auto begin_range = list.begin();
- const auto end_range = std::next(begin_range, shift);
- list.splice(list.end(), list, begin_range, end_range);
- }
-
- static void ListSplice(std::list<T>& in_list, const_list_iterator position,
- std::list<T>& out_list, const_list_iterator element) {
- in_list.splice(position, out_list, element);
- }
-
- [[nodiscard]] static const_list_iterator ListIterateTo(const std::list<T>& list,
- const T& element) {
- auto it = list.cbegin();
- while (it != list.cend() && *it != element) {
- ++it;
- }
- return it;
- }
-
- std::array<std::list<T>, Depth> levels;
- u64 used_priorities = 0;
-};
-
-} // namespace Common
diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp
new file mode 100644
index 000000000..d537517db
--- /dev/null
+++ b/src/common/nvidia_flags.cpp
@@ -0,0 +1,27 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <filesystem>
+#include <stdlib.h>
+
+#include <fmt/format.h>
+
+#include "common/file_util.h"
+#include "common/nvidia_flags.h"
+
+namespace Common {
+
+void ConfigureNvidiaEnvironmentFlags() {
+#ifdef _WIN32
+ const std::string shader_path = Common::FS::SanitizePath(
+ fmt::format("{}/nvidia/", Common::FS::GetUserPath(Common::FS::UserPath::ShaderDir)));
+ const std::string windows_path =
+ Common::FS::SanitizePath(shader_path, Common::FS::DirectorySeparator::BackwardSlash);
+ void(Common::FS::CreateFullPath(shader_path + '/'));
+ void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path).c_str()));
+ void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1"));
+#endif
+}
+
+} // namespace Common
diff --git a/src/common/nvidia_flags.h b/src/common/nvidia_flags.h
new file mode 100644
index 000000000..75a0233ac
--- /dev/null
+++ b/src/common/nvidia_flags.h
@@ -0,0 +1,10 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+namespace Common {
+
+/// Configure platform specific flags for Nvidia's driver
+void ConfigureNvidiaEnvironmentFlags();
+
+} // namespace Common
diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
index e5d3090d5..8fd8620fd 100644
--- a/src/common/page_table.cpp
+++ b/src/common/page_table.cpp
@@ -8,18 +8,12 @@ namespace Common {
PageTable::PageTable() = default;
-PageTable::~PageTable() = default;
+PageTable::~PageTable() noexcept = default;
-void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits,
- bool has_attribute) {
- const std::size_t num_page_table_entries{1ULL
- << (address_space_width_in_bits - page_size_in_bits)};
+void PageTable::Resize(size_t address_space_width_in_bits, size_t page_size_in_bits) {
+ const size_t num_page_table_entries{1ULL << (address_space_width_in_bits - page_size_in_bits)};
pointers.resize(num_page_table_entries);
backing_addr.resize(num_page_table_entries);
-
- if (has_attribute) {
- attributes.resize(num_page_table_entries);
- }
}
} // namespace Common
diff --git a/src/common/page_table.h b/src/common/page_table.h
index cf5eed780..61c5552e0 100644
--- a/src/common/page_table.h
+++ b/src/common/page_table.h
@@ -4,12 +4,10 @@
#pragma once
-#include <vector>
-
-#include <boost/icl/interval_map.hpp>
+#include <atomic>
+#include <tuple>
#include "common/common_types.h"
-#include "common/memory_hook.h"
#include "common/virtual_buffer.h"
namespace Common {
@@ -22,27 +20,6 @@ enum class PageType : u8 {
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
/// invalidation
RasterizerCachedMemory,
- /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
- Special,
- /// Page is allocated for use.
- Allocated,
-};
-
-struct SpecialRegion {
- enum class Type {
- DebugHook,
- IODevice,
- } type;
-
- MemoryHookPointer handler;
-
- [[nodiscard]] bool operator<(const SpecialRegion& other) const {
- return std::tie(type, handler) < std::tie(other.type, other.handler);
- }
-
- [[nodiscard]] bool operator==(const SpecialRegion& other) const {
- return std::tie(type, handler) == std::tie(other.type, other.handler);
- }
};
/**
@@ -50,27 +27,84 @@ struct SpecialRegion {
* mimics the way a real CPU page table works.
*/
struct PageTable {
+ /// Number of bits reserved for attribute tagging.
+ /// This can be at most the guaranteed alignment of the pointers in the page table.
+ static constexpr int ATTRIBUTE_BITS = 2;
+
+ /**
+ * Pair of host pointer and page type attribute.
+ * This uses the lower bits of a given pointer to store the attribute tag.
+ * Writing and reading the pointer attribute pair is guaranteed to be atomic for the same method
+ * call. In other words, they are guaranteed to be synchronized at all times.
+ */
+ class PageInfo {
+ public:
+ /// Returns the page pointer
+ [[nodiscard]] u8* Pointer() const noexcept {
+ return ExtractPointer(raw.load(std::memory_order_relaxed));
+ }
+
+ /// Returns the page type attribute
+ [[nodiscard]] PageType Type() const noexcept {
+ return ExtractType(raw.load(std::memory_order_relaxed));
+ }
+
+ /// Returns the page pointer and attribute pair, extracted from the same atomic read
+ [[nodiscard]] std::pair<u8*, PageType> PointerType() const noexcept {
+ const uintptr_t non_atomic_raw = raw.load(std::memory_order_relaxed);
+ return {ExtractPointer(non_atomic_raw), ExtractType(non_atomic_raw)};
+ }
+
+ /// Returns the raw representation of the page information.
+ /// Use ExtractPointer and ExtractType to unpack the value.
+ [[nodiscard]] uintptr_t Raw() const noexcept {
+ return raw.load(std::memory_order_relaxed);
+ }
+
+ /// Write a page pointer and type pair atomically
+ void Store(u8* pointer, PageType type) noexcept {
+ raw.store(reinterpret_cast<uintptr_t>(pointer) | static_cast<uintptr_t>(type));
+ }
+
+ /// Unpack a pointer from a page info raw representation
+ [[nodiscard]] static u8* ExtractPointer(uintptr_t raw) noexcept {
+ return reinterpret_cast<u8*>(raw & (~uintptr_t{0} << ATTRIBUTE_BITS));
+ }
+
+ /// Unpack a page type from a page info raw representation
+ [[nodiscard]] static PageType ExtractType(uintptr_t raw) noexcept {
+ return static_cast<PageType>(raw & ((uintptr_t{1} << ATTRIBUTE_BITS) - 1));
+ }
+
+ private:
+ std::atomic<uintptr_t> raw;
+ };
+
PageTable();
- ~PageTable();
+ ~PageTable() noexcept;
+
+ PageTable(const PageTable&) = delete;
+ PageTable& operator=(const PageTable&) = delete;
+
+ PageTable(PageTable&&) noexcept = default;
+ PageTable& operator=(PageTable&&) noexcept = default;
/**
- * Resizes the page table to be able to accomodate enough pages within
+ * Resizes the page table to be able to accommodate enough pages within
* a given address space.
*
* @param address_space_width_in_bits The address size width in bits.
+ * @param page_size_in_bits The page size in bits.
*/
- void Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits,
- bool has_attribute);
+ void Resize(size_t address_space_width_in_bits, size_t page_size_in_bits);
/**
* Vector of memory pointers backing each page. An entry can only be non-null if the
- * corresponding entry in the `attributes` vector is of type `Memory`.
+ * corresponding attribute element is of type `Memory`.
*/
- VirtualBuffer<u8*> pointers;
+ VirtualBuffer<PageInfo> pointers;
VirtualBuffer<u64> backing_addr;
-
- VirtualBuffer<PageType> attributes;
};
} // namespace Common
diff --git a/src/common/parent_of_member.h b/src/common/parent_of_member.h
new file mode 100644
index 000000000..d9a14529d
--- /dev/null
+++ b/src/common/parent_of_member.h
@@ -0,0 +1,191 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+
+namespace Common {
+namespace detail {
+template <typename T, size_t Size, size_t Align>
+struct TypedStorageImpl {
+ std::aligned_storage_t<Size, Align> storage_;
+};
+} // namespace detail
+
+template <typename T>
+using TypedStorage = detail::TypedStorageImpl<T, sizeof(T), alignof(T)>;
+
+template <typename T>
+static constexpr T* GetPointer(TypedStorage<T>& ts) {
+ return static_cast<T*>(static_cast<void*>(std::addressof(ts.storage_)));
+}
+
+template <typename T>
+static constexpr const T* GetPointer(const TypedStorage<T>& ts) {
+ return static_cast<const T*>(static_cast<const void*>(std::addressof(ts.storage_)));
+}
+
+namespace impl {
+
+template <size_t MaxDepth>
+struct OffsetOfUnionHolder {
+ template <typename ParentType, typename MemberType, size_t Offset>
+ union UnionImpl {
+ using PaddingMember = char;
+ static constexpr size_t GetOffset() {
+ return Offset;
+ }
+
+#pragma pack(push, 1)
+ struct {
+ PaddingMember padding[Offset];
+ MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1];
+ } data;
+#pragma pack(pop)
+ UnionImpl<ParentType, MemberType, Offset + 1> next_union;
+ };
+
+ template <typename ParentType, typename MemberType>
+ union UnionImpl<ParentType, MemberType, 0> {
+ static constexpr size_t GetOffset() {
+ return 0;
+ }
+
+ struct {
+ MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1];
+ } data;
+ UnionImpl<ParentType, MemberType, 1> next_union;
+ };
+
+ template <typename ParentType, typename MemberType>
+ union UnionImpl<ParentType, MemberType, MaxDepth> {};
+};
+
+template <typename ParentType, typename MemberType>
+struct OffsetOfCalculator {
+ using UnionHolder =
+ typename OffsetOfUnionHolder<sizeof(MemberType)>::template UnionImpl<ParentType, MemberType,
+ 0>;
+ union Union {
+ char c{};
+ UnionHolder first_union;
+ TypedStorage<ParentType> parent;
+
+ constexpr Union() : c() {}
+ };
+ static constexpr Union U = {};
+
+ static constexpr const MemberType* GetNextAddress(const MemberType* start,
+ const MemberType* target) {
+ while (start < target) {
+ start++;
+ }
+ return start;
+ }
+
+ static constexpr std::ptrdiff_t GetDifference(const MemberType* start,
+ const MemberType* target) {
+ return (target - start) * sizeof(MemberType);
+ }
+
+ template <typename CurUnion>
+ static constexpr std::ptrdiff_t OffsetOfImpl(MemberType ParentType::*member,
+ CurUnion& cur_union) {
+ constexpr size_t Offset = CurUnion::GetOffset();
+ const auto target = std::addressof(GetPointer(U.parent)->*member);
+ const auto start = std::addressof(cur_union.data.members[0]);
+ const auto next = GetNextAddress(start, target);
+
+ if (next != target) {
+ if constexpr (Offset < sizeof(MemberType) - 1) {
+ return OffsetOfImpl(member, cur_union.next_union);
+ } else {
+ UNREACHABLE();
+ }
+ }
+
+ return (next - start) * sizeof(MemberType) + Offset;
+ }
+
+ static constexpr std::ptrdiff_t OffsetOf(MemberType ParentType::*member) {
+ return OffsetOfImpl(member, U.first_union);
+ }
+};
+
+template <typename T>
+struct GetMemberPointerTraits;
+
+template <typename P, typename M>
+struct GetMemberPointerTraits<M P::*> {
+ using Parent = P;
+ using Member = M;
+};
+
+template <auto MemberPtr>
+using GetParentType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Parent;
+
+template <auto MemberPtr>
+using GetMemberType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Member;
+
+template <auto MemberPtr, typename RealParentType = GetParentType<MemberPtr>>
+static inline std::ptrdiff_t OffsetOf = [] {
+ using DeducedParentType = GetParentType<MemberPtr>;
+ using MemberType = GetMemberType<MemberPtr>;
+ static_assert(std::is_base_of<DeducedParentType, RealParentType>::value ||
+ std::is_same<RealParentType, DeducedParentType>::value);
+
+ return OffsetOfCalculator<RealParentType, MemberType>::OffsetOf(MemberPtr);
+}();
+
+} // namespace impl
+
+template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
+constexpr RealParentType& GetParentReference(impl::GetMemberType<MemberPtr>* member) {
+ std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>;
+ return *static_cast<RealParentType*>(
+ static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(member)) - Offset));
+}
+
+template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
+constexpr RealParentType const& GetParentReference(impl::GetMemberType<MemberPtr> const* member) {
+ std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>;
+ return *static_cast<const RealParentType*>(static_cast<const void*>(
+ static_cast<const uint8_t*>(static_cast<const void*>(member)) - Offset));
+}
+
+template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
+constexpr RealParentType* GetParentPointer(impl::GetMemberType<MemberPtr>* member) {
+ return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
+}
+
+template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
+constexpr RealParentType const* GetParentPointer(impl::GetMemberType<MemberPtr> const* member) {
+ return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
+}
+
+template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
+constexpr RealParentType& GetParentReference(impl::GetMemberType<MemberPtr>& member) {
+ return GetParentReference<MemberPtr, RealParentType>(std::addressof(member));
+}
+
+template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
+constexpr RealParentType const& GetParentReference(impl::GetMemberType<MemberPtr> const& member) {
+ return GetParentReference<MemberPtr, RealParentType>(std::addressof(member));
+}
+
+template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
+constexpr RealParentType* GetParentPointer(impl::GetMemberType<MemberPtr>& member) {
+ return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
+}
+
+template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
+constexpr RealParentType const* GetParentPointer(impl::GetMemberType<MemberPtr> const& member) {
+ return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
+}
+
+} // namespace Common
diff --git a/src/common/quaternion.h b/src/common/quaternion.h
index da44f35cd..4d0871eb4 100644
--- a/src/common/quaternion.h
+++ b/src/common/quaternion.h
@@ -36,6 +36,36 @@ public:
T length = std::sqrt(xyz.Length2() + w * w);
return {xyz / length, w / length};
}
+
+ [[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const {
+ const T x2 = xyz[0] * xyz[0];
+ const T y2 = xyz[1] * xyz[1];
+ const T z2 = xyz[2] * xyz[2];
+
+ const T xy = xyz[0] * xyz[1];
+ const T wz = w * xyz[2];
+ const T xz = xyz[0] * xyz[2];
+ const T wy = w * xyz[1];
+ const T yz = xyz[1] * xyz[2];
+ const T wx = w * xyz[0];
+
+ return {1.0f - 2.0f * (y2 + z2),
+ 2.0f * (xy + wz),
+ 2.0f * (xz - wy),
+ 0.0f,
+ 2.0f * (xy - wz),
+ 1.0f - 2.0f * (x2 + z2),
+ 2.0f * (yz + wx),
+ 0.0f,
+ 2.0f * (xz + wy),
+ 2.0f * (yz - wx),
+ 1.0f - 2.0f * (x2 + y2),
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f};
+ }
};
template <typename T>
diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h
index 138fa0131..4a8d09806 100644
--- a/src/common/ring_buffer.h
+++ b/src/common/ring_buffer.h
@@ -19,15 +19,14 @@ namespace Common {
/// SPSC ring buffer
/// @tparam T Element type
/// @tparam capacity Number of slots in ring buffer
-/// @tparam granularity Slot size in terms of number of elements
-template <typename T, std::size_t capacity, std::size_t granularity = 1>
+template <typename T, std::size_t capacity>
class RingBuffer {
- /// A "slot" is made of `granularity` elements of `T`.
- static constexpr std::size_t slot_size = granularity * sizeof(T);
+ /// A "slot" is made of a single `T`.
+ static constexpr std::size_t slot_size = sizeof(T);
// T must be safely memcpy-able and have a trivial default constructor.
static_assert(std::is_trivial_v<T>);
// Ensure capacity is sensible.
- static_assert(capacity < std::numeric_limits<std::size_t>::max() / 2 / granularity);
+ static_assert(capacity < std::numeric_limits<std::size_t>::max() / 2);
static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two");
// Ensure lock-free.
static_assert(std::atomic_size_t::is_always_lock_free);
@@ -47,7 +46,7 @@ public:
const std::size_t second_copy = push_count - first_copy;
const char* in = static_cast<const char*>(new_slots);
- std::memcpy(m_data.data() + pos * granularity, in, first_copy * slot_size);
+ std::memcpy(m_data.data() + pos, in, first_copy * slot_size);
in += first_copy * slot_size;
std::memcpy(m_data.data(), in, second_copy * slot_size);
@@ -74,7 +73,7 @@ public:
const std::size_t second_copy = pop_count - first_copy;
char* out = static_cast<char*>(output);
- std::memcpy(out, m_data.data() + pos * granularity, first_copy * slot_size);
+ std::memcpy(out, m_data.data() + pos, first_copy * slot_size);
out += first_copy * slot_size;
std::memcpy(out, m_data.data(), second_copy * slot_size);
@@ -84,9 +83,9 @@ public:
}
std::vector<T> Pop(std::size_t max_slots = ~std::size_t(0)) {
- std::vector<T> out(std::min(max_slots, capacity) * granularity);
- const std::size_t count = Pop(out.data(), out.size() / granularity);
- out.resize(count * granularity);
+ std::vector<T> out(std::min(max_slots, capacity));
+ const std::size_t count = Pop(out.data(), out.size());
+ out.resize(count);
return out;
}
@@ -113,7 +112,7 @@ private:
alignas(128) std::atomic_size_t m_write_index{0};
#endif
- std::array<T, granularity * capacity> m_data;
+ std::array<T, capacity> m_data;
};
} // namespace Common
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index 68ef5f197..35dac3a8f 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -10,7 +10,7 @@
namespace detail {
template <typename Func>
struct ScopeExitHelper {
- explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {}
+ explicit ScopeExitHelper(Func&& func_) : func(std::move(func_)) {}
~ScopeExitHelper() {
if (active) {
func();
@@ -49,3 +49,9 @@ ScopeExitHelper<Func> ScopeExit(Func&& func) {
* \endcode
*/
#define SCOPE_EXIT(body) auto CONCAT2(scope_exit_helper_, __LINE__) = detail::ScopeExit([&]() body)
+
+/**
+ * This macro is similar to SCOPE_EXIT, except the object is caller managed. This is intended to be
+ * used when the caller might want to cancel the ScopeExit.
+ */
+#define SCOPE_GUARD(body) detail::ScopeExit([&]() body)
diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h
index 4f946a258..06ac2f5bb 100644
--- a/src/common/spin_lock.h
+++ b/src/common/spin_lock.h
@@ -15,6 +15,14 @@ namespace Common {
*/
class SpinLock {
public:
+ SpinLock() = default;
+
+ SpinLock(const SpinLock&) = delete;
+ SpinLock& operator=(const SpinLock&) = delete;
+
+ SpinLock(SpinLock&&) = delete;
+ SpinLock& operator=(SpinLock&&) = delete;
+
void lock();
void unlock();
[[nodiscard]] bool try_lock();
diff --git a/src/common/stream.cpp b/src/common/stream.cpp
new file mode 100644
index 000000000..bf0496c26
--- /dev/null
+++ b/src/common/stream.cpp
@@ -0,0 +1,47 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <stdexcept>
+#include "common/common_types.h"
+#include "common/stream.h"
+
+namespace Common {
+
+Stream::Stream() = default;
+Stream::~Stream() = default;
+
+void Stream::Seek(s32 offset, SeekOrigin origin) {
+ if (origin == SeekOrigin::SetOrigin) {
+ if (offset < 0) {
+ position = 0;
+ } else if (position >= buffer.size()) {
+ position = buffer.size();
+ } else {
+ position = offset;
+ }
+ } else if (origin == SeekOrigin::FromCurrentPos) {
+ Seek(static_cast<s32>(position) + offset, SeekOrigin::SetOrigin);
+ } else if (origin == SeekOrigin::FromEnd) {
+ Seek(static_cast<s32>(buffer.size()) - offset, SeekOrigin::SetOrigin);
+ }
+}
+
+u8 Stream::ReadByte() {
+ if (position < buffer.size()) {
+ return buffer[position++];
+ } else {
+ throw std::out_of_range("Attempting to read a byte not within the buffer range");
+ }
+}
+
+void Stream::WriteByte(u8 byte) {
+ if (position == buffer.size()) {
+ buffer.push_back(byte);
+ position++;
+ } else {
+ buffer.insert(buffer.begin() + position, byte);
+ }
+}
+
+} // namespace Common
diff --git a/src/common/stream.h b/src/common/stream.h
new file mode 100644
index 000000000..0e40692de
--- /dev/null
+++ b/src/common/stream.h
@@ -0,0 +1,56 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+
+namespace Common {
+
+enum class SeekOrigin {
+ SetOrigin,
+ FromCurrentPos,
+ FromEnd,
+};
+
+class Stream {
+public:
+ /// Stream creates a bitstream and provides common functionality on the stream.
+ explicit Stream();
+ ~Stream();
+
+ Stream(const Stream&) = delete;
+ Stream& operator=(const Stream&) = delete;
+
+ Stream(Stream&&) = default;
+ Stream& operator=(Stream&&) = default;
+
+ /// Reposition bitstream "cursor" to the specified offset from origin
+ void Seek(s32 offset, SeekOrigin origin);
+
+ /// Reads next byte in the stream buffer and increments position
+ u8 ReadByte();
+
+ /// Writes byte at current position
+ void WriteByte(u8 byte);
+
+ [[nodiscard]] std::size_t GetPosition() const {
+ return position;
+ }
+
+ [[nodiscard]] std::vector<u8>& GetBuffer() {
+ return buffer;
+ }
+
+ [[nodiscard]] const std::vector<u8>& GetBuffer() const {
+ return buffer;
+ }
+
+private:
+ std::vector<u8> buffer;
+ std::size_t position{0};
+};
+
+} // namespace Common
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index 84883a1d3..7b614ad89 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -8,6 +8,7 @@
#include <cstdlib>
#include <locale>
#include <sstream>
+
#include "common/common_paths.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -21,14 +22,14 @@ namespace Common {
/// Make a string lowercase
std::string ToLower(std::string str) {
std::transform(str.begin(), str.end(), str.begin(),
- [](unsigned char c) { return std::tolower(c); });
+ [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return str;
}
/// Make a string uppercase
std::string ToUpper(std::string str) {
std::transform(str.begin(), str.end(), str.begin(),
- [](unsigned char c) { return std::toupper(c); });
+ [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
return str;
}
@@ -140,27 +141,13 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
}
std::string UTF16ToUTF8(const std::u16string& input) {
-#ifdef _MSC_VER
- // Workaround for missing char16_t/char32_t instantiations in MSVC2017
- std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
- std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend());
- return convert.to_bytes(tmp_buffer);
-#else
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
return convert.to_bytes(input);
-#endif
}
std::u16string UTF8ToUTF16(const std::string& input) {
-#ifdef _MSC_VER
- // Workaround for missing char16_t/char32_t instantiations in MSVC2017
- std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
- auto tmp_buffer = convert.from_bytes(input);
- return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend());
-#else
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
return convert.from_bytes(input);
-#endif
}
#ifdef _WIN32
diff --git a/src/common/swap.h b/src/common/swap.h
index 7665942a2..a80e191dc 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -394,7 +394,7 @@ public:
template <typename S, typename T2, typename F2>
friend S operator%(const S& p, const swapped_t v);
- // Arithmetics + assignements
+ // Arithmetics + assignments
template <typename S, typename T2, typename F2>
friend S operator+=(const S& p, const swapped_t v);
@@ -451,7 +451,7 @@ S operator%(const S& i, const swap_struct_t<T, F> v) {
return i % v.swap();
}
-// Arithmetics + assignements
+// Arithmetics + assignments
template <typename S, typename T, typename F>
S& operator+=(S& i, const swap_struct_t<T, F> v) {
i += v.swap();
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index a50c5d1de..49186e848 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -52,8 +52,8 @@ public:
template <typename T>
class Field : public FieldInterface {
public:
- Field(FieldType type, std::string name, T value)
- : name(std::move(name)), type(type), value(std::move(value)) {}
+ Field(FieldType type_, std::string name_, T value_)
+ : name(std::move(name_)), type(type_), value(std::move(value_)) {}
Field(const Field&) = default;
Field& operator=(const Field&) = default;
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index 8e5935e6a..d2c1ac60d 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/common_funcs.h"
+#include "common/logging/log.h"
#include "common/thread.h"
#ifdef __APPLE__
#include <mach/mach.h>
@@ -19,6 +21,8 @@
#include <unistd.h>
#endif
+#include <string>
+
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
#endif
@@ -110,6 +114,14 @@ void SetCurrentThreadName(const char* name) {
pthread_set_name_np(pthread_self(), name);
#elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void*)name);
+#elif defined(__linux__)
+ // Linux limits thread names to 15 characters and will outright reject any
+ // attempt to set a longer name with ERANGE.
+ std::string truncated(name, std::min(strlen(name), static_cast<size_t>(15)));
+ if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) {
+ errno = e;
+ LOG_ERROR(Common, "Failed to set thread name to '{}': {}", truncated, GetLastErrorMsg());
+ }
#else
pthread_setname_np(pthread_self(), name);
#endif
diff --git a/src/common/thread.h b/src/common/thread.h
index 52b359413..a8c17c71a 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -4,6 +4,7 @@
#pragma once
+#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
@@ -25,13 +26,13 @@ public:
void Wait() {
std::unique_lock lk{mutex};
- condvar.wait(lk, [&] { return is_set; });
+ condvar.wait(lk, [&] { return is_set.load(); });
is_set = false;
}
bool WaitFor(const std::chrono::nanoseconds& time) {
std::unique_lock lk{mutex};
- if (!condvar.wait_for(lk, time, [this] { return is_set; }))
+ if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
@@ -40,7 +41,7 @@ public:
template <class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
std::unique_lock lk{mutex};
- if (!condvar.wait_until(lk, time, [this] { return is_set; }))
+ if (!condvar.wait_until(lk, time, [this] { return is_set.load(); }))
return false;
is_set = false;
return true;
@@ -54,9 +55,9 @@ public:
}
private:
- bool is_set = false;
std::condition_variable condvar;
std::mutex mutex;
+ std::atomic_bool is_set{false};
};
class Barrier {
diff --git a/src/common/thread_worker.cpp b/src/common/thread_worker.cpp
new file mode 100644
index 000000000..8f9bf447a
--- /dev/null
+++ b/src/common/thread_worker.cpp
@@ -0,0 +1,58 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/thread.h"
+#include "common/thread_worker.h"
+
+namespace Common {
+
+ThreadWorker::ThreadWorker(std::size_t num_workers, const std::string& name) {
+ for (std::size_t i = 0; i < num_workers; ++i)
+ threads.emplace_back([this, thread_name{std::string{name}}] {
+ Common::SetCurrentThreadName(thread_name.c_str());
+
+ // Wait for first request
+ {
+ std::unique_lock lock{queue_mutex};
+ condition.wait(lock, [this] { return stop || !requests.empty(); });
+ }
+
+ while (true) {
+ std::function<void()> task;
+
+ {
+ std::unique_lock lock{queue_mutex};
+ condition.wait(lock, [this] { return stop || !requests.empty(); });
+ if (stop || requests.empty()) {
+ return;
+ }
+ task = std::move(requests.front());
+ requests.pop();
+ }
+
+ task();
+ }
+ });
+}
+
+ThreadWorker::~ThreadWorker() {
+ {
+ std::unique_lock lock{queue_mutex};
+ stop = true;
+ }
+ condition.notify_all();
+ for (std::thread& thread : threads) {
+ thread.join();
+ }
+}
+
+void ThreadWorker::QueueWork(std::function<void()>&& work) {
+ {
+ std::unique_lock lock{queue_mutex};
+ requests.emplace(work);
+ }
+ condition.notify_one();
+}
+
+} // namespace Common
diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h
new file mode 100644
index 000000000..f1859971f
--- /dev/null
+++ b/src/common/thread_worker.h
@@ -0,0 +1,30 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <functional>
+#include <mutex>
+#include <string>
+#include <vector>
+#include <queue>
+
+namespace Common {
+
+class ThreadWorker final {
+public:
+ explicit ThreadWorker(std::size_t num_workers, const std::string& name);
+ ~ThreadWorker();
+ void QueueWork(std::function<void()>&& work);
+
+private:
+ std::vector<std::thread> threads;
+ std::queue<std::function<void()>> requests;
+ std::mutex queue_mutex;
+ std::condition_variable condition;
+ std::atomic_bool stop{};
+};
+
+} // namespace Common
diff --git a/src/common/timer.cpp b/src/common/timer.cpp
deleted file mode 100644
index 2dc15e434..000000000
--- a/src/common/timer.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <ctime>
-#include <fmt/format.h>
-#include "common/common_types.h"
-#include "common/string_util.h"
-#include "common/timer.h"
-
-namespace Common {
-
-std::chrono::milliseconds Timer::GetTimeMs() {
- return std::chrono::duration_cast<std::chrono::milliseconds>(
- std::chrono::system_clock::now().time_since_epoch());
-}
-
-// --------------------------------------------
-// Initiate, Start, Stop, and Update the time
-// --------------------------------------------
-
-// Set initial values for the class
-Timer::Timer() : m_LastTime(0), m_StartTime(0), m_Running(false) {
- Update();
-}
-
-// Write the starting time
-void Timer::Start() {
- m_StartTime = GetTimeMs();
- m_Running = true;
-}
-
-// Stop the timer
-void Timer::Stop() {
- // Write the final time
- m_LastTime = GetTimeMs();
- m_Running = false;
-}
-
-// Update the last time variable
-void Timer::Update() {
- m_LastTime = GetTimeMs();
- // TODO(ector) - QPF
-}
-
-// -------------------------------------
-// Get time difference and elapsed time
-// -------------------------------------
-
-// Get the number of milliseconds since the last Update()
-std::chrono::milliseconds Timer::GetTimeDifference() {
- return GetTimeMs() - m_LastTime;
-}
-
-// Add the time difference since the last Update() to the starting time.
-// This is used to compensate for a paused game.
-void Timer::AddTimeDifference() {
- m_StartTime += GetTimeDifference();
-}
-
-// Get the time elapsed since the Start()
-std::chrono::milliseconds Timer::GetTimeElapsed() {
- // If we have not started yet, return 1 (because then I don't
- // have to change the FPS calculation in CoreRerecording.cpp .
- if (m_StartTime.count() == 0)
- return std::chrono::milliseconds(1);
-
- // Return the final timer time if the timer is stopped
- if (!m_Running)
- return (m_LastTime - m_StartTime);
-
- return (GetTimeMs() - m_StartTime);
-}
-
-// Get the formatted time elapsed since the Start()
-std::string Timer::GetTimeElapsedFormatted() const {
- // If we have not started yet, return zero
- if (m_StartTime.count() == 0)
- return "00:00:00:000";
-
- // The number of milliseconds since the start.
- // Use a different value if the timer is stopped.
- std::chrono::milliseconds Milliseconds;
- if (m_Running)
- Milliseconds = GetTimeMs() - m_StartTime;
- else
- Milliseconds = m_LastTime - m_StartTime;
- // Seconds
- std::chrono::seconds Seconds = std::chrono::duration_cast<std::chrono::seconds>(Milliseconds);
- // Minutes
- std::chrono::minutes Minutes = std::chrono::duration_cast<std::chrono::minutes>(Milliseconds);
- // Hours
- std::chrono::hours Hours = std::chrono::duration_cast<std::chrono::hours>(Milliseconds);
-
- std::string TmpStr = fmt::format("{:02}:{:02}:{:02}:{:03}", Hours.count(), Minutes.count() % 60,
- Seconds.count() % 60, Milliseconds.count() % 1000);
- return TmpStr;
-}
-
-// Get the number of seconds since January 1 1970
-std::chrono::seconds Timer::GetTimeSinceJan1970() {
- return std::chrono::duration_cast<std::chrono::seconds>(GetTimeMs());
-}
-
-std::chrono::seconds Timer::GetLocalTimeSinceJan1970() {
- time_t sysTime, tzDiff, tzDST;
- struct tm* gmTime;
-
- time(&sysTime);
-
- // Account for DST where needed
- gmTime = localtime(&sysTime);
- if (gmTime->tm_isdst == 1)
- tzDST = 3600;
- else
- tzDST = 0;
-
- // Lazy way to get local time in sec
- gmTime = gmtime(&sysTime);
- tzDiff = sysTime - mktime(gmTime);
-
- return std::chrono::seconds(sysTime + tzDiff + tzDST);
-}
-
-// Return the current time formatted as Minutes:Seconds:Milliseconds
-// in the form 00:00:000.
-std::string Timer::GetTimeFormatted() {
- time_t sysTime;
- struct tm* gmTime;
- char tmp[13];
-
- time(&sysTime);
- gmTime = localtime(&sysTime);
-
- strftime(tmp, 6, "%M:%S", gmTime);
-
- u64 milliseconds = static_cast<u64>(GetTimeMs().count()) % 1000;
- return fmt::format("{}:{:03}", tmp, milliseconds);
-}
-
-// Returns a timestamp with decimals for precise time comparisons
-// ----------------
-double Timer::GetDoubleTime() {
- // Get continuous timestamp
- u64 TmpSeconds = static_cast<u64>(Common::Timer::GetTimeSinceJan1970().count());
- double ms = static_cast<u64>(GetTimeMs().count()) % 1000;
-
- // Remove a few years. We only really want enough seconds to make
- // sure that we are detecting actual actions, perhaps 60 seconds is
- // enough really, but I leave a year of seconds anyway, in case the
- // user's clock is incorrect or something like that.
- TmpSeconds = TmpSeconds - (38 * 365 * 24 * 60 * 60);
-
- // Make a smaller integer that fits in the double
- u32 Seconds = static_cast<u32>(TmpSeconds);
- double TmpTime = Seconds + ms;
-
- return TmpTime;
-}
-
-} // Namespace Common
diff --git a/src/common/timer.h b/src/common/timer.h
deleted file mode 100644
index 8894a143d..000000000
--- a/src/common/timer.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <chrono>
-#include <string>
-#include "common/common_types.h"
-
-namespace Common {
-class Timer {
-public:
- Timer();
-
- void Start();
- void Stop();
- void Update();
-
- // The time difference is always returned in milliseconds, regardless of alternative internal
- // representation
- [[nodiscard]] std::chrono::milliseconds GetTimeDifference();
- void AddTimeDifference();
-
- [[nodiscard]] static std::chrono::seconds GetTimeSinceJan1970();
- [[nodiscard]] static std::chrono::seconds GetLocalTimeSinceJan1970();
- [[nodiscard]] static double GetDoubleTime();
-
- [[nodiscard]] static std::string GetTimeFormatted();
- [[nodiscard]] std::string GetTimeElapsedFormatted() const;
- [[nodiscard]] std::chrono::milliseconds GetTimeElapsed();
-
- [[nodiscard]] static std::chrono::milliseconds GetTimeMs();
-
-private:
- std::chrono::milliseconds m_LastTime;
- std::chrono::milliseconds m_StartTime;
- bool m_Running;
-};
-
-} // Namespace Common
diff --git a/src/common/tree.h b/src/common/tree.h
new file mode 100644
index 000000000..3da49e422
--- /dev/null
+++ b/src/common/tree.h
@@ -0,0 +1,674 @@
+/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */
+/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */
+/* $FreeBSD$ */
+
+/*-
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+/*
+ * This file defines data structures for red-black trees.
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute. It fulfills a set of conditions:
+ * - every search path from the root to a leaf consists of the
+ * same number of black nodes,
+ * - each red node (except for the root) has a black parent,
+ * - each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+namespace Common {
+template <typename T>
+class RBHead {
+public:
+ [[nodiscard]] T* Root() {
+ return rbh_root;
+ }
+
+ [[nodiscard]] const T* Root() const {
+ return rbh_root;
+ }
+
+ void SetRoot(T* root) {
+ rbh_root = root;
+ }
+
+ [[nodiscard]] bool IsEmpty() const {
+ return Root() == nullptr;
+ }
+
+private:
+ T* rbh_root = nullptr;
+};
+
+enum class EntryColor {
+ Black,
+ Red,
+};
+
+template <typename T>
+class RBEntry {
+public:
+ [[nodiscard]] T* Left() {
+ return rbe_left;
+ }
+
+ [[nodiscard]] const T* Left() const {
+ return rbe_left;
+ }
+
+ void SetLeft(T* left) {
+ rbe_left = left;
+ }
+
+ [[nodiscard]] T* Right() {
+ return rbe_right;
+ }
+
+ [[nodiscard]] const T* Right() const {
+ return rbe_right;
+ }
+
+ void SetRight(T* right) {
+ rbe_right = right;
+ }
+
+ [[nodiscard]] T* Parent() {
+ return rbe_parent;
+ }
+
+ [[nodiscard]] const T* Parent() const {
+ return rbe_parent;
+ }
+
+ void SetParent(T* parent) {
+ rbe_parent = parent;
+ }
+
+ [[nodiscard]] bool IsBlack() const {
+ return rbe_color == EntryColor::Black;
+ }
+
+ [[nodiscard]] bool IsRed() const {
+ return rbe_color == EntryColor::Red;
+ }
+
+ [[nodiscard]] EntryColor Color() const {
+ return rbe_color;
+ }
+
+ void SetColor(EntryColor color) {
+ rbe_color = color;
+ }
+
+private:
+ T* rbe_left = nullptr;
+ T* rbe_right = nullptr;
+ T* rbe_parent = nullptr;
+ EntryColor rbe_color{};
+};
+
+template <typename Node>
+[[nodiscard]] RBEntry<Node>& RB_ENTRY(Node* node) {
+ return node->GetEntry();
+}
+
+template <typename Node>
+[[nodiscard]] const RBEntry<Node>& RB_ENTRY(const Node* node) {
+ return node->GetEntry();
+}
+
+template <typename Node>
+[[nodiscard]] Node* RB_PARENT(Node* node) {
+ return RB_ENTRY(node).Parent();
+}
+
+template <typename Node>
+[[nodiscard]] const Node* RB_PARENT(const Node* node) {
+ return RB_ENTRY(node).Parent();
+}
+
+template <typename Node>
+void RB_SET_PARENT(Node* node, Node* parent) {
+ return RB_ENTRY(node).SetParent(parent);
+}
+
+template <typename Node>
+[[nodiscard]] Node* RB_LEFT(Node* node) {
+ return RB_ENTRY(node).Left();
+}
+
+template <typename Node>
+[[nodiscard]] const Node* RB_LEFT(const Node* node) {
+ return RB_ENTRY(node).Left();
+}
+
+template <typename Node>
+void RB_SET_LEFT(Node* node, Node* left) {
+ return RB_ENTRY(node).SetLeft(left);
+}
+
+template <typename Node>
+[[nodiscard]] Node* RB_RIGHT(Node* node) {
+ return RB_ENTRY(node).Right();
+}
+
+template <typename Node>
+[[nodiscard]] const Node* RB_RIGHT(const Node* node) {
+ return RB_ENTRY(node).Right();
+}
+
+template <typename Node>
+void RB_SET_RIGHT(Node* node, Node* right) {
+ return RB_ENTRY(node).SetRight(right);
+}
+
+template <typename Node>
+[[nodiscard]] bool RB_IS_BLACK(const Node* node) {
+ return RB_ENTRY(node).IsBlack();
+}
+
+template <typename Node>
+[[nodiscard]] bool RB_IS_RED(const Node* node) {
+ return RB_ENTRY(node).IsRed();
+}
+
+template <typename Node>
+[[nodiscard]] EntryColor RB_COLOR(const Node* node) {
+ return RB_ENTRY(node).Color();
+}
+
+template <typename Node>
+void RB_SET_COLOR(Node* node, EntryColor color) {
+ return RB_ENTRY(node).SetColor(color);
+}
+
+template <typename Node>
+void RB_SET(Node* node, Node* parent) {
+ auto& entry = RB_ENTRY(node);
+ entry.SetParent(parent);
+ entry.SetLeft(nullptr);
+ entry.SetRight(nullptr);
+ entry.SetColor(EntryColor::Red);
+}
+
+template <typename Node>
+void RB_SET_BLACKRED(Node* black, Node* red) {
+ RB_SET_COLOR(black, EntryColor::Black);
+ RB_SET_COLOR(red, EntryColor::Red);
+}
+
+template <typename Node>
+void RB_ROTATE_LEFT(RBHead<Node>* head, Node* elm, Node*& tmp) {
+ tmp = RB_RIGHT(elm);
+ RB_SET_RIGHT(elm, RB_LEFT(tmp));
+ if (RB_RIGHT(elm) != nullptr) {
+ RB_SET_PARENT(RB_LEFT(tmp), elm);
+ }
+
+ RB_SET_PARENT(tmp, RB_PARENT(elm));
+ if (RB_PARENT(tmp) != nullptr) {
+ if (elm == RB_LEFT(RB_PARENT(elm))) {
+ RB_SET_LEFT(RB_PARENT(elm), tmp);
+ } else {
+ RB_SET_RIGHT(RB_PARENT(elm), tmp);
+ }
+ } else {
+ head->SetRoot(tmp);
+ }
+
+ RB_SET_LEFT(tmp, elm);
+ RB_SET_PARENT(elm, tmp);
+}
+
+template <typename Node>
+void RB_ROTATE_RIGHT(RBHead<Node>* head, Node* elm, Node*& tmp) {
+ tmp = RB_LEFT(elm);
+ RB_SET_LEFT(elm, RB_RIGHT(tmp));
+ if (RB_LEFT(elm) != nullptr) {
+ RB_SET_PARENT(RB_RIGHT(tmp), elm);
+ }
+
+ RB_SET_PARENT(tmp, RB_PARENT(elm));
+ if (RB_PARENT(tmp) != nullptr) {
+ if (elm == RB_LEFT(RB_PARENT(elm))) {
+ RB_SET_LEFT(RB_PARENT(elm), tmp);
+ } else {
+ RB_SET_RIGHT(RB_PARENT(elm), tmp);
+ }
+ } else {
+ head->SetRoot(tmp);
+ }
+
+ RB_SET_RIGHT(tmp, elm);
+ RB_SET_PARENT(elm, tmp);
+}
+
+template <typename Node>
+void RB_INSERT_COLOR(RBHead<Node>* head, Node* elm) {
+ Node* parent = nullptr;
+ Node* tmp = nullptr;
+
+ while ((parent = RB_PARENT(elm)) != nullptr && RB_IS_RED(parent)) {
+ Node* gparent = RB_PARENT(parent);
+ if (parent == RB_LEFT(gparent)) {
+ tmp = RB_RIGHT(gparent);
+ if (tmp && RB_IS_RED(tmp)) {
+ RB_SET_COLOR(tmp, EntryColor::Black);
+ RB_SET_BLACKRED(parent, gparent);
+ elm = gparent;
+ continue;
+ }
+
+ if (RB_RIGHT(parent) == elm) {
+ RB_ROTATE_LEFT(head, parent, tmp);
+ tmp = parent;
+ parent = elm;
+ elm = tmp;
+ }
+
+ RB_SET_BLACKRED(parent, gparent);
+ RB_ROTATE_RIGHT(head, gparent, tmp);
+ } else {
+ tmp = RB_LEFT(gparent);
+ if (tmp && RB_IS_RED(tmp)) {
+ RB_SET_COLOR(tmp, EntryColor::Black);
+ RB_SET_BLACKRED(parent, gparent);
+ elm = gparent;
+ continue;
+ }
+
+ if (RB_LEFT(parent) == elm) {
+ RB_ROTATE_RIGHT(head, parent, tmp);
+ tmp = parent;
+ parent = elm;
+ elm = tmp;
+ }
+
+ RB_SET_BLACKRED(parent, gparent);
+ RB_ROTATE_LEFT(head, gparent, tmp);
+ }
+ }
+
+ RB_SET_COLOR(head->Root(), EntryColor::Black);
+}
+
+template <typename Node>
+void RB_REMOVE_COLOR(RBHead<Node>* head, Node* parent, Node* elm) {
+ Node* tmp;
+ while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head->Root()) {
+ if (RB_LEFT(parent) == elm) {
+ tmp = RB_RIGHT(parent);
+ if (RB_IS_RED(tmp)) {
+ RB_SET_BLACKRED(tmp, parent);
+ RB_ROTATE_LEFT(head, parent, tmp);
+ tmp = RB_RIGHT(parent);
+ }
+
+ if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) &&
+ (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) {
+ RB_SET_COLOR(tmp, EntryColor::Red);
+ elm = parent;
+ parent = RB_PARENT(elm);
+ } else {
+ if (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp))) {
+ Node* oleft;
+ if ((oleft = RB_LEFT(tmp)) != nullptr) {
+ RB_SET_COLOR(oleft, EntryColor::Black);
+ }
+
+ RB_SET_COLOR(tmp, EntryColor::Red);
+ RB_ROTATE_RIGHT(head, tmp, oleft);
+ tmp = RB_RIGHT(parent);
+ }
+
+ RB_SET_COLOR(tmp, RB_COLOR(parent));
+ RB_SET_COLOR(parent, EntryColor::Black);
+ if (RB_RIGHT(tmp)) {
+ RB_SET_COLOR(RB_RIGHT(tmp), EntryColor::Black);
+ }
+
+ RB_ROTATE_LEFT(head, parent, tmp);
+ elm = head->Root();
+ break;
+ }
+ } else {
+ tmp = RB_LEFT(parent);
+ if (RB_IS_RED(tmp)) {
+ RB_SET_BLACKRED(tmp, parent);
+ RB_ROTATE_RIGHT(head, parent, tmp);
+ tmp = RB_LEFT(parent);
+ }
+
+ if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) &&
+ (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) {
+ RB_SET_COLOR(tmp, EntryColor::Red);
+ elm = parent;
+ parent = RB_PARENT(elm);
+ } else {
+ if (RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) {
+ Node* oright;
+ if ((oright = RB_RIGHT(tmp)) != nullptr) {
+ RB_SET_COLOR(oright, EntryColor::Black);
+ }
+
+ RB_SET_COLOR(tmp, EntryColor::Red);
+ RB_ROTATE_LEFT(head, tmp, oright);
+ tmp = RB_LEFT(parent);
+ }
+
+ RB_SET_COLOR(tmp, RB_COLOR(parent));
+ RB_SET_COLOR(parent, EntryColor::Black);
+
+ if (RB_LEFT(tmp)) {
+ RB_SET_COLOR(RB_LEFT(tmp), EntryColor::Black);
+ }
+
+ RB_ROTATE_RIGHT(head, parent, tmp);
+ elm = head->Root();
+ break;
+ }
+ }
+ }
+
+ if (elm) {
+ RB_SET_COLOR(elm, EntryColor::Black);
+ }
+}
+
+template <typename Node>
+Node* RB_REMOVE(RBHead<Node>* head, Node* elm) {
+ Node* child = nullptr;
+ Node* parent = nullptr;
+ Node* old = elm;
+ EntryColor color{};
+
+ const auto finalize = [&] {
+ if (color == EntryColor::Black) {
+ RB_REMOVE_COLOR(head, parent, child);
+ }
+
+ return old;
+ };
+
+ if (RB_LEFT(elm) == nullptr) {
+ child = RB_RIGHT(elm);
+ } else if (RB_RIGHT(elm) == nullptr) {
+ child = RB_LEFT(elm);
+ } else {
+ Node* left;
+ elm = RB_RIGHT(elm);
+ while ((left = RB_LEFT(elm)) != nullptr) {
+ elm = left;
+ }
+
+ child = RB_RIGHT(elm);
+ parent = RB_PARENT(elm);
+ color = RB_COLOR(elm);
+
+ if (child) {
+ RB_SET_PARENT(child, parent);
+ }
+ if (parent) {
+ if (RB_LEFT(parent) == elm) {
+ RB_SET_LEFT(parent, child);
+ } else {
+ RB_SET_RIGHT(parent, child);
+ }
+ } else {
+ head->SetRoot(child);
+ }
+
+ if (RB_PARENT(elm) == old) {
+ parent = elm;
+ }
+
+ elm->SetEntry(old->GetEntry());
+
+ if (RB_PARENT(old)) {
+ if (RB_LEFT(RB_PARENT(old)) == old) {
+ RB_SET_LEFT(RB_PARENT(old), elm);
+ } else {
+ RB_SET_RIGHT(RB_PARENT(old), elm);
+ }
+ } else {
+ head->SetRoot(elm);
+ }
+ RB_SET_PARENT(RB_LEFT(old), elm);
+ if (RB_RIGHT(old)) {
+ RB_SET_PARENT(RB_RIGHT(old), elm);
+ }
+ if (parent) {
+ left = parent;
+ }
+
+ return finalize();
+ }
+
+ parent = RB_PARENT(elm);
+ color = RB_COLOR(elm);
+
+ if (child) {
+ RB_SET_PARENT(child, parent);
+ }
+ if (parent) {
+ if (RB_LEFT(parent) == elm) {
+ RB_SET_LEFT(parent, child);
+ } else {
+ RB_SET_RIGHT(parent, child);
+ }
+ } else {
+ head->SetRoot(child);
+ }
+
+ return finalize();
+}
+
+// Inserts a node into the RB tree
+template <typename Node, typename CompareFunction>
+Node* RB_INSERT(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
+ Node* parent = nullptr;
+ Node* tmp = head->Root();
+ int comp = 0;
+
+ while (tmp) {
+ parent = tmp;
+ comp = cmp(elm, parent);
+ if (comp < 0) {
+ tmp = RB_LEFT(tmp);
+ } else if (comp > 0) {
+ tmp = RB_RIGHT(tmp);
+ } else {
+ return tmp;
+ }
+ }
+
+ RB_SET(elm, parent);
+
+ if (parent != nullptr) {
+ if (comp < 0) {
+ RB_SET_LEFT(parent, elm);
+ } else {
+ RB_SET_RIGHT(parent, elm);
+ }
+ } else {
+ head->SetRoot(elm);
+ }
+
+ RB_INSERT_COLOR(head, elm);
+ return nullptr;
+}
+
+// Finds the node with the same key as elm
+template <typename Node, typename CompareFunction>
+Node* RB_FIND(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
+ Node* tmp = head->Root();
+
+ while (tmp) {
+ const int comp = cmp(elm, tmp);
+ if (comp < 0) {
+ tmp = RB_LEFT(tmp);
+ } else if (comp > 0) {
+ tmp = RB_RIGHT(tmp);
+ } else {
+ return tmp;
+ }
+ }
+
+ return nullptr;
+}
+
+// Finds the first node greater than or equal to the search key
+template <typename Node, typename CompareFunction>
+Node* RB_NFIND(RBHead<Node>* head, Node* elm, CompareFunction cmp) {
+ Node* tmp = head->Root();
+ Node* res = nullptr;
+
+ while (tmp) {
+ const int comp = cmp(elm, tmp);
+ if (comp < 0) {
+ res = tmp;
+ tmp = RB_LEFT(tmp);
+ } else if (comp > 0) {
+ tmp = RB_RIGHT(tmp);
+ } else {
+ return tmp;
+ }
+ }
+
+ return res;
+}
+
+// Finds the node with the same key as lelm
+template <typename Node, typename CompareFunction>
+Node* RB_FIND_LIGHT(RBHead<Node>* head, const void* lelm, CompareFunction lcmp) {
+ Node* tmp = head->Root();
+
+ while (tmp) {
+ const int comp = lcmp(lelm, tmp);
+ if (comp < 0) {
+ tmp = RB_LEFT(tmp);
+ } else if (comp > 0) {
+ tmp = RB_RIGHT(tmp);
+ } else {
+ return tmp;
+ }
+ }
+
+ return nullptr;
+}
+
+// Finds the first node greater than or equal to the search key
+template <typename Node, typename CompareFunction>
+Node* RB_NFIND_LIGHT(RBHead<Node>* head, const void* lelm, CompareFunction lcmp) {
+ Node* tmp = head->Root();
+ Node* res = nullptr;
+
+ while (tmp) {
+ const int comp = lcmp(lelm, tmp);
+ if (comp < 0) {
+ res = tmp;
+ tmp = RB_LEFT(tmp);
+ } else if (comp > 0) {
+ tmp = RB_RIGHT(tmp);
+ } else {
+ return tmp;
+ }
+ }
+
+ return res;
+}
+
+template <typename Node>
+Node* RB_NEXT(Node* elm) {
+ if (RB_RIGHT(elm)) {
+ elm = RB_RIGHT(elm);
+ while (RB_LEFT(elm)) {
+ elm = RB_LEFT(elm);
+ }
+ } else {
+ if (RB_PARENT(elm) && (elm == RB_LEFT(RB_PARENT(elm)))) {
+ elm = RB_PARENT(elm);
+ } else {
+ while (RB_PARENT(elm) && (elm == RB_RIGHT(RB_PARENT(elm)))) {
+ elm = RB_PARENT(elm);
+ }
+ elm = RB_PARENT(elm);
+ }
+ }
+ return elm;
+}
+
+template <typename Node>
+Node* RB_PREV(Node* elm) {
+ if (RB_LEFT(elm)) {
+ elm = RB_LEFT(elm);
+ while (RB_RIGHT(elm)) {
+ elm = RB_RIGHT(elm);
+ }
+ } else {
+ if (RB_PARENT(elm) && (elm == RB_RIGHT(RB_PARENT(elm)))) {
+ elm = RB_PARENT(elm);
+ } else {
+ while (RB_PARENT(elm) && (elm == RB_LEFT(RB_PARENT(elm)))) {
+ elm = RB_PARENT(elm);
+ }
+ elm = RB_PARENT(elm);
+ }
+ }
+ return elm;
+}
+
+template <typename Node>
+Node* RB_MINMAX(RBHead<Node>* head, bool is_min) {
+ Node* tmp = head->Root();
+ Node* parent = nullptr;
+
+ while (tmp) {
+ parent = tmp;
+ if (is_min) {
+ tmp = RB_LEFT(tmp);
+ } else {
+ tmp = RB_RIGHT(tmp);
+ }
+ }
+
+ return parent;
+}
+
+template <typename Node>
+Node* RB_MIN(RBHead<Node>* head) {
+ return RB_MINMAX(head, true);
+}
+
+template <typename Node>
+Node* RB_MAX(RBHead<Node>* head) {
+ return RB_MINMAX(head, false);
+}
+} // namespace Common
diff --git a/src/common/uuid.h b/src/common/uuid.h
index 4ab9a25f0..2e7a18405 100644
--- a/src/common/uuid.h
+++ b/src/common/uuid.h
@@ -14,8 +14,8 @@ constexpr u128 INVALID_UUID{{0, 0}};
struct UUID {
// UUIDs which are 0 are considered invalid!
- u128 uuid = INVALID_UUID;
- constexpr UUID() = default;
+ u128 uuid;
+ UUID() = default;
constexpr explicit UUID(const u128& id) : uuid{id} {}
constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
diff --git a/src/common/vector_math.h b/src/common/vector_math.h
index 2a0fcf541..22dba3c2d 100644
--- a/src/common/vector_math.h
+++ b/src/common/vector_math.h
@@ -87,7 +87,13 @@ public:
template <typename V>
[[nodiscard]] constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const {
- return {x * f, y * f};
+ using TV = decltype(T{} * V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
+ };
}
template <typename V>
@@ -98,7 +104,13 @@ public:
template <typename V>
[[nodiscard]] constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
- return {x / f, y / f};
+ using TV = decltype(T{} / V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
+ };
}
template <typename V>
@@ -168,7 +180,10 @@ public:
template <typename T, typename V>
[[nodiscard]] constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
- return Vec2<T>(f * vec.x, f * vec.y);
+ using C = std::common_type_t<T, V>;
+
+ return Vec2<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)),
+ static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y)));
}
using Vec2f = Vec2<float>;
@@ -237,7 +252,14 @@ public:
template <typename V>
[[nodiscard]] constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
- return {x * f, y * f, z * f};
+ using TV = decltype(T{} * V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)),
+ };
}
template <typename V>
@@ -247,7 +269,14 @@ public:
}
template <typename V>
[[nodiscard]] constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
- return {x / f, y / f, z / f};
+ using TV = decltype(T{} / V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)),
+ };
}
template <typename V>
@@ -367,7 +396,11 @@ public:
template <typename T, typename V>
[[nodiscard]] constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
- return Vec3<T>(f * vec.x, f * vec.y, f * vec.z);
+ using C = std::common_type_t<T, V>;
+
+ return Vec3<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)),
+ static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y)),
+ static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.z)));
}
template <>
@@ -446,7 +479,15 @@ public:
template <typename V>
[[nodiscard]] constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
- return {x * f, y * f, z * f, w * f};
+ using TV = decltype(T{} * V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(w) * static_cast<C>(f)),
+ };
}
template <typename V>
@@ -457,7 +498,15 @@ public:
template <typename V>
[[nodiscard]] constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
- return {x / f, y / f, z / f, w / f};
+ using TV = decltype(T{} / V{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)),
+ static_cast<TV>(static_cast<C>(w) / static_cast<C>(f)),
+ };
}
template <typename V>
@@ -582,7 +631,15 @@ public:
template <typename T, typename V>
[[nodiscard]] constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
- return {f * vec.x, f * vec.y, f * vec.z, f * vec.w};
+ using TV = decltype(V{} * T{});
+ using C = std::common_type_t<T, V>;
+
+ return {
+ static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.x)),
+ static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.y)),
+ static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.z)),
+ static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.w)),
+ };
}
using Vec4f = Vec4<float>;
diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp
index b009cb500..e3ca29258 100644
--- a/src/common/virtual_buffer.cpp
+++ b/src/common/virtual_buffer.cpp
@@ -13,7 +13,7 @@
namespace Common {
-void* AllocateMemoryPages(std::size_t size) {
+void* AllocateMemoryPages(std::size_t size) noexcept {
#ifdef _WIN32
void* base{VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE)};
#else
@@ -29,7 +29,7 @@ void* AllocateMemoryPages(std::size_t size) {
return base;
}
-void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) {
+void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
if (!base) {
return;
}
diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h
index 125cb42f0..fb1a6f81f 100644
--- a/src/common/virtual_buffer.h
+++ b/src/common/virtual_buffer.h
@@ -4,29 +4,55 @@
#pragma once
-#include "common/common_funcs.h"
+#include <type_traits>
+#include <utility>
namespace Common {
-void* AllocateMemoryPages(std::size_t size);
-void FreeMemoryPages(void* base, std::size_t size);
+void* AllocateMemoryPages(std::size_t size) noexcept;
+void FreeMemoryPages(void* base, std::size_t size) noexcept;
template <typename T>
-class VirtualBuffer final : NonCopyable {
+class VirtualBuffer final {
public:
+ // TODO: Uncomment this and change Common::PageTable::PageInfo to be trivially constructible
+ // using std::atomic_ref once libc++ has support for it
+ // static_assert(
+ // std::is_trivially_constructible_v<T>,
+ // "T must be trivially constructible, as non-trivial constructors will not be executed "
+ // "with the current allocator");
+
constexpr VirtualBuffer() = default;
explicit VirtualBuffer(std::size_t count) : alloc_size{count * sizeof(T)} {
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
}
- ~VirtualBuffer() {
+ ~VirtualBuffer() noexcept {
FreeMemoryPages(base_ptr, alloc_size);
}
+ VirtualBuffer(const VirtualBuffer&) = delete;
+ VirtualBuffer& operator=(const VirtualBuffer&) = delete;
+
+ VirtualBuffer(VirtualBuffer&& other) noexcept
+ : alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr),
+ nullptr} {}
+
+ VirtualBuffer& operator=(VirtualBuffer&& other) noexcept {
+ alloc_size = std::exchange(other.alloc_size, 0);
+ base_ptr = std::exchange(other.base_ptr, nullptr);
+ return *this;
+ }
+
void resize(std::size_t count) {
+ const auto new_size = count * sizeof(T);
+ if (new_size == alloc_size) {
+ return;
+ }
+
FreeMemoryPages(base_ptr, alloc_size);
- alloc_size = count * sizeof(T);
+ alloc_size = new_size;
base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size));
}
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index 3afbdb898..a8c143f85 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -15,10 +15,10 @@ namespace Common {
using base_timer = std::chrono::steady_clock;
using base_time_point = std::chrono::time_point<base_timer>;
-class StandardWallClock : public WallClock {
+class StandardWallClock final : public WallClock {
public:
- StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency)
- : WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) {
+ explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_)
+ : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, false) {
start_time = base_timer::now();
}
@@ -53,7 +53,7 @@ public:
return Common::Divide128On32(temporary, 1000000000).first;
}
- void Pause(bool is_paused) override {
+ void Pause([[maybe_unused]] bool is_paused) override {
// Do nothing in this clock type.
}
diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h
index 5db30083d..cef3e9499 100644
--- a/src/common/wall_clock.h
+++ b/src/common/wall_clock.h
@@ -13,6 +13,8 @@ namespace Common {
class WallClock {
public:
+ virtual ~WallClock() = default;
+
/// Returns current wall time in nanoseconds
[[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0;
@@ -36,9 +38,9 @@ public:
}
protected:
- WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native)
- : emulated_cpu_frequency{emulated_cpu_frequency},
- emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {}
+ explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_)
+ : emulated_cpu_frequency{emulated_cpu_frequency_},
+ emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {}
u64 emulated_cpu_frequency;
u64 emulated_clock_frequency;
diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp
index 424b39b1f..a65f6b832 100644
--- a/src/common/x64/native_clock.cpp
+++ b/src/common/x64/native_clock.cpp
@@ -2,19 +2,74 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
#include <chrono>
+#include <limits>
#include <mutex>
#include <thread>
#ifdef _MSC_VER
#include <intrin.h>
+
+#pragma intrinsic(__umulh)
+#pragma intrinsic(_udiv128)
#else
#include <x86intrin.h>
#endif
+#include "common/atomic_ops.h"
#include "common/uint128.h"
#include "common/x64/native_clock.h"
+namespace {
+
+[[nodiscard]] u64 GetFixedPoint64Factor(u64 numerator, u64 divisor) {
+#ifdef __SIZEOF_INT128__
+ const auto base = static_cast<unsigned __int128>(numerator) << 64ULL;
+ return static_cast<u64>(base / divisor);
+#elif defined(_M_X64) || defined(_M_ARM64)
+ std::array<u64, 2> r = {0, numerator};
+ u64 remainder;
+#if _MSC_VER < 1923
+ return udiv128(r[1], r[0], divisor, &remainder);
+#else
+ return _udiv128(r[1], r[0], divisor, &remainder);
+#endif
+#else
+ // This one is bit more inaccurate.
+ return MultiplyAndDivide64(std::numeric_limits<u64>::max(), numerator, divisor);
+#endif
+}
+
+[[nodiscard]] u64 MultiplyHigh(u64 a, u64 b) {
+#ifdef __SIZEOF_INT128__
+ return (static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b)) >> 64;
+#elif defined(_M_X64) || defined(_M_ARM64)
+ return __umulh(a, b); // MSVC
+#else
+ // Generic fallback
+ const u64 a_lo = u32(a);
+ const u64 a_hi = a >> 32;
+ const u64 b_lo = u32(b);
+ const u64 b_hi = b >> 32;
+
+ const u64 a_x_b_hi = a_hi * b_hi;
+ const u64 a_x_b_mid = a_hi * b_lo;
+ const u64 b_x_a_mid = b_hi * a_lo;
+ const u64 a_x_b_lo = a_lo * b_lo;
+
+ const u64 carry_bit = (static_cast<u64>(static_cast<u32>(a_x_b_mid)) +
+ static_cast<u64>(static_cast<u32>(b_x_a_mid)) + (a_x_b_lo >> 32)) >>
+ 32;
+
+ const u64 multhi = a_x_b_hi + (a_x_b_mid >> 32) + (b_x_a_mid >> 32) + carry_bit;
+
+ return multhi;
+#endif
+}
+
+} // namespace
+
namespace Common {
u64 EstimateRDTSCFrequency() {
@@ -43,59 +98,76 @@ u64 EstimateRDTSCFrequency() {
}
namespace X64 {
-NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency,
- u64 rtsc_frequency)
- : WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{
- rtsc_frequency} {
+NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
+ u64 rtsc_frequency_)
+ : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{
+ rtsc_frequency_} {
_mm_mfence();
- last_measure = __rdtsc();
- accumulated_ticks = 0U;
+ time_point.inner.last_measure = __rdtsc();
+ time_point.inner.accumulated_ticks = 0U;
+ ns_rtsc_factor = GetFixedPoint64Factor(1000000000, rtsc_frequency);
+ us_rtsc_factor = GetFixedPoint64Factor(1000000, rtsc_frequency);
+ ms_rtsc_factor = GetFixedPoint64Factor(1000, rtsc_frequency);
+ clock_rtsc_factor = GetFixedPoint64Factor(emulated_clock_frequency, rtsc_frequency);
+ cpu_rtsc_factor = GetFixedPoint64Factor(emulated_cpu_frequency, rtsc_frequency);
}
u64 NativeClock::GetRTSC() {
- std::scoped_lock scope{rtsc_serialize};
- _mm_mfence();
- const u64 current_measure = __rdtsc();
- u64 diff = current_measure - last_measure;
- diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
- if (current_measure > last_measure) {
- last_measure = current_measure;
- }
- accumulated_ticks += diff;
+ TimePoint new_time_point{};
+ TimePoint current_time_point{};
+ do {
+ current_time_point.pack = time_point.pack;
+ _mm_mfence();
+ const u64 current_measure = __rdtsc();
+ u64 diff = current_measure - current_time_point.inner.last_measure;
+ diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
+ new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure
+ ? current_measure
+ : current_time_point.inner.last_measure;
+ new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff;
+ } while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
+ current_time_point.pack));
/// The clock cannot be more precise than the guest timer, remove the lower bits
- return accumulated_ticks & inaccuracy_mask;
+ return new_time_point.inner.accumulated_ticks & inaccuracy_mask;
}
void NativeClock::Pause(bool is_paused) {
if (!is_paused) {
- _mm_mfence();
- last_measure = __rdtsc();
+ TimePoint current_time_point{};
+ TimePoint new_time_point{};
+ do {
+ current_time_point.pack = time_point.pack;
+ new_time_point.pack = current_time_point.pack;
+ _mm_mfence();
+ new_time_point.inner.last_measure = __rdtsc();
+ } while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
+ current_time_point.pack));
}
}
std::chrono::nanoseconds NativeClock::GetTimeNS() {
const u64 rtsc_value = GetRTSC();
- return std::chrono::nanoseconds{MultiplyAndDivide64(rtsc_value, 1000000000, rtsc_frequency)};
+ return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)};
}
std::chrono::microseconds NativeClock::GetTimeUS() {
const u64 rtsc_value = GetRTSC();
- return std::chrono::microseconds{MultiplyAndDivide64(rtsc_value, 1000000, rtsc_frequency)};
+ return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)};
}
std::chrono::milliseconds NativeClock::GetTimeMS() {
const u64 rtsc_value = GetRTSC();
- return std::chrono::milliseconds{MultiplyAndDivide64(rtsc_value, 1000, rtsc_frequency)};
+ return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)};
}
u64 NativeClock::GetClockCycles() {
const u64 rtsc_value = GetRTSC();
- return MultiplyAndDivide64(rtsc_value, emulated_clock_frequency, rtsc_frequency);
+ return MultiplyHigh(rtsc_value, clock_rtsc_factor);
}
u64 NativeClock::GetCPUCycles() {
const u64 rtsc_value = GetRTSC();
- return MultiplyAndDivide64(rtsc_value, emulated_cpu_frequency, rtsc_frequency);
+ return MultiplyHigh(rtsc_value, cpu_rtsc_factor);
}
} // namespace X64
diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h
index 891a3bbfd..7cbd400d2 100644
--- a/src/common/x64/native_clock.h
+++ b/src/common/x64/native_clock.h
@@ -6,15 +6,15 @@
#include <optional>
-#include "common/spin_lock.h"
#include "common/wall_clock.h"
namespace Common {
namespace X64 {
-class NativeClock : public WallClock {
+class NativeClock final : public WallClock {
public:
- NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency);
+ explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
+ u64 rtsc_frequency_);
std::chrono::nanoseconds GetTimeNS() override;
@@ -31,14 +31,28 @@ public:
private:
u64 GetRTSC();
+ union alignas(16) TimePoint {
+ TimePoint() : pack{} {}
+ u128 pack{};
+ struct Inner {
+ u64 last_measure{};
+ u64 accumulated_ticks{};
+ } inner;
+ };
+
/// value used to reduce the native clocks accuracy as some apss rely on
/// undefined behavior where the level of accuracy in the clock shouldn't
/// be higher.
- static constexpr u64 inaccuracy_mask = ~(0x400 - 1);
+ static constexpr u64 inaccuracy_mask = ~(UINT64_C(0x400) - 1);
+
+ TimePoint time_point;
+ // factors
+ u64 clock_rtsc_factor{};
+ u64 cpu_rtsc_factor{};
+ u64 ns_rtsc_factor{};
+ u64 us_rtsc_factor{};
+ u64 ms_rtsc_factor{};
- SpinLock rtsc_serialize{};
- u64 last_measure{};
- u64 accumulated_ticks{};
u64 rtsc_frequency;
};
} // namespace X64
diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h
index a5f5d4fc1..c2c9b6134 100644
--- a/src/common/x64/xbyak_abi.h
+++ b/src/common/x64/xbyak_abi.h
@@ -11,25 +11,25 @@
namespace Common::X64 {
-inline std::size_t RegToIndex(const Xbyak::Reg& reg) {
+constexpr size_t RegToIndex(const Xbyak::Reg& reg) {
using Kind = Xbyak::Reg::Kind;
ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0,
"RegSet only support GPRs and XMM registers.");
ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15.");
- return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16);
+ return static_cast<size_t>(reg.getIdx()) + (reg.getKind() == Kind::REG ? 0 : 16);
}
-inline Xbyak::Reg64 IndexToReg64(std::size_t reg_index) {
+constexpr Xbyak::Reg64 IndexToReg64(size_t reg_index) {
ASSERT(reg_index < 16);
return Xbyak::Reg64(static_cast<int>(reg_index));
}
-inline Xbyak::Xmm IndexToXmm(std::size_t reg_index) {
+constexpr Xbyak::Xmm IndexToXmm(size_t reg_index) {
ASSERT(reg_index >= 16 && reg_index < 32);
return Xbyak::Xmm(static_cast<int>(reg_index - 16));
}
-inline Xbyak::Reg IndexToReg(std::size_t reg_index) {
+constexpr Xbyak::Reg IndexToReg(size_t reg_index) {
if (reg_index < 16) {
return IndexToReg64(reg_index);
} else {
@@ -45,17 +45,17 @@ inline std::bitset<32> BuildRegSet(std::initializer_list<Xbyak::Reg> regs) {
return bits;
}
-const std::bitset<32> ABI_ALL_GPRS(0x0000FFFF);
-const std::bitset<32> ABI_ALL_XMMS(0xFFFF0000);
+constexpr inline std::bitset<32> ABI_ALL_GPRS(0x0000FFFF);
+constexpr inline std::bitset<32> ABI_ALL_XMMS(0xFFFF0000);
#ifdef _WIN32
// Microsoft x64 ABI
-const Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
-const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx;
-const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx;
-const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8;
-const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9;
+constexpr inline Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
+constexpr inline Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx;
+constexpr inline Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx;
+constexpr inline Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8;
+constexpr inline Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9;
const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({
// GPRs
@@ -102,11 +102,11 @@ constexpr size_t ABI_SHADOW_SPACE = 0x20;
#else
// System V x86-64 ABI
-const Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
-const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi;
-const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi;
-const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx;
-const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx;
+constexpr inline Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
+constexpr inline Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi;
+constexpr inline Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi;
+constexpr inline Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx;
+constexpr inline Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx;
const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({
// GPRs
@@ -182,7 +182,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b
size_t rsp_alignment, size_t needed_frame_size = 0) {
auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size);
- for (std::size_t i = 0; i < regs.size(); ++i) {
+ for (size_t i = 0; i < regs.size(); ++i) {
if (regs[i] && ABI_ALL_GPRS[i]) {
code.push(IndexToReg64(i));
}
@@ -192,7 +192,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b
code.sub(code.rsp, frame_info.subtraction);
}
- for (std::size_t i = 0; i < regs.size(); ++i) {
+ for (size_t i = 0; i < regs.size(); ++i) {
if (regs[i] && ABI_ALL_XMMS[i]) {
code.movaps(code.xword[code.rsp + frame_info.xmm_offset], IndexToXmm(i));
frame_info.xmm_offset += 0x10;
@@ -206,7 +206,7 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits
size_t rsp_alignment, size_t needed_frame_size = 0) {
auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size);
- for (std::size_t i = 0; i < regs.size(); ++i) {
+ for (size_t i = 0; i < regs.size(); ++i) {
if (regs[i] && ABI_ALL_XMMS[i]) {
code.movaps(IndexToXmm(i), code.xword[code.rsp + frame_info.xmm_offset]);
frame_info.xmm_offset += 0x10;
@@ -218,8 +218,8 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits
}
// GPRs need to be popped in reverse order
- for (std::size_t j = 0; j < regs.size(); ++j) {
- const std::size_t i = regs.size() - j - 1;
+ for (size_t j = 0; j < regs.size(); ++j) {
+ const size_t i = regs.size() - j - 1;
if (regs[i] && ABI_ALL_GPRS[i]) {
code.pop(IndexToReg64(i));
}
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index c85c9485f..28196d26a 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,9 +1,3 @@
-if (YUZU_ENABLE_BOXCAT)
- set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h)
-else()
- set(BCAT_BOXCAT_ADDITIONAL_SOURCES)
-endif()
-
add_library(core STATIC
arm/arm_interface.h
arm/arm_interface.cpp
@@ -19,8 +13,6 @@ add_library(core STATIC
arm/dynarmic/arm_exclusive_monitor.h
arm/exclusive_monitor.cpp
arm/exclusive_monitor.h
- arm/unicorn/arm_unicorn.cpp
- arm/unicorn/arm_unicorn.h
constants.cpp
constants.h
core.cpp
@@ -49,6 +41,7 @@ add_library(core STATIC
file_sys/bis_factory.h
file_sys/card_image.cpp
file_sys/card_image.h
+ file_sys/common_funcs.h
file_sys/content_archive.cpp
file_sys/content_archive.h
file_sys/control_metadata.cpp
@@ -126,6 +119,8 @@ add_library(core STATIC
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
+ frontend/applets/controller.cpp
+ frontend/applets/controller.h
frontend/applets/error.cpp
frontend/applets/error.h
frontend/applets/general_frontend.cpp
@@ -140,26 +135,54 @@ add_library(core STATIC
frontend/emu_window.h
frontend/framebuffer_layout.cpp
frontend/framebuffer_layout.h
+ frontend/input_interpreter.cpp
+ frontend/input_interpreter.h
frontend/input.h
- gdbstub/gdbstub.cpp
- gdbstub/gdbstub.h
hardware_interrupt_manager.cpp
hardware_interrupt_manager.h
hle/ipc.h
hle/ipc_helpers.h
- hle/kernel/address_arbiter.cpp
- hle/kernel/address_arbiter.h
hle/kernel/client_port.cpp
hle/kernel/client_port.h
hle/kernel/client_session.cpp
hle/kernel/client_session.h
hle/kernel/code_set.cpp
hle/kernel/code_set.h
- hle/kernel/errors.h
+ hle/kernel/svc_results.h
+ hle/kernel/global_scheduler_context.cpp
+ hle/kernel/global_scheduler_context.h
hle/kernel/handle_table.cpp
hle/kernel/handle_table.h
hle/kernel/hle_ipc.cpp
hle/kernel/hle_ipc.h
+ hle/kernel/k_address_arbiter.cpp
+ hle/kernel/k_address_arbiter.h
+ hle/kernel/k_affinity_mask.h
+ hle/kernel/k_condition_variable.cpp
+ hle/kernel/k_condition_variable.h
+ hle/kernel/k_event.cpp
+ hle/kernel/k_event.h
+ hle/kernel/k_light_condition_variable.h
+ hle/kernel/k_light_lock.cpp
+ hle/kernel/k_light_lock.h
+ hle/kernel/k_priority_queue.h
+ hle/kernel/k_readable_event.cpp
+ hle/kernel/k_readable_event.h
+ hle/kernel/k_resource_limit.cpp
+ hle/kernel/k_resource_limit.h
+ hle/kernel/k_scheduler.cpp
+ hle/kernel/k_scheduler.h
+ hle/kernel/k_scheduler_lock.h
+ hle/kernel/k_scoped_lock.h
+ hle/kernel/k_scoped_resource_reservation.h
+ hle/kernel/k_scoped_scheduler_lock_and_sleep.h
+ hle/kernel/k_synchronization_object.cpp
+ hle/kernel/k_synchronization_object.h
+ hle/kernel/k_thread.cpp
+ hle/kernel/k_thread.h
+ hle/kernel/k_thread_queue.h
+ hle/kernel/k_writable_event.cpp
+ hle/kernel/k_writable_event.h
hle/kernel/kernel.cpp
hle/kernel/kernel.h
hle/kernel/memory/address_space_info.cpp
@@ -179,8 +202,6 @@ add_library(core STATIC
hle/kernel/memory/slab_heap.h
hle/kernel/memory/system_control.cpp
hle/kernel/memory/system_control.h
- hle/kernel/mutex.cpp
- hle/kernel/mutex.h
hle/kernel/object.cpp
hle/kernel/object.h
hle/kernel/physical_core.cpp
@@ -190,36 +211,25 @@ add_library(core STATIC
hle/kernel/process.h
hle/kernel/process_capability.cpp
hle/kernel/process_capability.h
- hle/kernel/readable_event.cpp
- hle/kernel/readable_event.h
- hle/kernel/resource_limit.cpp
- hle/kernel/resource_limit.h
- hle/kernel/scheduler.cpp
- hle/kernel/scheduler.h
hle/kernel/server_port.cpp
hle/kernel/server_port.h
hle/kernel/server_session.cpp
hle/kernel/server_session.h
+ hle/kernel/service_thread.cpp
+ hle/kernel/service_thread.h
hle/kernel/session.cpp
hle/kernel/session.h
hle/kernel/shared_memory.cpp
hle/kernel/shared_memory.h
hle/kernel/svc.cpp
hle/kernel/svc.h
+ hle/kernel/svc_common.h
hle/kernel/svc_types.h
hle/kernel/svc_wrap.h
- hle/kernel/synchronization_object.cpp
- hle/kernel/synchronization_object.h
- hle/kernel/synchronization.cpp
- hle/kernel/synchronization.h
- hle/kernel/thread.cpp
- hle/kernel/thread.h
hle/kernel/time_manager.cpp
hle/kernel/time_manager.h
hle/kernel/transfer_memory.cpp
hle/kernel/transfer_memory.h
- hle/kernel/writable_event.cpp
- hle/kernel/writable_event.h
hle/lock.cpp
hle/lock.h
hle/result.h
@@ -244,6 +254,8 @@ add_library(core STATIC
hle/service/am/applet_oe.h
hle/service/am/applets/applets.cpp
hle/service/am/applets/applets.h
+ hle/service/am/applets/controller.cpp
+ hle/service/am/applets/controller.h
hle/service/am/applets/error.cpp
hle/service/am/applets/error.h
hle/service/am/applets/general_backend.cpp
@@ -254,6 +266,7 @@ add_library(core STATIC
hle/service/am/applets/software_keyboard.h
hle/service/am/applets/web_browser.cpp
hle/service/am/applets/web_browser.h
+ hle/service/am/applets/web_types.h
hle/service/am/idle.cpp
hle/service/am/idle.h
hle/service/am/omm.cpp
@@ -299,7 +312,6 @@ add_library(core STATIC
hle/service/audio/hwopus.h
hle/service/bcat/backend/backend.cpp
hle/service/bcat/backend/backend.h
- ${BCAT_BOXCAT_ADDITIONAL_SOURCES}
hle/service/bcat/bcat.cpp
hle/service/bcat/bcat.h
hle/service/bcat/module.cpp
@@ -389,14 +401,13 @@ add_library(core STATIC
hle/service/hid/controllers/xpad.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
+ hle/service/ldn/errors.h
hle/service/ldn/ldn.cpp
hle/service/ldn/ldn.h
hle/service/ldr/ldr.cpp
hle/service/ldr/ldr.h
hle/service/lm/lm.cpp
hle/service/lm/lm.h
- hle/service/lm/manager.cpp
- hle/service/lm/manager.h
hle/service/mig/mig.cpp
hle/service/mig/mig.h
hle/service/mii/manager.cpp
@@ -442,6 +453,8 @@ add_library(core STATIC
hle/service/nvdrv/devices/nvhost_gpu.h
hle/service/nvdrv/devices/nvhost_nvdec.cpp
hle/service/nvdrv/devices/nvhost_nvdec.h
+ hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
+ hle/service/nvdrv/devices/nvhost_nvdec_common.h
hle/service/nvdrv/devices/nvhost_nvjpg.cpp
hle/service/nvdrv/devices/nvhost_nvjpg.h
hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -455,10 +468,14 @@ add_library(core STATIC
hle/service/nvdrv/nvdrv.h
hle/service/nvdrv/nvmemp.cpp
hle/service/nvdrv/nvmemp.h
+ hle/service/nvdrv/syncpoint_manager.cpp
+ hle/service/nvdrv/syncpoint_manager.h
hle/service/nvflinger/buffer_queue.cpp
hle/service/nvflinger/buffer_queue.h
hle/service/nvflinger/nvflinger.cpp
hle/service/nvflinger/nvflinger.h
+ hle/service/olsc/olsc.cpp
+ hle/service/olsc/olsc.h
hle/service/pcie/pcie.cpp
hle/service/pcie/pcie.h
hle/service/pctl/module.cpp
@@ -501,6 +518,8 @@ add_library(core STATIC
hle/service/sockets/sfdnsres.h
hle/service/sockets/sockets.cpp
hle/service/sockets/sockets.h
+ hle/service/sockets/sockets_translate.cpp
+ hle/service/sockets/sockets_translate.h
hle/service/spl/csrng.cpp
hle/service/spl/csrng.h
hle/service/spl/module.cpp
@@ -601,6 +620,13 @@ add_library(core STATIC
tools/freezer.h
)
+if (YUZU_ENABLE_BOXCAT)
+ target_sources(core PRIVATE
+ hle/service/bcat/backend/boxcat.cpp
+ hle/service/bcat/backend/boxcat.h
+ )
+endif()
+
if (MSVC)
target_compile_options(core PRIVATE
# 'expression' : signed/unsigned mismatch
@@ -615,13 +641,30 @@ if (MSVC)
/we4267
# 'context' : truncation from 'type1' to 'type2'
/we4305
+ # 'function' : not all control paths return a value
+ /we4715
+ )
+else()
+ target_compile_options(core PRIVATE
+ -Werror=conversion
+ -Werror=ignored-qualifiers
+ -Werror=implicit-fallthrough
+ -Werror=sign-compare
+
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
+
+ $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
+
+ -Wno-sign-conversion
)
endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus unicorn zip)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus zip)
if (YUZU_ENABLE_BOXCAT)
target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT)
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index d2295ed90..0951e1976 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -147,10 +147,18 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContex
auto fp = ctx.cpu_registers[29];
auto lr = ctx.cpu_registers[30];
while (true) {
- out.push_back({"", 0, lr, 0});
- if (!fp) {
+ out.push_back({
+ .module = "",
+ .address = 0,
+ .original_address = lr,
+ .offset = 0,
+ .name = {},
+ });
+
+ if (fp == 0) {
break;
}
+
lr = memory.Read64(fp + 8) - 4;
fp = memory.Read64(fp);
}
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 1f24051e4..9a0151736 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -26,9 +26,10 @@ using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CO
/// Generic ARMv8 CPU interface
class ARM_Interface : NonCopyable {
public:
- explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers, bool uses_wall_clock)
- : system{system_}, interrupt_handlers{interrupt_handlers}, uses_wall_clock{
- uses_wall_clock} {}
+ explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers_,
+ bool uses_wall_clock_)
+ : system{system_}, interrupt_handlers{interrupt_handlers_}, uses_wall_clock{
+ uses_wall_clock_} {}
virtual ~ARM_Interface() = default;
struct ThreadContext32 {
@@ -64,15 +65,25 @@ public:
/// Step CPU by one instruction
virtual void Step() = 0;
+ /// Exits execution from a callback, the callback must rewind the stack
+ virtual void ExceptionalExit() = 0;
+
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
- /// Notifies CPU emulation that the current page table has changed.
- ///
- /// @param new_page_table The new page table.
- /// @param new_address_space_size_in_bits The new usable size of the address space in bits.
- /// This can be either 32, 36, or 39 on official software.
- ///
+ /**
+ * Clear instruction cache range
+ * @param addr Start address of the cache range to clear
+ * @param size Size of the cache range to clear, starting at addr
+ */
+ virtual void InvalidateCacheRange(VAddr addr, std::size_t size) = 0;
+
+ /**
+ * Notifies CPU emulation that the current page table has changed.
+ * @param new_page_table The new page table.
+ * @param new_address_space_size_in_bits The new usable size of the address space in bits.
+ * This can be either 32, 36, or 39 on official software.
+ */
virtual void PageTableChanged(Common::PageTable& new_page_table,
std::size_t new_address_space_size_in_bits) = 0;
diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h
index 71e582f79..c20c280f1 100644
--- a/src/core/arm/cpu_interrupt_handler.h
+++ b/src/core/arm/cpu_interrupt_handler.h
@@ -21,8 +21,8 @@ public:
CPUInterruptHandler(const CPUInterruptHandler&) = delete;
CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete;
- CPUInterruptHandler(CPUInterruptHandler&&) = default;
- CPUInterruptHandler& operator=(CPUInterruptHandler&&) = default;
+ CPUInterruptHandler(CPUInterruptHandler&&) = delete;
+ CPUInterruptHandler& operator=(CPUInterruptHandler&&) = delete;
bool IsInterrupted() const {
return is_interrupted;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index b5f28a86e..ec4407b6e 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -7,6 +7,7 @@
#include <dynarmic/A32/a32.h>
#include <dynarmic/A32/config.h>
#include <dynarmic/A32/context.h>
+#include "common/assert.h"
#include "common/logging/log.h"
#include "common/page_table.h"
#include "core/arm/cpu_interrupt_handler.h"
@@ -70,15 +71,9 @@ public:
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
- switch (exception) {
- case Dynarmic::A32::Exception::UndefinedInstruction:
- case Dynarmic::A32::Exception::UnpredictableInstruction:
- break;
- case Dynarmic::A32::Exception::Breakpoint:
- break;
- }
- LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
- static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
+ LOG_CRITICAL(Core_ARM,
+ "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
+ exception, pc, MemoryReadCode(pc), parent.IsInThumbMode());
UNIMPLEMENTED();
}
@@ -132,6 +127,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>(
page_table.pointers.data());
config.absolute_offset_page_table = true;
+ config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
config.only_detect_misalignment_via_page_table_on_page_boundary = true;
@@ -179,6 +175,9 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
}
+ if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
+ }
}
return std::make_unique<Dynarmic::A32::Jit>(config);
@@ -188,6 +187,10 @@ void ARM_Dynarmic_32::Run() {
jit->Run();
}
+void ARM_Dynarmic_32::ExceptionalExit() {
+ jit->ExceptionalExit();
+}
+
void ARM_Dynarmic_32::Step() {
jit->Step();
}
@@ -253,6 +256,9 @@ void ARM_Dynarmic_32::ChangeProcessorID(std::size_t new_core_id) {
}
void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) {
+ if (!jit) {
+ return;
+ }
Dynarmic::A32::Context context;
jit->SaveContext(context);
ctx.cpu_registers = context.Regs();
@@ -262,6 +268,9 @@ void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) {
}
void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) {
+ if (!jit) {
+ return;
+ }
Dynarmic::A32::Context context;
context.Regs() = ctx.cpu_registers;
context.ExtRegs() = ctx.extension_registers;
@@ -281,7 +290,17 @@ void ARM_Dynarmic_32::ClearInstructionCache() {
jit->ClearCache();
}
+void ARM_Dynarmic_32::InvalidateCacheRange(VAddr addr, std::size_t size) {
+ if (!jit) {
+ return;
+ }
+ jit->InvalidateCacheRange(static_cast<u32>(addr), size);
+}
+
void ARM_Dynarmic_32::ClearExclusiveState() {
+ if (!jit) {
+ return;
+ }
jit->ClearExclusiveState();
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h
index 2bab31b92..f6c4d4db9 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.h
@@ -42,6 +42,7 @@ public:
u32 GetPSTATE() const override;
void SetPSTATE(u32 pstate) override;
void Run() override;
+ void ExceptionalExit() override;
void Step() override;
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
@@ -49,6 +50,10 @@ public:
u64 GetTPIDR_EL0() const override;
void ChangeProcessorID(std::size_t new_core_id) override;
+ bool IsInThumbMode() const {
+ return (GetPSTATE() & 0x20) != 0;
+ }
+
void SaveContext(ThreadContext32& ctx) override;
void SaveContext(ThreadContext64& ctx) override {}
void LoadContext(const ThreadContext32& ctx) override;
@@ -58,6 +63,7 @@ public:
void ClearExclusiveState() override;
void ClearInstructionCache() override;
+ void InvalidateCacheRange(VAddr addr, std::size_t size) override;
void PageTableChanged(Common::PageTable& new_page_table,
std::size_t new_address_space_size_in_bits) override;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index ce9968724..ae5566ab8 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -6,6 +6,7 @@
#include <memory>
#include <dynarmic/A64/a64.h>
#include <dynarmic/A64/config.h>
+#include "common/assert.h"
#include "common/logging/log.h"
#include "common/page_table.h"
#include "core/arm/cpu_interrupt_handler.h"
@@ -13,11 +14,9 @@
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
-#include "core/core_timing_util.h"
-#include "core/gdbstub/gdbstub.h"
#include "core/hardware_properties.h"
+#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
#include "core/settings.h"
@@ -82,16 +81,9 @@ public:
}
void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
- LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc,
- num_instructions, MemoryReadCode(pc));
-
- ARM_Interface::ThreadContext64 ctx;
- parent.SaveContext(ctx);
- parent.inner_unicorn.LoadContext(ctx);
- parent.inner_unicorn.ExecuteInstructions(num_instructions);
- parent.inner_unicorn.SaveContext(ctx);
- parent.LoadContext(ctx);
- num_interpreted_instructions += num_instructions;
+ LOG_ERROR(Core_ARM,
+ "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc,
+ num_instructions, MemoryReadCode(pc));
}
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
@@ -103,16 +95,6 @@ public:
case Dynarmic::A64::Exception::Yield:
return;
case Dynarmic::A64::Exception::Breakpoint:
- if (GDBStub::IsServerEnabled()) {
- parent.jit->HaltExecution();
- parent.SetPC(pc);
- Kernel::Thread* const thread = parent.system.CurrentScheduler().GetCurrentThread();
- parent.SaveContext(thread->GetContext64());
- GDBStub::Break();
- GDBStub::SendTrap(thread, 5);
- return;
- }
- [[fallthrough]];
default:
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
@@ -127,18 +109,17 @@ public:
if (parent.uses_wall_clock) {
return;
}
+
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4
// times.
- u64 amortized_ticks =
- (ticks - num_interpreted_instructions) / Core::Hardware::NUM_CPU_CORES;
+ u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
parent.system.CoreTiming().AddTicks(amortized_ticks);
- num_interpreted_instructions = 0;
}
u64 GetTicksRemaining() override {
@@ -156,7 +137,6 @@ public:
}
ARM_Dynarmic_64& parent;
- std::size_t num_interpreted_instructions = 0;
u64 tpidrro_el0 = 0;
u64 tpidr_el0 = 0;
static constexpr u64 minimum_run_cycles = 1000U;
@@ -172,6 +152,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
// Memory
config.page_table = reinterpret_cast<void**>(page_table.pointers.data());
config.page_table_address_space_bits = address_space_bits;
+ config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
config.silently_mirror_page_table = false;
config.absolute_offset_page_table = true;
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
@@ -231,6 +212,9 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
if (Settings::values.cpuopt_unsafe_reduce_fp_error) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP;
}
+ if (Settings::values.cpuopt_unsafe_inaccurate_nan) {
+ config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
+ }
}
return std::make_shared<Dynarmic::A64::Jit>(config);
@@ -240,6 +224,10 @@ void ARM_Dynarmic_64::Run() {
jit->Run();
}
+void ARM_Dynarmic_64::ExceptionalExit() {
+ jit->ExceptionalExit();
+}
+
void ARM_Dynarmic_64::Step() {
cb->InterpreterFallback(jit->GetPC(), 1);
}
@@ -248,12 +236,8 @@ ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handle
bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor,
std::size_t core_index)
: ARM_Interface{system, interrupt_handlers, uses_wall_clock},
- cb(std::make_unique<DynarmicCallbacks64>(*this)), inner_unicorn{system, interrupt_handlers,
- uses_wall_clock,
- ARM_Unicorn::Arch::AArch64,
- core_index},
- core_index{core_index}, exclusive_monitor{
- dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
+ cb(std::make_unique<DynarmicCallbacks64>(*this)), core_index{core_index},
+ exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
ARM_Dynarmic_64::~ARM_Dynarmic_64() = default;
@@ -310,6 +294,9 @@ void ARM_Dynarmic_64::ChangeProcessorID(std::size_t new_core_id) {
}
void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) {
+ if (!jit) {
+ return;
+ }
ctx.cpu_registers = jit->GetRegisters();
ctx.sp = jit->GetSP();
ctx.pc = jit->GetPC();
@@ -321,6 +308,9 @@ void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) {
}
void ARM_Dynarmic_64::LoadContext(const ThreadContext64& ctx) {
+ if (!jit) {
+ return;
+ }
jit->SetRegisters(ctx.cpu_registers);
jit->SetSP(ctx.sp);
jit->SetPC(ctx.pc);
@@ -342,7 +332,17 @@ void ARM_Dynarmic_64::ClearInstructionCache() {
jit->ClearCache();
}
+void ARM_Dynarmic_64::InvalidateCacheRange(VAddr addr, std::size_t size) {
+ if (!jit) {
+ return;
+ }
+ jit->InvalidateCacheRange(addr, size);
+}
+
void ARM_Dynarmic_64::ClearExclusiveState() {
+ if (!jit) {
+ return;
+ }
jit->ClearExclusiveState();
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h
index 403c55961..329b59a32 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.h
@@ -12,7 +12,6 @@
#include "common/hash.h"
#include "core/arm/arm_interface.h"
#include "core/arm/exclusive_monitor.h"
-#include "core/arm/unicorn/arm_unicorn.h"
namespace Core::Memory {
class Memory;
@@ -41,6 +40,7 @@ public:
void SetPSTATE(u32 pstate) override;
void Run() override;
void Step() override;
+ void ExceptionalExit() override;
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
void SetTPIDR_EL0(u64 value) override;
@@ -56,6 +56,7 @@ public:
void ClearExclusiveState() override;
void ClearInstructionCache() override;
+ void InvalidateCacheRange(VAddr addr, std::size_t size) override;
void PageTableChanged(Common::PageTable& new_page_table,
std::size_t new_address_space_size_in_bits) override;
@@ -71,7 +72,6 @@ private:
std::unique_ptr<DynarmicCallbacks64> cb;
JitCacheType jit_cache;
std::shared_ptr<Dynarmic::A64::Jit> jit;
- ARM_Unicorn inner_unicorn;
std::size_t core_index;
DynarmicExclusiveMonitor& exclusive_monitor;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
index 54556e0f9..caefc09f4 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -34,7 +34,7 @@ std::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigne
CoprocReg CRm, unsigned opc2) {
LOG_CRITICAL(Core_ARM, "CP15: cdp{} p15, {}, {}, {}, {}, {}", two ? "2" : "", opc1, CRd, CRn,
CRm, opc2);
- return {};
+ return std::nullopt;
}
CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
@@ -115,7 +115,7 @@ std::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_trans
LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "",
long_transfer ? "l" : "", CRd);
}
- return {};
+ return std::nullopt;
}
std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
@@ -127,7 +127,7 @@ std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_tran
LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "",
long_transfer ? "l" : "", CRd);
}
- return {};
+ return std::nullopt;
}
} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
index 7356d252e..dc6f4af3a 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -35,8 +35,8 @@ public:
std::optional<u8> option) override;
ARM_Dynarmic_32& parent;
- u32 uprw;
- u32 uro;
+ u32 uprw = 0;
+ u32 uro = 0;
};
} // namespace Core
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
deleted file mode 100644
index 1df3f3ed1..000000000
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <unicorn/arm64.h>
-#include "common/assert.h"
-#include "common/microprofile.h"
-#include "core/arm/cpu_interrupt_handler.h"
-#include "core/arm/unicorn/arm_unicorn.h"
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/svc.h"
-#include "core/memory.h"
-
-namespace Core {
-
-// Load Unicorn DLL once on Windows using RAII
-#ifdef _MSC_VER
-#include <unicorn_dynload.h>
-struct LoadDll {
-private:
- LoadDll() {
- ASSERT(uc_dyn_load(NULL, 0));
- }
- ~LoadDll() {
- ASSERT(uc_dyn_free());
- }
- static LoadDll g_load_dll;
-};
-LoadDll LoadDll::g_load_dll;
-#endif
-
-#define CHECKED(expr) \
- do { \
- if (auto _cerr = (expr)) { \
- ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", _cerr, \
- uc_strerror(_cerr)); \
- } \
- } while (0)
-
-static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) {
- GDBStub::BreakpointAddress bkpt =
- GDBStub::GetNextBreakpointFromAddress(address, GDBStub::BreakpointType::Execute);
- if (GDBStub::IsMemoryBreak() ||
- (bkpt.type != GDBStub::BreakpointType::None && address == bkpt.address)) {
- auto core = static_cast<ARM_Unicorn*>(user_data);
- core->RecordBreak(bkpt);
- uc_emu_stop(uc);
- }
-}
-
-static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value,
- void* user_data) {
- auto* const system = static_cast<System*>(user_data);
-
- ARM_Interface::ThreadContext64 ctx{};
- system->CurrentArmInterface().SaveContext(ctx);
- ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr,
- ctx.pc, ctx.cpu_registers[30]);
-
- return false;
-}
-
-ARM_Unicorn::ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
- Arch architecture, std::size_t core_index)
- : ARM_Interface{system, interrupt_handlers, uses_wall_clock}, core_index{core_index} {
- const auto arch = architecture == Arch::AArch32 ? UC_ARCH_ARM : UC_ARCH_ARM64;
- CHECKED(uc_open(arch, UC_MODE_ARM, &uc));
-
- auto fpv = 3 << 20;
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv));
-
- uc_hook hook{};
- CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, UINT64_MAX));
- CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, &system, 0,
- UINT64_MAX));
- if (GDBStub::IsServerEnabled()) {
- CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, UINT64_MAX));
- last_bkpt_hit = false;
- }
-}
-
-ARM_Unicorn::~ARM_Unicorn() {
- CHECKED(uc_close(uc));
-}
-
-void ARM_Unicorn::SetPC(u64 pc) {
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc));
-}
-
-u64 ARM_Unicorn::GetPC() const {
- u64 val{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &val));
- return val;
-}
-
-u64 ARM_Unicorn::GetReg(int regn) const {
- u64 val{};
- auto treg = UC_ARM64_REG_SP;
- if (regn <= 28) {
- treg = (uc_arm64_reg)(UC_ARM64_REG_X0 + regn);
- } else if (regn < 31) {
- treg = (uc_arm64_reg)(UC_ARM64_REG_X29 + regn - 29);
- }
- CHECKED(uc_reg_read(uc, treg, &val));
- return val;
-}
-
-void ARM_Unicorn::SetReg(int regn, u64 val) {
- auto treg = UC_ARM64_REG_SP;
- if (regn <= 28) {
- treg = (uc_arm64_reg)(UC_ARM64_REG_X0 + regn);
- } else if (regn < 31) {
- treg = (uc_arm64_reg)(UC_ARM64_REG_X29 + regn - 29);
- }
- CHECKED(uc_reg_write(uc, treg, &val));
-}
-
-u128 ARM_Unicorn::GetVectorReg(int /*index*/) const {
- UNIMPLEMENTED();
- static constexpr u128 res{};
- return res;
-}
-
-void ARM_Unicorn::SetVectorReg(int /*index*/, u128 /*value*/) {
- UNIMPLEMENTED();
-}
-
-u32 ARM_Unicorn::GetPSTATE() const {
- u64 nzcv{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &nzcv));
- return static_cast<u32>(nzcv);
-}
-
-void ARM_Unicorn::SetPSTATE(u32 pstate) {
- u64 nzcv = pstate;
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &nzcv));
-}
-
-VAddr ARM_Unicorn::GetTlsAddress() const {
- u64 base{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
- return base;
-}
-
-void ARM_Unicorn::SetTlsAddress(VAddr base) {
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base));
-}
-
-u64 ARM_Unicorn::GetTPIDR_EL0() const {
- u64 value{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDR_EL0, &value));
- return value;
-}
-
-void ARM_Unicorn::SetTPIDR_EL0(u64 value) {
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDR_EL0, &value));
-}
-
-void ARM_Unicorn::ChangeProcessorID(std::size_t new_core_id) {
- core_index = new_core_id;
-}
-
-void ARM_Unicorn::Run() {
- if (GDBStub::IsServerEnabled()) {
- ExecuteInstructions(std::max(4000000U, 0U));
- } else {
- while (true) {
- if (interrupt_handlers[core_index].IsInterrupted()) {
- return;
- }
- ExecuteInstructions(10);
- }
- }
-}
-
-void ARM_Unicorn::Step() {
- ExecuteInstructions(1);
-}
-
-MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64));
-
-void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) {
- MICROPROFILE_SCOPE(ARM_Jit_Unicorn);
-
- // Temporarily map the code page for Unicorn
- u64 map_addr{GetPC() & ~Memory::PAGE_MASK};
- std::vector<u8> page_buffer(Memory::PAGE_SIZE);
- system.Memory().ReadBlock(map_addr, page_buffer.data(), page_buffer.size());
-
- CHECKED(uc_mem_map_ptr(uc, map_addr, page_buffer.size(),
- UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, page_buffer.data()));
- CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
- CHECKED(uc_mem_unmap(uc, map_addr, page_buffer.size()));
- if (GDBStub::IsServerEnabled()) {
- if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) {
- uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
- }
-
- Kernel::Thread* const thread = system.CurrentScheduler().GetCurrentThread();
- SaveContext(thread->GetContext64());
- if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
- last_bkpt_hit = false;
- GDBStub::Break();
- GDBStub::SendTrap(thread, 5);
- }
- }
-}
-
-void ARM_Unicorn::SaveContext(ThreadContext64& ctx) {
- int uregs[32];
- void* tregs[32];
-
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &ctx.sp));
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &ctx.pc));
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &ctx.pstate));
-
- for (auto i = 0; i < 29; ++i) {
- uregs[i] = UC_ARM64_REG_X0 + i;
- tregs[i] = &ctx.cpu_registers[i];
- }
- uregs[29] = UC_ARM64_REG_X29;
- tregs[29] = (void*)&ctx.cpu_registers[29];
- uregs[30] = UC_ARM64_REG_X30;
- tregs[30] = (void*)&ctx.cpu_registers[30];
-
- CHECKED(uc_reg_read_batch(uc, uregs, tregs, 31));
-
- for (int i = 0; i < 32; ++i) {
- uregs[i] = UC_ARM64_REG_Q0 + i;
- tregs[i] = &ctx.vector_registers[i];
- }
-
- CHECKED(uc_reg_read_batch(uc, uregs, tregs, 32));
-}
-
-void ARM_Unicorn::LoadContext(const ThreadContext64& ctx) {
- int uregs[32];
- void* tregs[32];
-
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &ctx.sp));
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &ctx.pc));
- CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &ctx.pstate));
-
- for (int i = 0; i < 29; ++i) {
- uregs[i] = UC_ARM64_REG_X0 + i;
- tregs[i] = (void*)&ctx.cpu_registers[i];
- }
- uregs[29] = UC_ARM64_REG_X29;
- tregs[29] = (void*)&ctx.cpu_registers[29];
- uregs[30] = UC_ARM64_REG_X30;
- tregs[30] = (void*)&ctx.cpu_registers[30];
-
- CHECKED(uc_reg_write_batch(uc, uregs, tregs, 31));
-
- for (auto i = 0; i < 32; ++i) {
- uregs[i] = UC_ARM64_REG_Q0 + i;
- tregs[i] = (void*)&ctx.vector_registers[i];
- }
-
- CHECKED(uc_reg_write_batch(uc, uregs, tregs, 32));
-}
-
-void ARM_Unicorn::PrepareReschedule() {
- CHECKED(uc_emu_stop(uc));
-}
-
-void ARM_Unicorn::ClearExclusiveState() {}
-
-void ARM_Unicorn::ClearInstructionCache() {}
-
-void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) {
- last_bkpt = bkpt;
- last_bkpt_hit = true;
-}
-
-void ARM_Unicorn::InterruptHook(uc_engine* uc, u32 int_no, void* user_data) {
- u32 esr{};
- CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
-
- const auto ec = esr >> 26;
- const auto iss = esr & 0xFFFFFF;
-
- auto* const arm_instance = static_cast<ARM_Unicorn*>(user_data);
-
- switch (ec) {
- case 0x15: // SVC
- Kernel::Svc::Call(arm_instance->system, iss);
- break;
- }
-}
-
-} // namespace Core
diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h
deleted file mode 100644
index 810aff311..000000000
--- a/src/core/arm/unicorn/arm_unicorn.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <unicorn/unicorn.h>
-#include "common/common_types.h"
-#include "core/arm/arm_interface.h"
-#include "core/gdbstub/gdbstub.h"
-
-namespace Core {
-
-class System;
-
-class ARM_Unicorn final : public ARM_Interface {
-public:
- enum class Arch {
- AArch32, // 32-bit ARM
- AArch64, // 64-bit ARM
- };
-
- explicit ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
- Arch architecture, std::size_t core_index);
- ~ARM_Unicorn() override;
-
- void SetPC(u64 pc) override;
- u64 GetPC() const override;
- u64 GetReg(int index) const override;
- void SetReg(int index, u64 value) override;
- u128 GetVectorReg(int index) const override;
- void SetVectorReg(int index, u128 value) override;
- u32 GetPSTATE() const override;
- void SetPSTATE(u32 pstate) override;
- VAddr GetTlsAddress() const override;
- void SetTlsAddress(VAddr address) override;
- void SetTPIDR_EL0(u64 value) override;
- u64 GetTPIDR_EL0() const override;
- void ChangeProcessorID(std::size_t new_core_id) override;
- void PrepareReschedule() override;
- void ClearExclusiveState() override;
- void ExecuteInstructions(std::size_t num_instructions);
- void Run() override;
- void Step() override;
- void ClearInstructionCache() override;
- void PageTableChanged(Common::PageTable&, std::size_t) override {}
- void RecordBreak(GDBStub::BreakpointAddress bkpt);
-
- void SaveContext(ThreadContext32& ctx) override {}
- void SaveContext(ThreadContext64& ctx) override;
- void LoadContext(const ThreadContext32& ctx) override {}
- void LoadContext(const ThreadContext64& ctx) override;
-
-private:
- static void InterruptHook(uc_engine* uc, u32 int_no, void* user_data);
-
- uc_engine* uc{};
- GDBStub::BreakpointAddress last_bkpt{};
- bool last_bkpt_hit = false;
- std::size_t core_index;
-};
-
-} // namespace Core
diff --git a/src/core/core.cpp b/src/core/core.cpp
index c2c0eec0b..30f5e1128 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -25,21 +25,20 @@
#include "core/file_sys/sdmc_factory.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
-#include "core/gdbstub/gdbstub.h"
#include "core/hardware_interrupt_manager.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/apm/controller.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/manager.h"
-#include "core/hle/service/lm/manager.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/time/time_manager.h"
#include "core/loader/loader.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
@@ -91,37 +90,47 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
std::string dir_name;
std::string filename;
Common::SplitPath(path, &dir_name, &filename, nullptr);
+
if (filename == "00") {
const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
std::vector<FileSys::VirtualFile> concat;
- for (u8 i = 0; i < 0x10; ++i) {
- auto next = dir->GetFile(fmt::format("{:02X}", i));
- if (next != nullptr)
+
+ for (u32 i = 0; i < 0x10; ++i) {
+ const auto file_name = fmt::format("{:02X}", i);
+ auto next = dir->GetFile(file_name);
+
+ if (next != nullptr) {
concat.push_back(std::move(next));
- else {
- next = dir->GetFile(fmt::format("{:02x}", i));
- if (next != nullptr)
- concat.push_back(std::move(next));
- else
+ } else {
+ next = dir->GetFile(file_name);
+
+ if (next == nullptr) {
break;
+ }
+
+ concat.push_back(std::move(next));
}
}
- if (concat.empty())
+ if (concat.empty()) {
return nullptr;
+ }
- return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
+ return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(std::move(concat),
+ dir->GetName());
}
- if (Common::FS::IsDirectory(path))
- return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
+ if (Common::FS::IsDirectory(path)) {
+ return vfs->OpenFile(path + "/main", FileSys::Mode::Read);
+ }
return vfs->OpenFile(path, FileSys::Mode::Read);
}
+
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, memory{system},
- cpu_manager{system}, reporter{system}, applet_manager{system} {}
+ cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
ResultStatus Run() {
status = ResultStatus::Success;
@@ -144,12 +153,12 @@ struct System::Impl {
}
ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
- LOG_DEBUG(HW_Memory, "initialized OK");
+ LOG_DEBUG(Core, "initialized OK");
device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue();
- is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue();
+ is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
kernel.SetMulticore(is_multicore);
cpu_manager.SetMulticore(is_multicore);
@@ -178,17 +187,18 @@ struct System::Impl {
arp_manager.ResetAll();
telemetry_session = std::make_unique<Core::TelemetrySession>();
- service_manager = std::make_shared<Service::SM::ServiceManager>();
- Service::Init(service_manager, system);
- GDBStub::DeferStart();
-
- interrupt_manager = std::make_unique<Core::Hardware::InterruptManager>(system);
gpu_core = VideoCore::CreateGPU(emu_window, system);
if (!gpu_core) {
return ResultStatus::ErrorVideoCore;
}
- gpu_core->Renderer().Rasterizer().SetupDirtyFlags();
+
+ service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
+ services = std::make_unique<Service::Services>(service_manager, system);
+ interrupt_manager = std::make_unique<Hardware::InterruptManager>(system);
+
+ // Initialize time manager, which must happen after kernel is created
+ time_manager.Initialize();
is_powered_on = true;
exit_lock = false;
@@ -203,9 +213,11 @@ struct System::Impl {
return ResultStatus::Success;
}
- ResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
- const std::string& filepath) {
- app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
+ ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath,
+ std::size_t program_index) {
+ app_loader = Loader::GetLoader(system, GetGameFileFromPath(virtual_filesystem, filepath),
+ program_index);
+
if (!app_loader) {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
return ResultStatus::ErrorGetLoader;
@@ -219,12 +231,12 @@ struct System::Impl {
return init_result;
}
- telemetry_session->AddInitialInfo(*app_loader);
+ telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
auto main_process =
Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland);
- const auto [load_result, load_parameters] = app_loader->Load(*main_process);
+ const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
- LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
+ LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result);
Shutdown();
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
@@ -232,6 +244,7 @@ struct System::Impl {
}
AddGlueRegistrationForProcess(*app_loader, *main_process);
kernel.MakeCurrentProcess(main_process.get());
+ kernel.InitializeCores();
// Initialize cheat engine
if (cheat_engine) {
@@ -253,8 +266,7 @@ struct System::Impl {
u64 title_id{0};
if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
- LOG_ERROR(Core, "Failed to find title id for ROM (Error {})",
- static_cast<u32>(load_result));
+ LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", load_result);
}
perf_stats = std::make_unique<PerfStats>(title_id);
// Reset counters and set time origin to current frame
@@ -280,8 +292,6 @@ struct System::Impl {
perf_stats->GetMeanFrametime());
}
- lm_manager.Flush();
-
is_powered_on = false;
exit_lock = false;
@@ -290,19 +300,17 @@ struct System::Impl {
}
// Shutdown emulation session
- GDBStub::Shutdown();
- Service::Shutdown();
+ services.reset();
service_manager.reset();
cheat_engine.reset();
telemetry_session.reset();
- device_memory.reset();
// Close all CPU/threading state
cpu_manager.Shutdown();
// Shutdown kernel and core timing
- kernel.Shutdown();
core_timing.Shutdown();
+ kernel.Shutdown();
// Close app loader
app_loader.reset();
@@ -333,7 +341,7 @@ struct System::Impl {
Service::Glue::ApplicationLaunchProperty launch{};
launch.title_id = process.GetTitleID();
- FileSys::PatchManager pm{launch.title_id};
+ FileSys::PatchManager pm{launch.title_id, fs_controller, *content_provider};
launch.version = pm.GetGameVersion().value_or(0);
// TODO(DarkLordZach): When FSController/Game Card Support is added, if
@@ -387,11 +395,14 @@ struct System::Impl {
/// Service State
Service::Glue::ARPManager arp_manager;
- Service::LM::Manager lm_manager{reporter};
+ Service::Time::TimeManager time_manager;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
+ /// Services
+ std::unique_ptr<Service::Services> services;
+
/// Telemetry session for this emulation session
std::unique_ptr<Core::TelemetrySession> telemetry_session;
@@ -407,6 +418,8 @@ struct System::Impl {
bool is_multicore{};
bool is_async_gpu{};
+ ExecuteProgramCallback execute_program_callback;
+
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{};
};
@@ -438,8 +451,17 @@ void System::InvalidateCpuInstructionCaches() {
impl->kernel.InvalidateAllInstructionCaches();
}
-System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
- return impl->Load(*this, emu_window, filepath);
+void System::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) {
+ impl->kernel.InvalidateCpuInstructionCacheRange(addr, size);
+}
+
+void System::Shutdown() {
+ impl->Shutdown();
+}
+
+System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
+ std::size_t program_index) {
+ return impl->Load(*this, emu_window, filepath, program_index);
}
bool System::IsPoweredOn() const {
@@ -467,11 +489,11 @@ const TelemetrySession& System::TelemetrySession() const {
}
ARM_Interface& System::CurrentArmInterface() {
- return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface();
+ return impl->kernel.CurrentPhysicalCore().ArmInterface();
}
const ARM_Interface& System::CurrentArmInterface() const {
- return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface();
+ return impl->kernel.CurrentPhysicalCore().ArmInterface();
}
std::size_t System::CurrentCoreIndex() const {
@@ -480,14 +502,6 @@ std::size_t System::CurrentCoreIndex() const {
return core;
}
-Kernel::Scheduler& System::CurrentScheduler() {
- return impl->kernel.CurrentScheduler();
-}
-
-const Kernel::Scheduler& System::CurrentScheduler() const {
- return impl->kernel.CurrentScheduler();
-}
-
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
return impl->kernel.CurrentPhysicalCore();
}
@@ -496,22 +510,14 @@ const Kernel::PhysicalCore& System::CurrentPhysicalCore() const {
return impl->kernel.CurrentPhysicalCore();
}
-Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
- return impl->kernel.Scheduler(core_index);
-}
-
-const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
- return impl->kernel.Scheduler(core_index);
-}
-
/// Gets the global scheduler
-Kernel::GlobalScheduler& System::GlobalScheduler() {
- return impl->kernel.GlobalScheduler();
+Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() {
+ return impl->kernel.GlobalSchedulerContext();
}
/// Gets the global scheduler
-const Kernel::GlobalScheduler& System::GlobalScheduler() const {
- return impl->kernel.GlobalScheduler();
+const Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() const {
+ return impl->kernel.GlobalSchedulerContext();
}
Kernel::Process* System::CurrentProcess() {
@@ -531,15 +537,11 @@ const Kernel::Process* System::CurrentProcess() const {
}
ARM_Interface& System::ArmInterface(std::size_t core_index) {
- auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread();
- ASSERT(thread && !thread->IsHLEThread());
- return thread->ArmInterface();
+ return impl->kernel.PhysicalCore(core_index).ArmInterface();
}
const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
- auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread();
- ASSERT(thread && !thread->IsHLEThread());
- return thread->ArmInterface();
+ return impl->kernel.PhysicalCore(core_index).ArmInterface();
}
ExclusiveMonitor& System::Monitor() {
@@ -626,15 +628,19 @@ const std::string& System::GetStatusDetails() const {
return impl->status_details;
}
-Loader::AppLoader& System::GetAppLoader() const {
+Loader::AppLoader& System::GetAppLoader() {
+ return *impl->app_loader;
+}
+
+const Loader::AppLoader& System::GetAppLoader() const {
return *impl->app_loader;
}
-void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
+void System::SetFilesystem(FileSys::VirtualFilesystem vfs) {
impl->virtual_filesystem = std::move(vfs);
}
-std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
+FileSys::VirtualFilesystem System::GetFilesystem() const {
return impl->virtual_filesystem;
}
@@ -710,12 +716,12 @@ const Service::APM::Controller& System::GetAPMController() const {
return impl->apm_controller;
}
-Service::LM::Manager& System::GetLogManager() {
- return impl->lm_manager;
+Service::Time::TimeManager& System::GetTimeManager() {
+ return impl->time_manager;
}
-const Service::LM::Manager& System::GetLogManager() const {
- return impl->lm_manager;
+const Service::Time::TimeManager& System::GetTimeManager() const {
+ return impl->time_manager;
}
void System::SetExitLock(bool locked) {
@@ -734,14 +740,6 @@ const System::CurrentBuildProcessID& System::GetCurrentProcessBuildID() const {
return impl->build_id;
}
-System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
- return impl->Init(*this, emu_window);
-}
-
-void System::Shutdown() {
- impl->Shutdown();
-}
-
Service::SM::ServiceManager& System::ServiceManager() {
return *impl->service_manager;
}
@@ -772,4 +770,16 @@ bool System::IsMulticore() const {
return impl->is_multicore;
}
+void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) {
+ impl->execute_program_callback = std::move(callback);
+}
+
+void System::ExecuteProgram(std::size_t program_index) {
+ if (impl->execute_program_callback) {
+ impl->execute_program_callback(program_index);
+ } else {
+ LOG_CRITICAL(Core, "execute_program_callback must be initialized by the frontend");
+ }
+}
+
} // namespace Core
diff --git a/src/core/core.h b/src/core/core.h
index 5c6cfbffe..3a8e040c1 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -5,6 +5,7 @@
#pragma once
#include <cstddef>
+#include <functional>
#include <memory>
#include <string>
#include <vector>
@@ -25,11 +26,11 @@ class VfsFilesystem;
} // namespace FileSys
namespace Kernel {
-class GlobalScheduler;
+class GlobalSchedulerContext;
class KernelCore;
class PhysicalCore;
class Process;
-class Scheduler;
+class KScheduler;
} // namespace Kernel
namespace Loader {
@@ -61,14 +62,14 @@ namespace Glue {
class ARPManager;
}
-namespace LM {
-class Manager;
-} // namespace LM
-
namespace SM {
class ServiceManager;
} // namespace SM
+namespace Time {
+class TimeManager;
+} // namespace Time
+
} // namespace Service
namespace Tegra {
@@ -120,7 +121,7 @@ public:
* Gets the instance of the System singleton class.
* @returns Reference to the instance of the System singleton class.
*/
- static System& GetInstance() {
+ [[deprecated("Use of the global system instance is deprecated")]] static System& GetInstance() {
return s_instance;
}
@@ -140,19 +141,19 @@ public:
* Run the OS and Application
* This function will start emulation and run the relevant devices
*/
- ResultStatus Run();
+ [[nodiscard]] ResultStatus Run();
/**
* Pause the OS and Application
* This function will pause emulation and stop the relevant devices
*/
- ResultStatus Pause();
+ [[nodiscard]] ResultStatus Pause();
/**
* Step the CPU one instruction
* @return Result status, indicating whether or not the operation succeeded.
*/
- ResultStatus SingleStep();
+ [[nodiscard]] ResultStatus SingleStep();
/**
* Invalidate the CPU instruction caches
@@ -161,6 +162,8 @@ public:
*/
void InvalidateCpuInstructionCaches();
+ void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size);
+
/// Shutdown the emulated system.
void Shutdown();
@@ -169,22 +172,24 @@ public:
* @param emu_window Reference to the host-system window used for video output and keyboard
* input.
* @param filepath String path to the executable application to load on the host file system.
+ * @param program_index Specifies the index within the container of the program to launch.
* @returns ResultStatus code, indicating if the operation succeeded.
*/
- ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath);
+ [[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
+ std::size_t program_index = 0);
/**
* Indicates if the emulated system is powered on (all subsystems initialized and able to run an
* application).
* @returns True if the emulated system is powered on, otherwise false.
*/
- bool IsPoweredOn() const;
+ [[nodiscard]] bool IsPoweredOn() const;
/// Gets a reference to the telemetry session for this emulation session.
- Core::TelemetrySession& TelemetrySession();
+ [[nodiscard]] Core::TelemetrySession& TelemetrySession();
/// Gets a reference to the telemetry session for this emulation session.
- const Core::TelemetrySession& TelemetrySession() const;
+ [[nodiscard]] const Core::TelemetrySession& TelemetrySession() const;
/// Prepare the core emulation for a reschedule
void PrepareReschedule();
@@ -193,181 +198,163 @@ public:
void PrepareReschedule(u32 core_index);
/// Gets and resets core performance statistics
- PerfStatsResults GetAndResetPerfStats();
+ [[nodiscard]] PerfStatsResults GetAndResetPerfStats();
/// Gets an ARM interface to the CPU core that is currently running
- ARM_Interface& CurrentArmInterface();
+ [[nodiscard]] ARM_Interface& CurrentArmInterface();
/// Gets an ARM interface to the CPU core that is currently running
- const ARM_Interface& CurrentArmInterface() const;
+ [[nodiscard]] const ARM_Interface& CurrentArmInterface() const;
/// Gets the index of the currently running CPU core
- std::size_t CurrentCoreIndex() const;
-
- /// Gets the scheduler for the CPU core that is currently running
- Kernel::Scheduler& CurrentScheduler();
-
- /// Gets the scheduler for the CPU core that is currently running
- const Kernel::Scheduler& CurrentScheduler() const;
+ [[nodiscard]] std::size_t CurrentCoreIndex() const;
/// Gets the physical core for the CPU core that is currently running
- Kernel::PhysicalCore& CurrentPhysicalCore();
+ [[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore();
/// Gets the physical core for the CPU core that is currently running
- const Kernel::PhysicalCore& CurrentPhysicalCore() const;
+ [[nodiscard]] const Kernel::PhysicalCore& CurrentPhysicalCore() const;
/// Gets a reference to an ARM interface for the CPU core with the specified index
- ARM_Interface& ArmInterface(std::size_t core_index);
+ [[nodiscard]] ARM_Interface& ArmInterface(std::size_t core_index);
/// Gets a const reference to an ARM interface from the CPU core with the specified index
- const ARM_Interface& ArmInterface(std::size_t core_index) const;
+ [[nodiscard]] const ARM_Interface& ArmInterface(std::size_t core_index) const;
- CpuManager& GetCpuManager();
+ /// Gets a reference to the underlying CPU manager.
+ [[nodiscard]] CpuManager& GetCpuManager();
- const CpuManager& GetCpuManager() const;
+ /// Gets a const reference to the underlying CPU manager
+ [[nodiscard]] const CpuManager& GetCpuManager() const;
/// Gets a reference to the exclusive monitor
- ExclusiveMonitor& Monitor();
+ [[nodiscard]] ExclusiveMonitor& Monitor();
/// Gets a constant reference to the exclusive monitor
- const ExclusiveMonitor& Monitor() const;
+ [[nodiscard]] const ExclusiveMonitor& Monitor() const;
/// Gets a mutable reference to the system memory instance.
- Core::Memory::Memory& Memory();
+ [[nodiscard]] Core::Memory::Memory& Memory();
/// Gets a constant reference to the system memory instance.
- const Core::Memory::Memory& Memory() const;
+ [[nodiscard]] const Core::Memory::Memory& Memory() const;
/// Gets a mutable reference to the GPU interface
- Tegra::GPU& GPU();
+ [[nodiscard]] Tegra::GPU& GPU();
/// Gets an immutable reference to the GPU interface.
- const Tegra::GPU& GPU() const;
+ [[nodiscard]] const Tegra::GPU& GPU() const;
/// Gets a mutable reference to the renderer.
- VideoCore::RendererBase& Renderer();
+ [[nodiscard]] VideoCore::RendererBase& Renderer();
/// Gets an immutable reference to the renderer.
- const VideoCore::RendererBase& Renderer() const;
-
- /// Gets the scheduler for the CPU core with the specified index
- Kernel::Scheduler& Scheduler(std::size_t core_index);
-
- /// Gets the scheduler for the CPU core with the specified index
- const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
+ [[nodiscard]] const VideoCore::RendererBase& Renderer() const;
/// Gets the global scheduler
- Kernel::GlobalScheduler& GlobalScheduler();
+ [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
/// Gets the global scheduler
- const Kernel::GlobalScheduler& GlobalScheduler() const;
+ [[nodiscard]] const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const;
/// Gets the manager for the guest device memory
- Core::DeviceMemory& DeviceMemory();
+ [[nodiscard]] Core::DeviceMemory& DeviceMemory();
/// Gets the manager for the guest device memory
- const Core::DeviceMemory& DeviceMemory() const;
+ [[nodiscard]] const Core::DeviceMemory& DeviceMemory() const;
/// Provides a pointer to the current process
- Kernel::Process* CurrentProcess();
+ [[nodiscard]] Kernel::Process* CurrentProcess();
/// Provides a constant pointer to the current process.
- const Kernel::Process* CurrentProcess() const;
+ [[nodiscard]] const Kernel::Process* CurrentProcess() const;
/// Provides a reference to the core timing instance.
- Timing::CoreTiming& CoreTiming();
+ [[nodiscard]] Timing::CoreTiming& CoreTiming();
/// Provides a constant reference to the core timing instance.
- const Timing::CoreTiming& CoreTiming() const;
+ [[nodiscard]] const Timing::CoreTiming& CoreTiming() const;
/// Provides a reference to the interrupt manager instance.
- Core::Hardware::InterruptManager& InterruptManager();
+ [[nodiscard]] Core::Hardware::InterruptManager& InterruptManager();
/// Provides a constant reference to the interrupt manager instance.
- const Core::Hardware::InterruptManager& InterruptManager() const;
+ [[nodiscard]] const Core::Hardware::InterruptManager& InterruptManager() const;
/// Provides a reference to the kernel instance.
- Kernel::KernelCore& Kernel();
+ [[nodiscard]] Kernel::KernelCore& Kernel();
/// Provides a constant reference to the kernel instance.
- const Kernel::KernelCore& Kernel() const;
+ [[nodiscard]] const Kernel::KernelCore& Kernel() const;
/// Provides a reference to the internal PerfStats instance.
- Core::PerfStats& GetPerfStats();
+ [[nodiscard]] Core::PerfStats& GetPerfStats();
/// Provides a constant reference to the internal PerfStats instance.
- const Core::PerfStats& GetPerfStats() const;
+ [[nodiscard]] const Core::PerfStats& GetPerfStats() const;
/// Provides a reference to the frame limiter;
- Core::FrameLimiter& FrameLimiter();
+ [[nodiscard]] Core::FrameLimiter& FrameLimiter();
/// Provides a constant referent to the frame limiter
- const Core::FrameLimiter& FrameLimiter() const;
+ [[nodiscard]] const Core::FrameLimiter& FrameLimiter() const;
/// Gets the name of the current game
- Loader::ResultStatus GetGameName(std::string& out) const;
+ [[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const;
void SetStatus(ResultStatus new_status, const char* details);
- const std::string& GetStatusDetails() const;
+ [[nodiscard]] const std::string& GetStatusDetails() const;
- Loader::AppLoader& GetAppLoader() const;
+ [[nodiscard]] Loader::AppLoader& GetAppLoader();
+ [[nodiscard]] const Loader::AppLoader& GetAppLoader() const;
- Service::SM::ServiceManager& ServiceManager();
- const Service::SM::ServiceManager& ServiceManager() const;
+ [[nodiscard]] Service::SM::ServiceManager& ServiceManager();
+ [[nodiscard]] const Service::SM::ServiceManager& ServiceManager() const;
- void SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs);
+ void SetFilesystem(FileSys::VirtualFilesystem vfs);
- std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
+ [[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const;
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
u64 main_region_size);
void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set);
-
void SetDefaultAppletFrontendSet();
- Service::AM::Applets::AppletManager& GetAppletManager();
-
- const Service::AM::Applets::AppletManager& GetAppletManager() const;
+ [[nodiscard]] Service::AM::Applets::AppletManager& GetAppletManager();
+ [[nodiscard]] const Service::AM::Applets::AppletManager& GetAppletManager() const;
void SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider);
- FileSys::ContentProvider& GetContentProvider();
-
- const FileSys::ContentProvider& GetContentProvider() const;
+ [[nodiscard]] FileSys::ContentProvider& GetContentProvider();
+ [[nodiscard]] const FileSys::ContentProvider& GetContentProvider() const;
- Service::FileSystem::FileSystemController& GetFileSystemController();
-
- const Service::FileSystem::FileSystemController& GetFileSystemController() const;
+ [[nodiscard]] Service::FileSystem::FileSystemController& GetFileSystemController();
+ [[nodiscard]] const Service::FileSystem::FileSystemController& GetFileSystemController() const;
void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
FileSys::ContentProvider* provider);
void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
- const Reporter& GetReporter() const;
-
- Service::Glue::ARPManager& GetARPManager();
-
- const Service::Glue::ARPManager& GetARPManager() const;
+ [[nodiscard]] const Reporter& GetReporter() const;
- Service::APM::Controller& GetAPMController();
+ [[nodiscard]] Service::Glue::ARPManager& GetARPManager();
+ [[nodiscard]] const Service::Glue::ARPManager& GetARPManager() const;
- const Service::APM::Controller& GetAPMController() const;
+ [[nodiscard]] Service::APM::Controller& GetAPMController();
+ [[nodiscard]] const Service::APM::Controller& GetAPMController() const;
- Service::LM::Manager& GetLogManager();
-
- const Service::LM::Manager& GetLogManager() const;
+ [[nodiscard]] Service::Time::TimeManager& GetTimeManager();
+ [[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
void SetExitLock(bool locked);
-
- bool GetExitLock() const;
+ [[nodiscard]] bool GetExitLock() const;
void SetCurrentProcessBuildID(const CurrentBuildProcessID& id);
-
- const CurrentBuildProcessID& GetCurrentProcessBuildID() const;
+ [[nodiscard]] const CurrentBuildProcessID& GetCurrentProcessBuildID() const;
/// Register a host thread as an emulated CPU Core.
void RegisterCoreThread(std::size_t id);
@@ -382,18 +369,27 @@ public:
void ExitDynarmicProfile();
/// Tells if system is running on multicore.
- bool IsMulticore() const;
+ [[nodiscard]] bool IsMulticore() const;
-private:
- System();
+ /// Type used for the frontend to designate a callback for System to re-launch the application
+ /// using a specified program index.
+ using ExecuteProgramCallback = std::function<void(std::size_t)>;
/**
- * Initialize the emulated system.
- * @param emu_window Reference to the host-system window used for video output and keyboard
- * input.
- * @return ResultStatus code, indicating if the operation succeeded.
+ * Registers a callback from the frontend for System to re-launch the application using a
+ * specified program index.
+ * @param callback Callback from the frontend to relaunch the application.
*/
- ResultStatus Init(Frontend::EmuWindow& emu_window);
+ void RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback);
+
+ /**
+ * Instructs the frontend to re-launch the application using the specified program_index.
+ * @param program_index Specifies the index within the application of the program to launch.
+ */
+ void ExecuteProgram(std::size_t program_index);
+
+private:
+ System();
struct Impl;
std::unique_ptr<Impl> impl;
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index e6c8461a5..874b5673a 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -49,6 +49,7 @@ void CoreTiming::ThreadEntry(CoreTiming& instance) {
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
instance.on_thread_init();
instance.ThreadLoop();
+ MicroProfileOnThreadExit();
}
void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index b0b6036e4..77ff4c6fe 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -27,8 +27,8 @@ using TimedCallback =
/// Contains the characteristics of a particular event.
struct EventType {
- EventType(TimedCallback&& callback, std::string&& name)
- : callback{std::move(callback)}, name{std::move(name)} {}
+ explicit EventType(TimedCallback&& callback_, std::string&& name_)
+ : callback{std::move(callback_)}, name{std::move(name_)} {}
/// The event's callback function.
TimedCallback callback;
@@ -67,8 +67,8 @@ public:
void Shutdown();
/// Sets if emulation is multicore or single core, must be set before Initialize
- void SetMulticore(bool is_multicore) {
- this->is_multicore = is_multicore;
+ void SetMulticore(bool is_multicore_) {
+ is_multicore = is_multicore_;
}
/// Check if it's using host timing.
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index ef0bae556..8f04fb8f5 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -4,16 +4,16 @@
#include "common/fiber.h"
#include "common/microprofile.h"
+#include "common/scope_exit.h"
#include "common/thread.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
-#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
#include "video_core/gpu.h"
namespace Core {
@@ -109,28 +109,26 @@ void* CpuManager::GetStartFuncParamater() {
void CpuManager::MultiCoreRunGuestThread() {
auto& kernel = system.Kernel();
- {
- auto& sched = kernel.CurrentScheduler();
- sched.OnThreadStart();
- }
+ kernel.CurrentScheduler()->OnThreadStart();
+ auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
+ auto& host_context = thread->GetHostContext();
+ host_context->SetRewindPoint(GuestRewindFunction, this);
MultiCoreRunGuestLoop();
}
void CpuManager::MultiCoreRunGuestLoop() {
auto& kernel = system.Kernel();
- auto* thread = kernel.CurrentScheduler().GetCurrentThread();
+
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
- auto& arm_interface = thread->ArmInterface();
system.EnterDynarmicProfile();
while (!physical_core->IsInterrupted()) {
- arm_interface.Run();
+ physical_core->Run();
physical_core = &kernel.CurrentPhysicalCore();
}
system.ExitDynarmicProfile();
- arm_interface.ClearExclusiveState();
- auto& scheduler = kernel.CurrentScheduler();
- scheduler.TryDoContextSwitch();
+ physical_core->ArmInterface().ClearExclusiveState();
+ kernel.CurrentScheduler()->RescheduleCurrentCore();
}
}
@@ -139,25 +137,21 @@ void CpuManager::MultiCoreRunIdleThread() {
while (true) {
auto& physical_core = kernel.CurrentPhysicalCore();
physical_core.Idle();
- auto& scheduler = kernel.CurrentScheduler();
- scheduler.TryDoContextSwitch();
+ kernel.CurrentScheduler()->RescheduleCurrentCore();
}
}
void CpuManager::MultiCoreRunSuspendThread() {
auto& kernel = system.Kernel();
- {
- auto& sched = kernel.CurrentScheduler();
- sched.OnThreadStart();
- }
+ kernel.CurrentScheduler()->OnThreadStart();
while (true) {
auto core = kernel.GetCurrentHostThreadID();
- auto& scheduler = kernel.CurrentScheduler();
- Kernel::Thread* current_thread = scheduler.GetCurrentThread();
+ auto& scheduler = *kernel.CurrentScheduler();
+ Kernel::KThread* current_thread = scheduler.GetCurrentThread();
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context);
ASSERT(scheduler.ContextSwitchPending());
ASSERT(core == kernel.GetCurrentHostThreadID());
- scheduler.TryDoContextSwitch();
+ scheduler.RescheduleCurrentCore();
}
}
@@ -205,32 +199,30 @@ void CpuManager::MultiCorePause(bool paused) {
void CpuManager::SingleCoreRunGuestThread() {
auto& kernel = system.Kernel();
- {
- auto& sched = kernel.CurrentScheduler();
- sched.OnThreadStart();
- }
+ kernel.CurrentScheduler()->OnThreadStart();
+ auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
+ auto& host_context = thread->GetHostContext();
+ host_context->SetRewindPoint(GuestRewindFunction, this);
SingleCoreRunGuestLoop();
}
void CpuManager::SingleCoreRunGuestLoop() {
auto& kernel = system.Kernel();
- auto* thread = kernel.CurrentScheduler().GetCurrentThread();
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
- auto& arm_interface = thread->ArmInterface();
system.EnterDynarmicProfile();
if (!physical_core->IsInterrupted()) {
- arm_interface.Run();
+ physical_core->Run();
physical_core = &kernel.CurrentPhysicalCore();
}
system.ExitDynarmicProfile();
- thread->SetPhantomMode(true);
+ kernel.SetIsPhantomModeForSingleCore(true);
system.CoreTiming().Advance();
- thread->SetPhantomMode(false);
- arm_interface.ClearExclusiveState();
+ kernel.SetIsPhantomModeForSingleCore(false);
+ physical_core->ArmInterface().ClearExclusiveState();
PreemptSingleCore();
auto& scheduler = kernel.Scheduler(current_core);
- scheduler.TryDoContextSwitch();
+ scheduler.RescheduleCurrentCore();
}
}
@@ -242,51 +234,53 @@ void CpuManager::SingleCoreRunIdleThread() {
system.CoreTiming().AddTicks(1000U);
idle_count++;
auto& scheduler = physical_core.Scheduler();
- scheduler.TryDoContextSwitch();
+ scheduler.RescheduleCurrentCore();
}
}
void CpuManager::SingleCoreRunSuspendThread() {
auto& kernel = system.Kernel();
- {
- auto& sched = kernel.CurrentScheduler();
- sched.OnThreadStart();
- }
+ kernel.CurrentScheduler()->OnThreadStart();
while (true) {
auto core = kernel.GetCurrentHostThreadID();
- auto& scheduler = kernel.CurrentScheduler();
- Kernel::Thread* current_thread = scheduler.GetCurrentThread();
+ auto& scheduler = *kernel.CurrentScheduler();
+ Kernel::KThread* current_thread = scheduler.GetCurrentThread();
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[0].host_context);
ASSERT(scheduler.ContextSwitchPending());
ASSERT(core == kernel.GetCurrentHostThreadID());
- scheduler.TryDoContextSwitch();
+ scheduler.RescheduleCurrentCore();
}
}
void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
- std::size_t old_core = current_core;
- auto& scheduler = system.Kernel().Scheduler(old_core);
- Kernel::Thread* current_thread = scheduler.GetCurrentThread();
- if (idle_count >= 4 || from_running_enviroment) {
- if (!from_running_enviroment) {
- system.CoreTiming().Idle();
- idle_count = 0;
+ {
+ auto& kernel = system.Kernel();
+ auto& scheduler = kernel.Scheduler(current_core);
+ Kernel::KThread* current_thread = scheduler.GetCurrentThread();
+ if (idle_count >= 4 || from_running_enviroment) {
+ if (!from_running_enviroment) {
+ system.CoreTiming().Idle();
+ idle_count = 0;
+ }
+ kernel.SetIsPhantomModeForSingleCore(true);
+ system.CoreTiming().Advance();
+ kernel.SetIsPhantomModeForSingleCore(false);
}
- current_thread->SetPhantomMode(true);
- system.CoreTiming().Advance();
- current_thread->SetPhantomMode(false);
+ current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
+ system.CoreTiming().ResetTicks();
+ scheduler.Unload(scheduler.GetCurrentThread());
+
+ auto& next_scheduler = kernel.Scheduler(current_core);
+ Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
}
- current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
- system.CoreTiming().ResetTicks();
- scheduler.Unload();
- auto& next_scheduler = system.Kernel().Scheduler(current_core);
- Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
- /// May have changed scheduler
- auto& current_scheduler = system.Kernel().Scheduler(current_core);
- current_scheduler.Reload();
- auto* currrent_thread2 = current_scheduler.GetCurrentThread();
- if (!currrent_thread2->IsIdleThread()) {
- idle_count = 0;
+
+ // May have changed scheduler
+ {
+ auto& scheduler = system.Kernel().Scheduler(current_core);
+ scheduler.Reload(scheduler.GetCurrentThread());
+ if (!scheduler.IsIdle()) {
+ idle_count = 0;
+ }
}
}
@@ -328,7 +322,7 @@ void CpuManager::RunThread(std::size_t core) {
system.RegisterCoreThread(core);
std::string name;
if (is_multicore) {
- name = "yuzu:CoreCPUThread_" + std::to_string(core);
+ name = "yuzu:CPUCore_" + std::to_string(core);
} else {
name = "yuzu:CPUThread";
}
@@ -343,6 +337,16 @@ void CpuManager::RunThread(std::size_t core) {
data.initialized = true;
const bool sc_sync = !is_async_gpu && !is_multicore;
bool sc_sync_first_use = sc_sync;
+
+ // Cleanup
+ SCOPE_EXIT({
+ data.host_context->Exit();
+ data.enter_barrier.reset();
+ data.exit_barrier.reset();
+ data.initialized = false;
+ MicroProfileOnThreadExit();
+ });
+
/// Running
while (running_mode) {
data.is_running = false;
@@ -351,8 +355,13 @@ void CpuManager::RunThread(std::size_t core) {
system.GPU().ObtainContext();
sc_sync_first_use = false;
}
- auto& scheduler = system.Kernel().CurrentScheduler();
- Kernel::Thread* current_thread = scheduler.GetCurrentThread();
+
+ // Abort if emulation was killed before the session really starts
+ if (!system.IsPoweredOn()) {
+ return;
+ }
+
+ auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
data.is_running = true;
Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext());
data.is_running = false;
@@ -360,11 +369,6 @@ void CpuManager::RunThread(std::size_t core) {
data.exit_barrier->Wait();
data.is_paused = false;
}
- /// Time to cleanup
- data.host_context->Exit();
- data.enter_barrier.reset();
- data.exit_barrier.reset();
- data.initialized = false;
}
} // namespace Core
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index dc591c730..ad116dcc0 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -23,7 +23,6 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
-#include "core/core.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
@@ -144,6 +143,7 @@ u64 GetSignatureTypeDataSize(SignatureType type) {
return 0x3C;
}
UNREACHABLE();
+ return 0;
}
u64 GetSignatureTypePaddingSize(SignatureType type) {
@@ -158,6 +158,7 @@ u64 GetSignatureTypePaddingSize(SignatureType type) {
return 0x40;
}
UNREACHABLE();
+ return 0;
}
SignatureType Ticket::GetSignatureType() const {
@@ -170,8 +171,7 @@ SignatureType Ticket::GetSignatureType() const {
if (const auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->sig_type;
}
-
- UNREACHABLE();
+ throw std::bad_variant_access{};
}
TicketData& Ticket::GetData() {
@@ -184,8 +184,7 @@ TicketData& Ticket::GetData() {
if (auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->data;
}
-
- UNREACHABLE();
+ throw std::bad_variant_access{};
}
const TicketData& Ticket::GetData() const {
@@ -198,8 +197,7 @@ const TicketData& Ticket::GetData() const {
if (const auto* ticket = std::get_if<ECDSATicket>(&data)) {
return ticket->data;
}
-
- UNREACHABLE();
+ throw std::bad_variant_access{};
}
u64 Ticket::GetSize() const {
@@ -412,7 +410,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
// Combine sources and seed
for (auto& source : sd_key_sources) {
for (std::size_t i = 0; i < source.size(); ++i) {
- source[i] ^= sd_seed[i & 0xF];
+ source[i] = static_cast<u8>(source[i] ^ sd_seed[i & 0xF]);
}
}
@@ -570,6 +568,11 @@ KeyManager::KeyManager() {
// Initialize keys
const std::string hactool_keys_dir = Common::FS::GetHactoolConfigurationPath();
const std::string yuzu_keys_dir = Common::FS::GetUserPath(Common::FS::UserPath::KeysDir);
+
+ if (!Common::FS::Exists(yuzu_keys_dir)) {
+ Common::FS::CreateDir(yuzu_keys_dir);
+ }
+
if (Settings::values.use_dev_keys) {
dev_mode = true;
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
@@ -1022,10 +1025,10 @@ void KeyManager::DeriveBase() {
}
}
-void KeyManager::DeriveETicket(PartitionDataManager& data) {
+void KeyManager::DeriveETicket(PartitionDataManager& data,
+ const FileSys::ContentProvider& provider) {
// ETicket keys
- const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
- 0x0100000000000033, FileSys::ContentRecordType::Program);
+ const auto es = provider.GetEntry(0x0100000000000033, FileSys::ContentRecordType::Program);
if (es == nullptr) {
return;
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 321b75323..0a7220286 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -20,6 +20,10 @@ namespace Common::FS {
class IOFile;
}
+namespace FileSys {
+class ContentProvider;
+}
+
namespace Loader {
enum class ResultStatus : u16;
}
@@ -252,7 +256,7 @@ public:
bool BaseDeriveNecessary() const;
void DeriveBase();
- void DeriveETicket(PartitionDataManager& data);
+ void DeriveETicket(PartitionDataManager& data, const FileSys::ContentProvider& provider);
void PopulateTickets();
void SynthesizeTickets();
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index 46136d04a..5f1c86a09 100644
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -26,6 +26,7 @@
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/vfs_vector.h"
+#include "core/loader/loader.h"
using Common::AsArray;
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 9ffda2e14..7c6304ff0 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -4,11 +4,10 @@
#include <fmt/format.h>
#include "common/file_util.h"
-#include "core/core.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/registered_cache.h"
-#include "core/settings.h"
+#include "core/file_sys/vfs.h"
namespace FileSys {
@@ -82,11 +81,11 @@ VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
}
}
-VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const {
+VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id,
+ VirtualFilesystem file_system) const {
auto& keys = Core::Crypto::KeyManager::Instance();
- Core::Crypto::PartitionDataManager pdm{
- Core::System::GetInstance().GetFilesystem()->OpenDirectory(
- Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)};
+ Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory(
+ Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)};
keys.PopulateFromPartitionData(pdm);
switch (id) {
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 8f0451c98..136485881 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -6,7 +6,8 @@
#include <memory>
-#include "core/file_sys/vfs.h"
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
@@ -51,7 +52,7 @@ public:
VirtualDir GetModificationDumpRoot(u64 title_id) const;
VirtualDir OpenPartition(BisPartitionId id) const;
- VirtualFile OpenPartitionStorage(BisPartitionId id) const;
+ VirtualFile OpenPartitionStorage(BisPartitionId id, VirtualFilesystem file_system) const;
VirtualDir GetImageDirectory() const;
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 664a47e7f..8dee5590b 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -8,11 +8,11 @@
#include <fmt/ostream.h>
#include "common/logging/log.h"
+#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
-#include "core/file_sys/romfs.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_offset.h"
@@ -29,9 +29,10 @@ constexpr std::array partition_names{
"logo",
};
-XCI::XCI(VirtualFile file_)
+XCI::XCI(VirtualFile file_, std::size_t program_index)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
- partitions(partition_names.size()), partitions_raw(partition_names.size()) {
+ partitions(partition_names.size()),
+ partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
status = Loader::ResultStatus::ErrorBadXCIHeader;
return;
@@ -61,7 +62,8 @@ XCI::XCI(VirtualFile file_)
}
secure_partition = std::make_shared<NSP>(
- main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]));
+ main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]),
+ program_index);
ncas = secure_partition->GetNCAsCollapsed();
program =
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index e1b136426..4960e90fe 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -9,9 +9,12 @@
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/crypto/key_manager.h"
#include "core/file_sys/vfs.h"
+namespace Core::Crypto {
+class KeyManager;
+}
+
namespace Loader {
enum class ResultStatus : u16;
}
@@ -75,7 +78,7 @@ enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
class XCI : public ReadOnlyVfsDirectory {
public:
- explicit XCI(VirtualFile file);
+ explicit XCI(VirtualFile file, std::size_t program_index = 0);
~XCI() override;
Loader::ResultStatus GetStatus() const;
@@ -140,6 +143,6 @@ private:
u64 update_normal_partition_end;
- Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/file_sys/common_funcs.h b/src/core/file_sys/common_funcs.h
new file mode 100644
index 000000000..7ed97aa50
--- /dev/null
+++ b/src/core/file_sys/common_funcs.h
@@ -0,0 +1,56 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace FileSys {
+
+constexpr u64 AOC_TITLE_ID_MASK = 0x7FF;
+constexpr u64 AOC_TITLE_ID_OFFSET = 0x1000;
+constexpr u64 BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
+
+/**
+ * Gets the base title ID from a given title ID.
+ *
+ * @param title_id The title ID.
+ * @returns The base title ID.
+ */
+[[nodiscard]] constexpr u64 GetBaseTitleID(u64 title_id) {
+ return title_id & BASE_TITLE_ID_MASK;
+}
+
+/**
+ * Gets the base title ID with a program index offset from a given title ID.
+ *
+ * @param title_id The title ID.
+ * @param program_index The program index.
+ * @returns The base title ID with a program index offset.
+ */
+[[nodiscard]] constexpr u64 GetBaseTitleIDWithProgramIndex(u64 title_id, u64 program_index) {
+ return GetBaseTitleID(title_id) + program_index;
+}
+
+/**
+ * Gets the AOC (Add-On Content) base title ID from a given title ID.
+ *
+ * @param title_id The title ID.
+ * @returns The AOC base title ID.
+ */
+[[nodiscard]] constexpr u64 GetAOCBaseTitleID(u64 title_id) {
+ return GetBaseTitleID(title_id) + AOC_TITLE_ID_OFFSET;
+}
+
+/**
+ * Gets the AOC (Add-On Content) ID from a given AOC title ID.
+ *
+ * @param aoc_title_id The AOC title ID.
+ * @returns The AOC ID.
+ */
+[[nodiscard]] constexpr u64 GetAOCID(u64 aoc_title_id) {
+ return aoc_title_id & AOC_TITLE_ID_MASK;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 5039341c7..d12218fc2 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -10,10 +10,10 @@
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/ctr_encryption_layer.h"
+#include "core/crypto/key_manager.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_patch.h"
#include "core/file_sys/partition_filesystem.h"
-#include "core/file_sys/romfs.h"
#include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.h"
@@ -43,17 +43,17 @@ static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
struct IVFCHeader {
u32_le magic;
u32_le magic_number;
- INSERT_UNION_PADDING_BYTES(8);
+ INSERT_PADDING_BYTES_NOINIT(8);
std::array<IVFCLevel, 6> levels;
- INSERT_UNION_PADDING_BYTES(64);
+ INSERT_PADDING_BYTES_NOINIT(64);
};
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
struct NCASectionHeaderBlock {
- INSERT_UNION_PADDING_BYTES(3);
+ INSERT_PADDING_BYTES_NOINIT(3);
NCASectionFilesystemType filesystem_type;
NCASectionCryptoType crypto_type;
- INSERT_UNION_PADDING_BYTES(3);
+ INSERT_PADDING_BYTES_NOINIT(3);
};
static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
@@ -61,7 +61,7 @@ struct NCASectionRaw {
NCASectionHeaderBlock header;
std::array<u8, 0x138> block_data;
std::array<u8, 0x8> section_ctr;
- INSERT_UNION_PADDING_BYTES(0xB8);
+ INSERT_PADDING_BYTES_NOINIT(0xB8);
};
static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
@@ -69,19 +69,19 @@ struct PFS0Superblock {
NCASectionHeaderBlock header_block;
std::array<u8, 0x20> hash;
u32_le size;
- INSERT_UNION_PADDING_BYTES(4);
+ INSERT_PADDING_BYTES_NOINIT(4);
u64_le hash_table_offset;
u64_le hash_table_size;
u64_le pfs0_header_offset;
u64_le pfs0_size;
- INSERT_UNION_PADDING_BYTES(0x1B0);
+ INSERT_PADDING_BYTES_NOINIT(0x1B0);
};
static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
struct RomFSSuperblock {
NCASectionHeaderBlock header_block;
IVFCHeader ivfc;
- INSERT_UNION_PADDING_BYTES(0x118);
+ INSERT_PADDING_BYTES_NOINIT(0x118);
};
static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
@@ -89,19 +89,19 @@ struct BKTRHeader {
u64_le offset;
u64_le size;
u32_le magic;
- INSERT_UNION_PADDING_BYTES(0x4);
+ INSERT_PADDING_BYTES_NOINIT(0x4);
u32_le number_entries;
- INSERT_UNION_PADDING_BYTES(0x4);
+ INSERT_PADDING_BYTES_NOINIT(0x4);
};
static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
struct BKTRSuperblock {
NCASectionHeaderBlock header_block;
IVFCHeader ivfc;
- INSERT_UNION_PADDING_BYTES(0x18);
+ INSERT_PADDING_BYTES_NOINIT(0x18);
BKTRHeader relocation;
BKTRHeader subsection;
- INSERT_UNION_PADDING_BYTES(0xC0);
+ INSERT_PADDING_BYTES_NOINIT(0xC0);
};
static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
@@ -119,7 +119,8 @@ static bool IsValidNCA(const NCAHeader& header) {
}
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
- : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
+ : file(std::move(file_)),
+ bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
return;
@@ -322,7 +323,7 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
subsection_buckets.back().entries.push_back({size, {0}, 0});
- std::optional<Core::Crypto::Key128> key = {};
+ std::optional<Core::Crypto::Key128> key;
if (encrypted) {
if (has_rights_id) {
status = Loader::ResultStatus::Success;
@@ -409,8 +410,9 @@ u8 NCA::GetCryptoRevision() const {
std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
const auto master_key_id = GetCryptoRevision();
- if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
- return {};
+ if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) {
+ return std::nullopt;
+ }
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -419,15 +421,17 @@ std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
Core::Crypto::Key128 out;
- if (type == NCASectionCryptoType::XTS)
+ if (type == NCASectionCryptoType::XTS) {
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
- else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR)
+ } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) {
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
- else
+ } else {
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
- static_cast<u8>(type));
+ type);
+ }
+
u128 out_128{};
- memcpy(out_128.data(), out.data(), 16);
+ std::memcpy(out_128.data(), out.data(), sizeof(u128));
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
master_key_id, header.key_index, out_128[1], out_128[0]);
@@ -441,18 +445,18 @@ std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
memcpy(rights_id.data(), header.rights_id.data(), 16);
if (rights_id == u128{}) {
status = Loader::ResultStatus::ErrorInvalidRightsID;
- return {};
+ return std::nullopt;
}
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
if (titlekey == Core::Crypto::Key128{}) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
- return {};
+ return std::nullopt;
}
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
- return {};
+ return std::nullopt;
}
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -476,7 +480,7 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
case NCASectionCryptoType::BKTR:
LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
- std::optional<Core::Crypto::Key128> key = {};
+ std::optional<Core::Crypto::Key128> key;
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
@@ -506,7 +510,7 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
// TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
default:
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
- static_cast<u8>(s_header.raw.header.crypto_type));
+ s_header.raw.header.crypto_type);
return nullptr;
}
}
@@ -515,15 +519,17 @@ Loader::ResultStatus NCA::GetStatus() const {
return status;
}
-std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const {
- if (status != Loader::ResultStatus::Success)
+std::vector<VirtualFile> NCA::GetFiles() const {
+ if (status != Loader::ResultStatus::Success) {
return {};
+ }
return files;
}
-std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const {
- if (status != Loader::ResultStatus::Success)
+std::vector<VirtualDir> NCA::GetSubdirectories() const {
+ if (status != Loader::ResultStatus::Success) {
return {};
+ }
return dirs;
}
@@ -531,7 +537,7 @@ std::string NCA::GetName() const {
return file->GetName();
}
-std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const {
+VirtualDir NCA::GetParentDirectory() const {
return file->GetContainingDirectory();
}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index d25cbcf91..e9eccdea3 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -82,7 +82,7 @@ struct NCAHeader {
};
static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
-inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
+inline bool IsDirectoryExeFS(const VirtualDir& pfs) {
// According to switchbrew, an exefs must only contain these two files:
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
}
@@ -104,10 +104,10 @@ public:
Loader::ResultStatus GetStatus() const;
- std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
- std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
std::string GetName() const override;
- std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
+ VirtualDir GetParentDirectory() const override;
NCAContentType GetType() const;
u64 GetTitleId() const;
@@ -158,7 +158,7 @@ private:
bool encrypted = false;
bool is_update = false;
- Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 63cd2eead..b0a130345 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -5,6 +5,7 @@
#include "common/string_util.h"
#include "common/swap.h"
#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/vfs.h"
namespace FileSys {
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index e37b2fadf..403c4219a 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -10,7 +10,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
@@ -83,7 +83,7 @@ enum class Language : u8 {
Italian = 7,
Dutch = 8,
CanadianFrench = 9,
- Portugese = 10,
+ Portuguese = 10,
Russian = 11,
Korean = 12,
Taiwanese = 13,
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index 2aff2708a..c52fafb6f 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -266,8 +266,9 @@ std::multimap<u64, VirtualFile> RomFSBuildContext::Build() {
cur_file->offset = file_partition_size;
file_partition_size += cur_file->size;
cur_file->entry_offset = entry_offset;
- entry_offset += sizeof(RomFSFileEntry) +
- Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4);
+ entry_offset +=
+ static_cast<u32>(sizeof(RomFSFileEntry) +
+ Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4));
prev_file = cur_file;
}
// Assign deferred parent/sibling ownership.
@@ -284,8 +285,9 @@ std::multimap<u64, VirtualFile> RomFSBuildContext::Build() {
for (const auto& it : directories) {
cur_dir = it.second;
cur_dir->entry_offset = entry_offset;
- entry_offset += sizeof(RomFSDirectoryEntry) +
- Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4);
+ entry_offset +=
+ static_cast<u32>(sizeof(RomFSDirectoryEntry) +
+ Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4));
}
// Assign deferred parent/sibling ownership.
for (auto it = directories.rbegin(); it->second != root; ++it) {
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index a08a70efd..a6101f1c0 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -245,9 +245,11 @@ void IPSwitchCompiler::Parse() {
// Read rest of patch
while (true) {
- if (i + 1 >= lines.size())
+ if (i + 1 >= lines.size()) {
break;
- const auto patch_line = lines[++i];
+ }
+
+ const auto& patch_line = lines[++i];
// Start of new patch
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
@@ -297,7 +299,7 @@ void IPSwitchCompiler::Parse() {
patch_text->GetName(), offset, Common::HexToString(replace));
}
- patch.records.insert_or_assign(offset, std::move(replace));
+ patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace));
}
patches.push_back(std::move(patch));
diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp
index 76313679d..ef93ef3ed 100644
--- a/src/core/file_sys/kernel_executable.cpp
+++ b/src/core/file_sys/kernel_executable.cpp
@@ -2,9 +2,12 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cstring>
+
#include "common/string_util.h"
#include "core/file_sys/kernel_executable.h"
#include "core/file_sys/vfs_offset.h"
+#include "core/loader/loader.h"
namespace FileSys {
diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h
index 324a57384..044c554d3 100644
--- a/src/core/file_sys/kernel_executable.h
+++ b/src/core/file_sys/kernel_executable.h
@@ -4,10 +4,17 @@
#pragma once
+#include <array>
+#include <vector>
+
#include "common/common_funcs.h"
+#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs_types.h"
-#include "core/loader/loader.h"
+
+namespace Loader {
+enum class ResultStatus : u16;
+}
namespace FileSys {
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index 93d0df6b9..3596541b2 100644
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/vfs.h"
namespace FileSys {
@@ -107,7 +108,7 @@ std::vector<u8> CNMT::Serialize() const {
memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
}
- auto offset = header.table_offset;
+ u64_le offset = header.table_offset;
for (const auto& rec : content_records) {
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 1f82fff0a..53535e5f5 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -10,7 +10,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
class CNMT;
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
index fe7375e84..a65ec6798 100644
--- a/src/core/file_sys/nca_patch.cpp
+++ b/src/core/file_sys/nca_patch.cpp
@@ -12,6 +12,49 @@
#include "core/file_sys/nca_patch.h"
namespace FileSys {
+namespace {
+template <bool Subsection, typename BlockType, typename BucketType>
+std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block,
+ const BucketType& buckets) {
+ if constexpr (Subsection) {
+ const auto& last_bucket = buckets[block.number_buckets - 1];
+ if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) {
+ return {block.number_buckets - 1, last_bucket.number_entries};
+ }
+ } else {
+ ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
+ }
+
+ std::size_t bucket_id = std::count_if(
+ block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
+ [&offset](u64 base_offset) { return base_offset <= offset; });
+
+ const auto& bucket = buckets[bucket_id];
+
+ if (bucket.number_entries == 1) {
+ return {bucket_id, 0};
+ }
+
+ std::size_t low = 0;
+ std::size_t mid = 0;
+ std::size_t high = bucket.number_entries - 1;
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if (bucket.entries[mid].address_patch > offset) {
+ high = mid - 1;
+ } else {
+ if (mid == bucket.number_entries - 1 ||
+ bucket.entries[mid + 1].address_patch > offset) {
+ return {bucket_id, mid};
+ }
+
+ low = mid + 1;
+ }
+ }
+ UNREACHABLE_MSG("Offset could not be found in BKTR block.");
+ return {0, 0};
+}
+} // Anonymous namespace
BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
@@ -110,46 +153,6 @@ std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
return raw_read;
}
-template <bool Subsection, typename BlockType, typename BucketType>
-std::pair<std::size_t, std::size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
- BucketType buckets) const {
- if constexpr (Subsection) {
- const auto last_bucket = buckets[block.number_buckets - 1];
- if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
- return {block.number_buckets - 1, last_bucket.number_entries};
- } else {
- ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
- }
-
- std::size_t bucket_id = std::count_if(
- block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
- [&offset](u64 base_offset) { return base_offset <= offset; });
-
- const auto bucket = buckets[bucket_id];
-
- if (bucket.number_entries == 1)
- return {bucket_id, 0};
-
- std::size_t low = 0;
- std::size_t mid = 0;
- std::size_t high = bucket.number_entries - 1;
- while (low <= high) {
- mid = (low + high) / 2;
- if (bucket.entries[mid].address_patch > offset) {
- high = mid - 1;
- } else {
- if (mid == bucket.number_entries - 1 ||
- bucket.entries[mid + 1].address_patch > offset) {
- return {bucket_id, mid};
- }
-
- low = mid + 1;
- }
- }
-
- UNREACHABLE_MSG("Offset could not be found in BKTR block.");
-}
-
RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
return relocation_buckets[res.first].entries[res.second];
@@ -188,7 +191,7 @@ bool BKTR::Resize(std::size_t new_size) {
return false;
}
-std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const {
+VirtualDir BKTR::GetContainingDirectory() const {
return base_romfs->GetContainingDirectory();
}
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h
index 8e64e8378..503cf473e 100644
--- a/src/core/file_sys/nca_patch.h
+++ b/src/core/file_sys/nca_patch.h
@@ -106,7 +106,7 @@ public:
bool Resize(std::size_t new_size) override;
- std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
+ VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
@@ -117,10 +117,6 @@ public:
bool Rename(std::string_view name) override;
private:
- template <bool Subsection, typename BlockType, typename BucketType>
- std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, BlockType block,
- BucketType buckets) const;
-
RelocationEntry GetRelocationEntry(u64 offset) const;
RelocationEntry GetNextRelocationEntry(u64 offset) const;
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 846986736..48a2ed4d4 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -21,7 +21,7 @@ bool PartitionFilesystem::Header::HasValidMagicValue() const {
magic == Common::MakeMagic('P', 'F', 'S', '0');
}
-PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
+PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
// At least be as large as the header
if (file->GetSize() < sizeof(Header)) {
status = Loader::ResultStatus::ErrorBadPFSHeader;
@@ -89,11 +89,11 @@ std::map<std::string, u64> PartitionFilesystem::GetFileSizes() const {
return sizes;
}
-std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
+std::vector<VirtualFile> PartitionFilesystem::GetFiles() const {
return pfs_files;
}
-std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
+std::vector<VirtualDir> PartitionFilesystem::GetSubdirectories() const {
return {};
}
@@ -101,7 +101,7 @@ std::string PartitionFilesystem::GetName() const {
return is_hfs ? "HFS0" : "PFS0";
}
-std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const {
+VirtualDir PartitionFilesystem::GetParentDirectory() const {
// TODO(DarkLordZach): Add support for nested containers.
return nullptr;
}
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 279193b19..0f831148e 100644
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.h
@@ -24,7 +24,7 @@ namespace FileSys {
*/
class PartitionFilesystem : public ReadOnlyVfsDirectory {
public:
- explicit PartitionFilesystem(std::shared_ptr<VfsFile> file);
+ explicit PartitionFilesystem(VirtualFile file);
~PartitionFilesystem() override;
Loader::ResultStatus GetStatus() const;
@@ -32,10 +32,10 @@ public:
std::map<std::string, u64> GetFileOffsets() const;
std::map<std::string, u64> GetFileSizes() const;
- std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
- std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
std::string GetName() const override;
- std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
+ VirtualDir GetParentDirectory() const override;
void PrintDebugInfo() const;
private:
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 729dbb5f4..7c3284df8 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -12,6 +12,7 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/ips_layer.h"
@@ -27,30 +28,38 @@
#include "core/settings.h"
namespace FileSys {
+namespace {
-constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
-constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
+constexpr u32 SINGLE_BYTE_MODULUS = 0x100;
constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
"main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2",
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
};
-std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
+enum class TitleVersionFormat : u8 {
+ ThreeElements, ///< vX.Y.Z
+ FourElements, ///< vX.Y.Z.W
+};
+
+std::string FormatTitleVersion(u32 version,
+ TitleVersionFormat format = TitleVersionFormat::ThreeElements) {
std::array<u8, sizeof(u32)> bytes{};
- bytes[0] = version % SINGLE_BYTE_MODULUS;
+ bytes[0] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
for (std::size_t i = 1; i < bytes.size(); ++i) {
version /= SINGLE_BYTE_MODULUS;
- bytes[i] = version % SINGLE_BYTE_MODULUS;
+ bytes[i] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
}
- if (format == TitleVersionFormat::FourElements)
+ if (format == TitleVersionFormat::FourElements) {
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
+ }
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
}
-std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<VfsDirectory> dir,
- std::string_view name) {
+// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
+// doesn't have a directory with name.
+VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
#ifdef _WIN32
return dir->GetSubdirectory(name);
#else
@@ -66,7 +75,47 @@ std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<Vfs
#endif
}
-PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
+std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
+ u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
+ const auto build_id_raw = Common::HexToString(build_id_, upper);
+ const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
+ const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
+
+ if (file == nullptr) {
+ LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
+ title_id, build_id);
+ return std::nullopt;
+ }
+
+ std::vector<u8> data(file->GetSize());
+ if (file->Read(data.data(), data.size()) != data.size()) {
+ LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
+ title_id, build_id);
+ return std::nullopt;
+ }
+
+ const Core::Memory::TextCheatParser parser;
+ return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
+}
+
+void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
+ if (to.empty()) {
+ to += with;
+ } else {
+ to += ", ";
+ to += with;
+ }
+}
+
+bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
+ return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
+}
+} // Anonymous namespace
+
+PatchManager::PatchManager(u64 title_id_,
+ const Service::FileSystem::FileSystemController& fs_controller_,
+ const ContentProvider& content_provider_)
+ : title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
PatchManager::~PatchManager() = default;
@@ -82,34 +131,30 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (Settings::values.dump_exefs) {
LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
- const auto dump_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
+ const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id);
if (dump_dir != nullptr) {
const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
VfsRawCopyD(exefs, exefs_dir);
}
}
- const auto& installed = Core::System::GetInstance().GetContentProvider();
-
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- const auto update = installed.GetEntry(update_tid, ContentRecordType::Program);
+ const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
- FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
+ FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
exefs = update->GetExeFS();
}
// LayeredExeFS
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir != nullptr && load_dir->GetSize() > 0) {
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(
@@ -195,8 +240,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
if (Settings::values.dump_nso) {
LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
title_id);
- const auto dump_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id);
+ const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id);
if (dump_dir != nullptr) {
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
@@ -208,8 +252,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return nso;
@@ -246,14 +289,13 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
return out;
}
-bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
+bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
const auto build_id_raw = Common::HexToString(build_id_);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return false;
@@ -266,37 +308,9 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
return !CollectPatches(patch_dirs, build_id).empty();
}
-namespace {
-std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
- const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
- const VirtualDir& base_path, bool upper) {
- const auto build_id_raw = Common::HexToString(build_id_, upper);
- const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
- const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
-
- if (file == nullptr) {
- LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
- title_id, build_id);
- return std::nullopt;
- }
-
- std::vector<u8> data(file->GetSize());
- if (file->Read(data.data(), data.size()) != data.size()) {
- LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
- title_id, build_id);
- return std::nullopt;
- }
-
- Core::Memory::TextCheatParser parser;
- return parser.Parse(system,
- std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
-}
-
-} // Anonymous namespace
-
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
- const Core::System& system, const std::array<u8, 32>& build_id_) const {
- const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
+ const BuildID& build_id_) const {
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return {};
@@ -315,14 +329,12 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
if (cheats_dir != nullptr) {
- auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
- if (res.has_value()) {
+ if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
std::copy(res->begin(), res->end(), std::back_inserter(out));
continue;
}
- res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
- if (res.has_value()) {
+ if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
std::copy(res->begin(), res->end(), std::back_inserter(out));
}
}
@@ -331,9 +343,9 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
return out;
}
-static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
+ const Service::FileSystem::FileSystemController& fs_controller) {
+ const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
load_dir == nullptr || load_dir->GetSize() <= 0) {
return;
@@ -395,19 +407,19 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
title_id, static_cast<u8>(type));
- if (type == ContentRecordType::Program || type == ContentRecordType::Data)
+ if (type == ContentRecordType::Program || type == ContentRecordType::Data) {
LOG_INFO(Loader, "{}", log_string);
- else
+ } else {
LOG_DEBUG(Loader, "{}", log_string);
+ }
- if (romfs == nullptr)
+ if (romfs == nullptr) {
return romfs;
-
- const auto& installed = Core::System::GetInstance().GetContentProvider();
+ }
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- const auto update = installed.GetEntryRaw(update_tid, type);
+ const auto update = content_provider.GetEntryRaw(update_tid, type);
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
@@ -418,7 +430,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
- FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
+ FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
}
} else if (!update_disabled && update_raw != nullptr) {
@@ -431,33 +443,22 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
}
// LayeredFS
- ApplyLayeredFS(romfs, title_id, type);
+ ApplyLayeredFS(romfs, title_id, type, fs_controller);
return romfs;
}
-static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
- if (to.empty())
- to += with;
- else
- to += ", " + with;
-}
-
-static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
- return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
-}
-
-std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
- VirtualFile update_raw) const {
- if (title_id == 0)
+PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const {
+ if (title_id == 0) {
return {};
+ }
+
std::map<std::string, std::string, std::less<>> out;
- const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- PatchManager update{update_tid};
+ PatchManager update{update_tid, fs_controller, content_provider};
const auto metadata = update.GetControlMetadata();
const auto& nacp = metadata.first;
@@ -468,13 +469,12 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (nacp != nullptr) {
out.insert_or_assign(update_label, nacp->GetVersionString());
} else {
- if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
- const auto meta_ver = installed.GetEntryVersion(update_tid);
+ if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
+ const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
out.insert_or_assign(update_label, "");
} else {
- out.insert_or_assign(
- update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
+ out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver));
}
} else if (update_raw != nullptr) {
out.insert_or_assign(update_label, "PACKED");
@@ -482,8 +482,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
// General Mods (LayeredFS and IPS)
- const auto mod_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+ const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
for (const auto& mod : mod_dir->GetSubdirectories()) {
std::string types;
@@ -527,13 +526,15 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
// DLC
- const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
+ const auto dlc_entries =
+ content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
std::vector<ContentProviderEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
- [this, &installed](const ContentProviderEntry& entry) {
- return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
- installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
+ [this](const ContentProviderEntry& entry) {
+ return GetBaseTitleID(entry.title_id) == title_id &&
+ content_provider.GetEntry(entry)->GetStatus() ==
+ Loader::ResultStatus::Success;
});
if (!dlc_match.empty()) {
// Ensure sorted so DLC IDs show in order.
@@ -554,49 +555,52 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
std::optional<u32> PatchManager::GetGameVersion() const {
- const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto update_tid = GetUpdateTitleID(title_id);
- if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
- return installed.GetEntryVersion(update_tid);
+ if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
+ return content_provider.GetEntryVersion(update_tid);
}
- return installed.GetEntryVersion(title_id);
+ return content_provider.GetEntryVersion(title_id);
}
-std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
- const auto& installed = Core::System::GetInstance().GetContentProvider();
-
- const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
- if (base_control_nca == nullptr)
+PatchManager::Metadata PatchManager::GetControlMetadata() const {
+ const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
+ if (base_control_nca == nullptr) {
return {};
+ }
return ParseControlNCA(*base_control_nca);
}
-std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
+PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
const auto base_romfs = nca.GetRomFS();
- if (base_romfs == nullptr)
+ if (base_romfs == nullptr) {
return {};
+ }
const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
- if (romfs == nullptr)
+ if (romfs == nullptr) {
return {};
+ }
const auto extracted = ExtractRomFS(romfs);
- if (extracted == nullptr)
+ if (extracted == nullptr) {
return {};
+ }
auto nacp_file = extracted->GetFile("control.nacp");
- if (nacp_file == nullptr)
+ if (nacp_file == nullptr) {
nacp_file = extracted->GetFile("Control.nacp");
+ }
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
VirtualFile icon_file;
for (const auto& language : FileSys::LANGUAGE_NAMES) {
- icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat");
- if (icon_file != nullptr)
+ icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat"));
+ if (icon_file != nullptr) {
break;
+ }
}
return {std::move(nacp), icon_file};
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index f4cb918dd..fb1853035 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -6,88 +6,89 @@
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include "common/common_types.h"
#include "core/file_sys/nca_metadata.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
#include "core/memory/dmnt_cheat_types.h"
namespace Core {
class System;
}
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace FileSys {
+class ContentProvider;
class NCA;
class NACP;
-enum class TitleVersionFormat : u8 {
- ThreeElements, ///< vX.Y.Z
- FourElements, ///< vX.Y.Z.W
-};
-
-std::string FormatTitleVersion(u32 version,
- TitleVersionFormat format = TitleVersionFormat::ThreeElements);
-
-// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
-// doesn't have a directory with name.
-std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<VfsDirectory> dir,
- std::string_view name);
-
// A centralized class to manage patches to games.
class PatchManager {
public:
- explicit PatchManager(u64 title_id);
+ using BuildID = std::array<u8, 0x20>;
+ using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>;
+ using PatchVersionNames = std::map<std::string, std::string, std::less<>>;
+
+ explicit PatchManager(u64 title_id_,
+ const Service::FileSystem::FileSystemController& fs_controller_,
+ const ContentProvider& content_provider_);
~PatchManager();
- u64 GetTitleID() const;
+ [[nodiscard]] u64 GetTitleID() const;
// Currently tracked ExeFS patches:
// - Game Updates
- VirtualDir PatchExeFS(VirtualDir exefs) const;
+ [[nodiscard]] VirtualDir PatchExeFS(VirtualDir exefs) const;
// Currently tracked NSO patches:
// - IPS
// - IPSwitch
- std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const;
+ [[nodiscard]] std::vector<u8> PatchNSO(const std::vector<u8>& nso,
+ const std::string& name) const;
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
// Used to prevent expensive copies in NSO loader.
- bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
+ [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
// Creates a CheatList object with all
- std::vector<Core::Memory::CheatEntry> CreateCheatList(
- const Core::System& system, const std::array<u8, 0x20>& build_id) const;
+ [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
+ const BuildID& build_id) const;
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
- VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
- ContentRecordType type = ContentRecordType::Program,
- VirtualFile update_raw = nullptr) const;
+ [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
+ ContentRecordType type = ContentRecordType::Program,
+ VirtualFile update_raw = nullptr) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
- std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
- VirtualFile update_raw = nullptr) const;
+ [[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const;
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
// std::nullopt
- std::optional<u32> GetGameVersion() const;
+ [[nodiscard]] std::optional<u32> GetGameVersion() const;
// Given title_id of the program, attempts to get the control data of the update and parse
// it, falling back to the base control data.
- std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
+ [[nodiscard]] Metadata GetControlMetadata() const;
// Version of GetControlMetadata that takes an arbitrary NCA
- std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
+ [[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
private:
- std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
- const std::string& build_id) const;
+ [[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
+ const std::string& build_id) const;
u64 title_id;
+ const Service::FileSystem::FileSystemController& fs_controller;
+ const ContentProvider& content_provider;
};
} // namespace FileSys
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 43169bf9f..9cf49bf44 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -7,6 +7,7 @@
#include "common/logging/log.h"
#include "core/file_sys/program_metadata.h"
+#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace FileSys {
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 35069972b..455532567 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -9,7 +9,7 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
namespace Loader {
enum class ResultStatus : u16;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index da01002d5..431302f55 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -105,7 +105,8 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
return ContentRecordType::HtmlDocument;
default:
- UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type));
+ UNREACHABLE_MSG("Invalid NCAContentType={:02X}", type);
+ return ContentRecordType{};
}
}
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 5b414b0f0..b08a1687a 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -67,18 +67,18 @@ public:
virtual void Refresh() = 0;
virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0;
- virtual bool HasEntry(ContentProviderEntry entry) const;
+ bool HasEntry(ContentProviderEntry entry) const;
virtual std::optional<u32> GetEntryVersion(u64 title_id) const = 0;
virtual VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const = 0;
- virtual VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const;
+ VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const;
virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0;
- virtual VirtualFile GetEntryRaw(ContentProviderEntry entry) const;
+ VirtualFile GetEntryRaw(ContentProviderEntry entry) const;
virtual std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const = 0;
- virtual std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const;
+ std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const;
virtual std::vector<ContentProviderEntry> ListEntries() const;
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 2fd07ed04..82e683782 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -4,7 +4,6 @@
#pragma once
-#include <array>
#include "core/file_sys/vfs.h"
namespace FileSys {
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 418a39a7e..f4e16e4be 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -6,8 +6,8 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/file_sys/card_image.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -19,7 +19,9 @@
namespace FileSys {
-RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
+RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider,
+ Service::FileSystem::FileSystemController& controller)
+ : content_provider{provider}, filesystem_controller{controller} {
// Load the RomFS from the app
if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
LOG_ERROR(Service_FS, "Unable to read RomFS!");
@@ -36,49 +38,71 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
}
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const {
- if (!updatable)
+ if (!updatable) {
return MakeResult<VirtualFile>(file);
+ }
- const PatchManager patch_manager(current_process_title_id);
+ const PatchManager patch_manager{current_process_title_id, filesystem_controller,
+ content_provider};
return MakeResult<VirtualFile>(
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
}
-ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
- ContentRecordType type) const {
- std::shared_ptr<NCA> res;
+ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
+ auto nca = content_provider.GetEntry(title_id, type);
- switch (storage) {
- case StorageId::None:
- res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
- break;
- case StorageId::NandSystem:
- res =
- Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry(
- title_id, type);
- break;
- case StorageId::NandUser:
- res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry(
- title_id, type);
- break;
- case StorageId::SdCard:
- res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry(
- title_id, type);
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
+ if (nca == nullptr) {
+ // TODO: Find the right error code to use here
+ return RESULT_UNKNOWN;
}
+ const PatchManager patch_manager{title_id, filesystem_controller, content_provider};
+
+ return MakeResult<VirtualFile>(
+ patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type));
+}
+
+ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFSWithProgramIndex(
+ u64 title_id, u8 program_index, ContentRecordType type) const {
+ const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index);
+
+ return OpenPatchedRomFS(res_title_id, type);
+}
+
+ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
+ ContentRecordType type) const {
+ const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type);
if (res == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return RESULT_UNKNOWN;
}
+
const auto romfs = res->GetRomFS();
if (romfs == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return RESULT_UNKNOWN;
}
+
return MakeResult<VirtualFile>(romfs);
}
+std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage,
+ ContentRecordType type) const {
+ switch (storage) {
+ case StorageId::None:
+ return content_provider.GetEntry(title_id, type);
+ case StorageId::NandSystem:
+ return filesystem_controller.GetSystemNANDContents()->GetEntry(title_id, type);
+ case StorageId::NandUser:
+ return filesystem_controller.GetUserNANDContents()->GetEntry(title_id, type);
+ case StorageId::SdCard:
+ return filesystem_controller.GetSDMCContents()->GetEntry(title_id, type);
+ case StorageId::Host:
+ case StorageId::GameCard:
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
+ return nullptr;
+ }
+}
+
} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index c5d40285c..96dd0d578 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -13,8 +13,15 @@ namespace Loader {
class AppLoader;
} // namespace Loader
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace FileSys {
+class ContentProvider;
+class NCA;
+
enum class ContentRecordType : u8;
enum class StorageId : u8 {
@@ -29,18 +36,30 @@ enum class StorageId : u8 {
/// File system interface to the RomFS archive
class RomFSFactory {
public:
- explicit RomFSFactory(Loader::AppLoader& app_loader);
+ explicit RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider,
+ Service::FileSystem::FileSystemController& controller);
~RomFSFactory();
void SetPackedUpdate(VirtualFile update_raw);
- ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
- ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const;
+ [[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
+ [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFS(u64 title_id,
+ ContentRecordType type) const;
+ [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFSWithProgramIndex(
+ u64 title_id, u8 program_index, ContentRecordType type) const;
+ [[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage,
+ ContentRecordType type) const;
private:
+ [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage,
+ ContentRecordType type) const;
+
VirtualFile file;
VirtualFile update_raw;
bool updatable;
u64 ivfc_offset;
+
+ ContentProvider& content_provider;
+ Service::FileSystem::FileSystemController& filesystem_controller;
};
} // namespace FileSys
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index ba4efee3a..b7bfe0928 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -70,7 +70,8 @@ std::string SaveDataAttribute::DebugInfo() const {
static_cast<u8>(rank), index);
}
-SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
+SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_directory_)
+ : dir{std::move(save_directory_)}, system{system_} {
// Delete all temporary storages
// On hardware, it is expected that temporary storage be empty at first use.
dir->DeleteSubdirectoryRecursive("temp");
@@ -83,7 +84,7 @@ ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space,
PrintSaveDataAttributeWarnings(meta);
const auto save_directory =
- GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
+ GetFullPath(system, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
auto out = dir->CreateDirectoryRelative(save_directory);
@@ -100,7 +101,7 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space,
const SaveDataAttribute& meta) const {
const auto save_directory =
- GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
+ GetFullPath(system, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
auto out = dir->GetDirectoryRelative(save_directory);
@@ -135,13 +136,14 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
}
}
-std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
- u128 user_id, u64 save_id) {
+std::string SaveDataFactory::GetFullPath(Core::System& system, SaveDataSpaceId space,
+ SaveDataType type, u64 title_id, u128 user_id,
+ u64 save_id) {
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
// be interpreted as the title id of the current process.
if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) {
if (title_id == 0) {
- title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ title_id = system.CurrentProcess()->GetTitleID();
}
}
@@ -167,7 +169,7 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
u128 user_id) const {
- const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
+ const auto path = GetFullPath(system, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
const auto dir = GetOrCreateDirectoryRelative(this->dir, path);
const auto size_file = dir->GetFile(SAVE_DATA_SIZE_FILENAME);
@@ -182,7 +184,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
SaveDataSize new_value) const {
- const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
+ const auto path = GetFullPath(system, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
const auto dir = GetOrCreateDirectoryRelative(this->dir, path);
const auto size_file = dir->CreateFile(SAVE_DATA_SIZE_FILENAME);
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index 6625bbbd8..86c9f5350 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -12,6 +12,10 @@
#include "core/file_sys/vfs.h"
#include "core/hle/result.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
enum class SaveDataSpaceId : u8 {
@@ -54,7 +58,7 @@ struct SaveDataAttribute {
SaveDataType type;
SaveDataRank rank;
u16 index;
- INSERT_PADDING_BYTES(4);
+ INSERT_PADDING_BYTES_NOINIT(4);
u64 zero_1;
u64 zero_2;
u64 zero_3;
@@ -68,7 +72,7 @@ struct SaveDataExtraData {
u64 owner_id;
s64 timestamp;
SaveDataFlags flags;
- INSERT_PADDING_BYTES(4);
+ INSERT_PADDING_BYTES_NOINIT(4);
s64 available_size;
s64 journal_size;
s64 commit_id;
@@ -84,7 +88,7 @@ struct SaveDataSize {
/// File system interface to the SaveData archive
class SaveDataFactory {
public:
- explicit SaveDataFactory(VirtualDir dir);
+ explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_);
~SaveDataFactory();
ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
@@ -93,8 +97,8 @@ public:
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space);
- static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
- u128 user_id, u64 save_id);
+ static std::string GetFullPath(Core::System& system, SaveDataSpaceId space, SaveDataType type,
+ u64 title_id, u128 user_id, u64 save_id);
SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const;
void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
@@ -102,6 +106,7 @@ public:
private:
VirtualDir dir;
+ Core::System& system;
};
} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index 6f732e4d8..cb56d8f2d 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -5,8 +5,8 @@
#include <memory>
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/sdmc_factory.h"
+#include "core/file_sys/vfs.h"
#include "core/file_sys/xts_archive.h"
-#include "core/settings.h"
namespace FileSys {
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 42dc4e08a..2bb92ba93 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -5,7 +5,7 @@
#pragma once
#include <memory>
-#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_types.h"
#include "core/hle/result.h"
namespace FileSys {
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 175a8266a..c05735ddd 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -19,42 +19,10 @@
#include "core/loader/loader.h"
namespace FileSys {
-namespace {
-void SetTicketKeys(const std::vector<VirtualFile>& files) {
- auto& keys = Core::Crypto::KeyManager::Instance();
- for (const auto& ticket_file : files) {
- if (ticket_file == nullptr) {
- continue;
- }
-
- if (ticket_file->GetExtension() != "tik") {
- continue;
- }
-
- if (ticket_file->GetSize() <
- Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
- continue;
- }
-
- Core::Crypto::Key128 key{};
- ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
-
- // We get the name without the extension in order to create the rights ID.
- std::string name_only(ticket_file->GetName());
- name_only.erase(name_only.size() - 4);
-
- const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
- u128 rights_id;
- std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
- keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
- }
-}
-} // Anonymous namespace
-
-NSP::NSP(VirtualFile file_)
- : file(std::move(file_)), status{Loader::ResultStatus::Success},
- pfs(std::make_shared<PartitionFilesystem>(file)) {
+NSP::NSP(VirtualFile file_, std::size_t program_index)
+ : file(std::move(file_)), program_index(program_index), status{Loader::ResultStatus::Success},
+ pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} {
if (pfs->GetStatus() != Loader::ResultStatus::Success) {
status = pfs->GetStatus();
return;
@@ -178,7 +146,7 @@ std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType
if (extracted)
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
- const auto title_id_iter = ncas.find(title_id);
+ const auto title_id_iter = ncas.find(title_id + program_index);
if (title_id_iter == ncas.end())
return nullptr;
@@ -232,6 +200,35 @@ VirtualDir NSP::GetParentDirectory() const {
return file->GetContainingDirectory();
}
+void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) {
+ for (const auto& ticket_file : files) {
+ if (ticket_file == nullptr) {
+ continue;
+ }
+
+ if (ticket_file->GetExtension() != "tik") {
+ continue;
+ }
+
+ if (ticket_file->GetSize() <
+ Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
+ continue;
+ }
+
+ Core::Crypto::Key128 key{};
+ ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
+
+ // We get the name without the extension in order to create the rights ID.
+ std::string name_only(ticket_file->GetName());
+ name_only.erase(name_only.size() - 4);
+
+ const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
+ u128 rights_id;
+ std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
+ keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ }
+}
+
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
exefs = pfs;
@@ -267,9 +264,9 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
}
const CNMT cnmt(inner_file);
- auto& ncas_title = ncas[cnmt.GetTitleID()];
- ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca;
+ ncas[cnmt.GetTitleID()][{cnmt.GetType(), ContentRecordType::Meta}] = nca;
+
for (const auto& rec : cnmt.GetContentRecords()) {
const auto id_string = Common::HexToString(rec.nca_id, false);
auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
@@ -286,13 +283,32 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
}
auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0);
+
if (next_nca->GetType() == NCAContentType::Program) {
- program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
+ program_status[next_nca->GetTitleId()] = next_nca->GetStatus();
}
- if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
- (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
- (cnmt.GetTitleID() & 0x800) != 0)) {
- ncas_title[{cnmt.GetType(), rec.type}] = std::move(next_nca);
+
+ if (next_nca->GetStatus() != Loader::ResultStatus::Success &&
+ next_nca->GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ continue;
+ }
+
+ // If the last 3 hexadecimal digits of the CNMT TitleID is 0x800 or is missing the
+ // BKTRBaseRomFS, this is an update NCA. Otherwise, this is a base NCA.
+ if ((cnmt.GetTitleID() & 0x800) != 0 ||
+ next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ // If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and
+ // 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular
+ // update NCA.
+ if ((next_nca->GetTitleId() & 0x7FF) != 0 &&
+ (next_nca->GetTitleId() & 0x800) == 0) {
+ ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
+ std::move(next_nca);
+ } else {
+ ncas[cnmt.GetTitleID()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
+ }
+ } else {
+ ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
}
}
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index cf89de6a9..54581a6f3 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -10,6 +10,10 @@
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
+namespace Core::Crypto {
+class KeyManager;
+}
+
namespace Loader {
enum class ResultStatus : u16;
}
@@ -23,7 +27,7 @@ enum class ContentRecordType : u8;
class NSP : public ReadOnlyVfsDirectory {
public:
- explicit NSP(VirtualFile file);
+ explicit NSP(VirtualFile file, std::size_t program_index = 0);
~NSP() override;
Loader::ResultStatus GetStatus() const;
@@ -59,11 +63,14 @@ public:
VirtualDir GetParentDirectory() const override;
private:
+ void SetTicketKeys(const std::vector<VirtualFile>& files);
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
void ReadNCAs(const std::vector<VirtualFile>& files);
VirtualFile file;
+ const std::size_t program_index;
+
bool extracted = false;
Loader::ResultStatus status;
std::map<u64, Loader::ResultStatus> program_status;
@@ -73,7 +80,7 @@ private:
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
std::vector<VirtualFile> ticket_files;
- Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::KeyManager& keys;
VirtualFile romfs;
VirtualDir exefs;
diff --git a/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp b/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp
index 69d62ce8f..29ef110a6 100644
--- a/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp
+++ b/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp
@@ -6,191 +6,384 @@
namespace FileSys::SystemArchive::SharedFontData {
-const std::array<unsigned char, 2932> FONT_NINTENDO_EXTENDED{{
- 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x80, 0x00, 0x03, 0x00, 0x70, 0x44, 0x53, 0x49, 0x47,
- 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0b, 0x6c, 0x00, 0x00, 0x00, 0x08, 0x4f, 0x53, 0x2f, 0x32,
- 0x33, 0x86, 0x1d, 0x9b, 0x00, 0x00, 0x01, 0x78, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6d, 0x61, 0x70,
- 0xc2, 0x06, 0x20, 0xde, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x63, 0x76, 0x74, 0x20,
- 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x04, 0x2c, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6d,
- 0x06, 0x59, 0x9c, 0x37, 0x00, 0x00, 0x02, 0xa0, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70,
- 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x0b, 0x64, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6c, 0x79, 0x66,
- 0x10, 0x31, 0x88, 0x00, 0x00, 0x00, 0x04, 0x34, 0x00, 0x00, 0x04, 0x64, 0x68, 0x65, 0x61, 0x64,
- 0x15, 0x9d, 0xef, 0x91, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61,
- 0x09, 0x60, 0x03, 0x71, 0x00, 0x00, 0x01, 0x34, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6d, 0x74, 0x78,
- 0x0d, 0x2e, 0x03, 0xa7, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x26, 0x6c, 0x6f, 0x63, 0x61,
- 0x05, 0xc0, 0x04, 0x6c, 0x00, 0x00, 0x08, 0x98, 0x00, 0x00, 0x00, 0x1e, 0x6d, 0x61, 0x78, 0x70,
- 0x02, 0x1c, 0x00, 0x5f, 0x00, 0x00, 0x01, 0x58, 0x00, 0x00, 0x00, 0x20, 0x6e, 0x61, 0x6d, 0x65,
- 0x7c, 0xe0, 0x84, 0x5c, 0x00, 0x00, 0x08, 0xb8, 0x00, 0x00, 0x02, 0x09, 0x70, 0x6f, 0x73, 0x74,
- 0x47, 0x4e, 0x74, 0x19, 0x00, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x9e, 0x70, 0x72, 0x65, 0x70,
- 0x1c, 0xfc, 0x7d, 0x9c, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x01, 0x00, 0x00, 0x7c, 0xc7, 0xb1, 0x63, 0x5f, 0x0f, 0x3c, 0xf5, 0x00, 0x1b, 0x03, 0xe8,
- 0x00, 0x00, 0x00, 0x00, 0xd9, 0x44, 0x2f, 0x5d, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x45, 0x7b, 0x69,
- 0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x84, 0xff, 0x83, 0x01, 0xf4, 0x03, 0xe8,
- 0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x5e,
- 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x74, 0x01, 0x90, 0x00, 0x05,
- 0x00, 0x04, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00, 0x01, 0x1f, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00,
- 0x03, 0xc3, 0x00, 0x66, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+const std::array<unsigned char, 6024> FONT_NINTENDO_EXTENDED{{
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x80, 0x00, 0x03, 0x00, 0x60, 0x4F, 0x53, 0x2F, 0x32,
+ 0x34, 0x00, 0x1E, 0x26, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6D, 0x61, 0x70,
+ 0xC1, 0xE7, 0xC8, 0xF3, 0x00, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x01, 0x72, 0x63, 0x76, 0x74, 0x20,
+ 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6D,
+ 0x06, 0x59, 0x9C, 0x37, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x80, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6C, 0x79, 0x66,
+ 0x50, 0x0B, 0xEA, 0xFA, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x0F, 0x04, 0x68, 0x65, 0x61, 0x64,
+ 0x18, 0x65, 0x81, 0x09, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61,
+ 0x09, 0x88, 0x03, 0x86, 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6D, 0x74, 0x78,
+ 0x0A, 0xF0, 0x01, 0x94, 0x00, 0x00, 0x01, 0xC8, 0x00, 0x00, 0x00, 0x42, 0x6C, 0x6F, 0x63, 0x61,
+ 0x34, 0x80, 0x30, 0x6E, 0x00, 0x00, 0x05, 0x14, 0x00, 0x00, 0x00, 0x3A, 0x6D, 0x61, 0x78, 0x70,
+ 0x02, 0x2C, 0x00, 0x72, 0x00, 0x00, 0x01, 0x48, 0x00, 0x00, 0x00, 0x20, 0x6E, 0x61, 0x6D, 0x65,
+ 0xDB, 0xC5, 0x42, 0x4D, 0x00, 0x00, 0x14, 0x54, 0x00, 0x00, 0x01, 0xFE, 0x70, 0x6F, 0x73, 0x74,
+ 0xF4, 0xB4, 0xAC, 0xAB, 0x00, 0x00, 0x16, 0x54, 0x00, 0x00, 0x01, 0x2A, 0x70, 0x72, 0x65, 0x70,
+ 0x1C, 0xFC, 0x7D, 0x9C, 0x00, 0x00, 0x04, 0xF4, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0xC9, 0x16, 0x5B, 0x71, 0x5F, 0x0F, 0x3C, 0xF5, 0x00, 0x0B, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xD9, 0x44, 0x2F, 0x5D, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x02, 0x0D, 0xA7,
+ 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x9A, 0xFF, 0x80, 0x02, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0xEC, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x71,
+ 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0xC4, 0x01, 0x90, 0x00, 0x05,
+ 0x00, 0x04, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00, 0x01, 0x26, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00,
+ 0x03, 0xDA, 0x00, 0x68, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xc0, 0x00, 0x00, 0xe0, 0xe9, 0x03, 0x84, 0xff, 0x83,
- 0x01, 0xf4, 0x02, 0xee, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
- 0x02, 0xbc, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x03, 0xe8, 0x00, 0xeb, 0x01, 0x21, 0x00, 0xff,
- 0x00, 0xff, 0x01, 0x3d, 0x01, 0x17, 0x00, 0x42, 0x00, 0x1c, 0x00, 0x3e, 0x00, 0x17, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x68, 0x00, 0x01, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x1c, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x06, 0x00, 0x4c,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xC0, 0x00, 0x0D, 0xE0, 0xF0, 0x03, 0x9A, 0xFF, 0x80,
+ 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
+ 0x02, 0xCD, 0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0x04, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
+ 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
+ 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
+ 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C,
+ 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x00, 0x50, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA9, 0xE0, 0xB4,
+ 0xE0, 0xE9, 0xE0, 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA0,
+ 0xE0, 0xB3, 0xE0, 0xE0, 0xE0, 0xEF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xF5, 0xFF, 0xE3, 0x1F, 0x64,
+ 0x1F, 0x5B, 0x1F, 0x30, 0x1F, 0x2B, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0a,
- 0x00, 0x08, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe9, 0xff, 0xff,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe0, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf5,
- 0xff, 0xe3, 0x1f, 0x24, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xb8, 0x00, 0x00, 0x2c, 0x4b, 0xb8, 0x00, 0x09, 0x50, 0x58, 0xb1, 0x01, 0x01, 0x8e, 0x59, 0xb8,
- 0x01, 0xff, 0x85, 0xb8, 0x00, 0x44, 0x1d, 0xb9, 0x00, 0x09, 0x00, 0x03, 0x5f, 0x5e, 0x2d, 0xb8,
- 0x00, 0x01, 0x2c, 0x20, 0x20, 0x45, 0x69, 0x44, 0xb0, 0x01, 0x60, 0x2d, 0xb8, 0x00, 0x02, 0x2c,
- 0xb8, 0x00, 0x01, 0x2a, 0x21, 0x2d, 0xb8, 0x00, 0x03, 0x2c, 0x20, 0x46, 0xb0, 0x03, 0x25, 0x46,
- 0x52, 0x58, 0x23, 0x59, 0x20, 0x8a, 0x20, 0x8a, 0x49, 0x64, 0x8a, 0x20, 0x46, 0x20, 0x68, 0x61,
- 0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8a, 0x59, 0x2f,
- 0x20, 0xb0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x59, 0x1b,
- 0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x65, 0x59, 0x59, 0x3a, 0x2d, 0xb8, 0x00,
- 0x04, 0x2c, 0x20, 0x46, 0xb0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8a, 0x59, 0x20, 0x46, 0x20,
- 0x6a, 0x61, 0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x6a, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8a, 0x59,
- 0x2f, 0xfd, 0x2d, 0xb8, 0x00, 0x05, 0x2c, 0x4b, 0x20, 0xb0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58,
- 0xb0, 0x80, 0x44, 0x1b, 0xb0, 0x40, 0x44, 0x59, 0x1b, 0x21, 0x21, 0x20, 0x45, 0xb0, 0xc0, 0x50,
- 0x58, 0xb0, 0xc0, 0x44, 0x1b, 0x21, 0x59, 0x59, 0x2d, 0xb8, 0x00, 0x06, 0x2c, 0x20, 0x20, 0x45,
- 0x69, 0x44, 0xb0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0xb0, 0x01, 0x60, 0x2d,
- 0xb8, 0x00, 0x07, 0x2c, 0xb8, 0x00, 0x06, 0x2a, 0x2d, 0xb8, 0x00, 0x08, 0x2c, 0x4b, 0x20, 0xb0,
- 0x03, 0x26, 0x53, 0x58, 0xb0, 0x40, 0x1b, 0xb0, 0x00, 0x59, 0x8a, 0x8a, 0x20, 0xb0, 0x03, 0x26,
- 0x53, 0x58, 0x23, 0x21, 0xb0, 0x80, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03, 0x26,
- 0x53, 0x58, 0x23, 0x21, 0xb8, 0x00, 0xc0, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03,
- 0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x00, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0,
- 0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x40, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20,
- 0xb8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xb0, 0x03, 0x25, 0x45, 0xb8, 0x01, 0x80, 0x50, 0x58, 0x23,
- 0x21, 0xb8, 0x01, 0x80, 0x23, 0x21, 0x1b, 0xb0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59,
- 0x1b, 0x21, 0x59, 0x44, 0x2d, 0xb8, 0x00, 0x09, 0x2c, 0x4b, 0x53, 0x58, 0x45, 0x44, 0x1b, 0x21,
- 0x21, 0x59, 0x2d, 0x00, 0xb8, 0x00, 0x00, 0x2b, 0x00, 0xba, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07,
- 0x2b, 0xb8, 0x00, 0x00, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x06,
- 0x00, 0x00, 0x35, 0x01, 0x33, 0x15, 0x01, 0x23, 0x35, 0x03, 0x52, 0x94, 0xfc, 0xa6, 0x8c, 0x90,
- 0x03, 0x58, 0x86, 0xfc, 0xa0, 0x8e, 0x00, 0x00, 0x00, 0x02, 0x00, 0xeb, 0x00, 0xcc, 0x02, 0xfb,
- 0x03, 0x1e, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x33, 0x13, 0x23, 0x27, 0x23, 0x07, 0x23,
- 0x13, 0x17, 0x07, 0x06, 0x15, 0x33, 0x27, 0x07, 0x01, 0xbc, 0x6d, 0xd2, 0x7c, 0x26, 0xcc, 0x26,
- 0x7c, 0xd1, 0x35, 0x40, 0x02, 0x89, 0x45, 0x02, 0x03, 0x1e, 0xfd, 0xae, 0x77, 0x77, 0x02, 0x52,
- 0x9b, 0xcc, 0x08, 0x04, 0xda, 0x02, 0x00, 0x00, 0x00, 0x03, 0x01, 0x21, 0x00, 0xcc, 0x02, 0xc5,
- 0x03, 0x1e, 0x00, 0x15, 0x00, 0x1f, 0x00, 0x2b, 0x00, 0x00, 0x25, 0x11, 0x33, 0x32, 0x1e, 0x02,
- 0x15, 0x14, 0x0e, 0x02, 0x07, 0x1e, 0x01, 0x15, 0x14, 0x0e, 0x02, 0x2b, 0x01, 0x13, 0x33, 0x32,
- 0x36, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x1d, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b,
- 0x01, 0x15, 0x01, 0x21, 0xea, 0x25, 0x3f, 0x2e, 0x1a, 0x0e, 0x15, 0x1b, 0x0e, 0x2d, 0x2d, 0x1a,
- 0x2e, 0x3f, 0x25, 0xf8, 0x76, 0x62, 0x20, 0x2a, 0x28, 0x22, 0x62, 0x76, 0x10, 0x18, 0x11, 0x09,
- 0x22, 0x22, 0x74, 0xcc, 0x02, 0x52, 0x18, 0x2b, 0x3c, 0x24, 0x1d, 0x1f, 0x17, 0x17, 0x14, 0x0f,
- 0x48, 0x2f, 0x24, 0x3f, 0x2e, 0x1a, 0x01, 0x5b, 0x29, 0x20, 0x20, 0x2b, 0x94, 0xf8, 0x0e, 0x16,
- 0x1c, 0x0e, 0x1f, 0x31, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7,
- 0x03, 0x1e, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x33, 0x17, 0x37, 0x33, 0x03, 0x13, 0x23, 0x27, 0x07,
- 0x23, 0x13, 0x03, 0x01, 0x04, 0x86, 0x69, 0x69, 0x86, 0xa3, 0xa8, 0x88, 0x6c, 0x6c, 0x88, 0xa8,
- 0xa3, 0x03, 0x1e, 0xcb, 0xcb, 0xfe, 0xda, 0xfe, 0xd4, 0xcf, 0xcf, 0x01, 0x2c, 0x01, 0x26, 0x00,
- 0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7, 0x03, 0x1e, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x03,
- 0x33, 0x17, 0x32, 0x15, 0x1e, 0x01, 0x15, 0x1b, 0x01, 0x33, 0x03, 0x15, 0x23, 0x35, 0x01, 0xb8,
- 0xb9, 0x7e, 0x01, 0x01, 0x01, 0x03, 0x70, 0x75, 0x7f, 0xb9, 0x76, 0x01, 0xa3, 0x01, 0x7b, 0x01,
- 0x01, 0x01, 0x05, 0x02, 0xff, 0x00, 0x01, 0x0a, 0xfe, 0x85, 0xd7, 0xd7, 0x00, 0x01, 0x01, 0x3d,
- 0x00, 0xcc, 0x02, 0xa9, 0x03, 0x1e, 0x00, 0x06, 0x00, 0x00, 0x25, 0x11, 0x33, 0x11, 0x33, 0x15,
- 0x21, 0x01, 0x3d, 0x75, 0xf7, 0xfe, 0x94, 0xcc, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00,
- 0x00, 0x02, 0x01, 0x17, 0x00, 0xbc, 0x02, 0xcf, 0x03, 0x0e, 0x00, 0x15, 0x00, 0x21, 0x00, 0x00,
- 0x25, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x1d, 0x01, 0x0e, 0x03, 0x1d, 0x01, 0x17, 0x15, 0x23, 0x27,
- 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x15, 0x01, 0x17,
- 0xf4, 0x27, 0x40, 0x2e, 0x19, 0x01, 0x1f, 0x24, 0x1e, 0x78, 0x7d, 0x6a, 0x5c, 0x75, 0x76, 0x72,
- 0x12, 0x19, 0x11, 0x08, 0x26, 0x26, 0x6a, 0xbc, 0x02, 0x52, 0x1d, 0x31, 0x42, 0x25, 0x16, 0x18,
- 0x32, 0x2a, 0x1b, 0x02, 0x01, 0xef, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x10, 0x1a, 0x1e, 0x0f, 0x23,
- 0x36, 0xb0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x42, 0x00, 0xbc, 0x03, 0xa4, 0x03, 0x0e, 0x00, 0x0a,
- 0x00, 0x11, 0x00, 0x00, 0x13, 0x35, 0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01,
- 0x11, 0x33, 0x11, 0x33, 0x15, 0x21, 0x42, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b, 0xfe, 0x53, 0x01,
- 0x15, 0xfe, 0xeb, 0x01, 0xf7, 0x75, 0xf6, 0xfe, 0x95, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62,
- 0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c,
- 0x00, 0xbc, 0x03, 0xca, 0x03, 0x0e, 0x00, 0x0a, 0x00, 0x21, 0x00, 0x2f, 0x00, 0x00, 0x13, 0x35,
- 0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15,
- 0x14, 0x06, 0x07, 0x0e, 0x03, 0x15, 0x17, 0x15, 0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32,
- 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01, 0x15, 0x1c, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b,
- 0xfe, 0x53, 0x01, 0x15, 0xfe, 0xeb, 0x01, 0xf7, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x01,
- 0x0d, 0x0e, 0x0a, 0x78, 0x7d, 0x69, 0x5c, 0x75, 0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14,
- 0x1d, 0x13, 0x69, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62, 0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02,
- 0x52, 0x1d, 0x31, 0x42, 0x25, 0x2b, 0x44, 0x1d, 0x01, 0x08, 0x09, 0x07, 0x01, 0xf1, 0x06, 0xd7,
- 0xd7, 0x01, 0x3f, 0x11, 0x19, 0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x02, 0x00, 0x3e,
- 0x00, 0xb3, 0x03, 0xa8, 0x03, 0x17, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x00, 0x13, 0x34, 0x3e, 0x02,
- 0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x27, 0x2e, 0x01, 0x23, 0x22, 0x0e, 0x02, 0x15,
- 0x14, 0x16, 0x15, 0x1e, 0x05, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e, 0x02, 0x35, 0x33, 0x1e,
- 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x04, 0x35, 0x01, 0x11, 0x33, 0x11, 0x33, 0x15,
- 0x21, 0x50, 0x24, 0x3b, 0x4a, 0x27, 0x28, 0x4b, 0x39, 0x22, 0x73, 0x01, 0x01, 0x08, 0x2b, 0x29,
- 0x10, 0x20, 0x19, 0x0f, 0x01, 0x0b, 0x35, 0x41, 0x46, 0x3b, 0x25, 0x23, 0x3a, 0x4b, 0x27, 0x2b,
- 0x50, 0x3f, 0x26, 0x74, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x11, 0x2c, 0x42, 0x4d, 0x42, 0x2c,
- 0x01, 0xef, 0x73, 0xf6, 0xfe, 0x97, 0x02, 0x70, 0x2a, 0x3f, 0x2a, 0x14, 0x18, 0x2e, 0x44, 0x2c,
- 0x02, 0x03, 0x01, 0x27, 0x27, 0x07, 0x10, 0x1a, 0x12, 0x02, 0x0b, 0x02, 0x1f, 0x22, 0x19, 0x17,
- 0x27, 0x3f, 0x34, 0x2c, 0x3e, 0x28, 0x13, 0x1a, 0x32, 0x48, 0x2e, 0x30, 0x30, 0x06, 0x0f, 0x1a,
- 0x13, 0x21, 0x27, 0x1e, 0x1b, 0x29, 0x3e, 0x31, 0xfe, 0x4c, 0x02, 0x53, 0xfe, 0x10, 0x63, 0x00,
- 0x00, 0x03, 0x00, 0x17, 0x00, 0xb3, 0x03, 0xce, 0x03, 0x17, 0x00, 0x38, 0x00, 0x4f, 0x00, 0x5d,
- 0x00, 0x00, 0x13, 0x34, 0x3e, 0x02, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x23, 0x2e,
- 0x01, 0x23, 0x22, 0x0e, 0x02, 0x15, 0x14, 0x1e, 0x04, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e,
- 0x02, 0x35, 0x33, 0x1e, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x27, 0x2e, 0x03, 0x35,
- 0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x14, 0x06, 0x07, 0x30, 0x0e, 0x02, 0x31, 0x17, 0x15,
- 0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01,
- 0x15, 0x2a, 0x24, 0x3a, 0x4a, 0x26, 0x29, 0x4b, 0x39, 0x23, 0x73, 0x01, 0x01, 0x08, 0x2a, 0x2a,
- 0x10, 0x1f, 0x1a, 0x10, 0x2c, 0x42, 0x4d, 0x42, 0x2c, 0x23, 0x39, 0x4b, 0x27, 0x2b, 0x51, 0x3f,
- 0x27, 0x75, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x10, 0x1f, 0x1c, 0x25, 0x53, 0x47, 0x2e, 0x01,
- 0xed, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x0c, 0x0e, 0x0c, 0x78, 0x7d, 0x68, 0x5d, 0x75,
- 0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14, 0x1d, 0x13, 0x69, 0x02, 0x71, 0x2a, 0x3e, 0x2a,
- 0x14, 0x18, 0x2e, 0x44, 0x2c, 0x02, 0x02, 0x27, 0x29, 0x07, 0x11, 0x1a, 0x12, 0x1d, 0x24, 0x1c,
- 0x1d, 0x2b, 0x40, 0x32, 0x2c, 0x3f, 0x29, 0x13, 0x1a, 0x31, 0x49, 0x2e, 0x30, 0x30, 0x06, 0x0f,
- 0x19, 0x13, 0x1e, 0x22, 0x0b, 0x0e, 0x20, 0x2f, 0x43, 0x30, 0xfe, 0x4b, 0x02, 0x52, 0x1d, 0x32,
- 0x42, 0x25, 0x2c, 0x42, 0x1d, 0x08, 0x0a, 0x08, 0xf1, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x11, 0x19,
- 0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12,
- 0x00, 0x12, 0x00, 0x32, 0x00, 0x72, 0x00, 0x8e, 0x00, 0xac, 0x00, 0xbe, 0x00, 0xf0, 0x01, 0x14,
- 0x01, 0x5c, 0x01, 0xb6, 0x02, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0xa2, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x02, 0x00, 0x07, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x2f,
- 0x00, 0x17, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x46, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x58, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x06, 0x00, 0x12, 0x00, 0x65, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x01, 0x00, 0x20,
- 0x00, 0x77, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x97, 0x00, 0x03,
- 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x5e, 0x00, 0xa5, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
- 0x00, 0x04, 0x00, 0x24, 0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x05, 0x00, 0x1a,
- 0x01, 0x27, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x24, 0x01, 0x41, 0x00, 0x03,
- 0x00, 0x01, 0x04, 0x09, 0x00, 0x11, 0x00, 0x02, 0x01, 0x65, 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53,
- 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61,
- 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x3b, 0x3b,
- 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
- 0x2d, 0x52, 0x3b, 0x32, 0x30, 0x31, 0x39, 0x3b, 0x46, 0x4c, 0x56, 0x49, 0x2d, 0x36, 0x31, 0x34,
- 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
- 0x20, 0x52, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x59,
- 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2d,
- 0x52, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00,
- 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
- 0x6e, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x72, 0x00,
- 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
- 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3b, 0x00, 0x3b, 0x00, 0x59, 0x00,
- 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00,
- 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00,
- 0x52, 0x00, 0x3b, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x39, 0x00, 0x3b, 0x00, 0x46, 0x00,
- 0x4c, 0x00, 0x56, 0x00, 0x49, 0x00, 0x2d, 0x00, 0x36, 0x00, 0x31, 0x00, 0x34, 0x00, 0x59, 0x00,
- 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00,
- 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
- 0x52, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
- 0x20, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x59, 0x00, 0x75, 0x00,
- 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00,
- 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x52, 0x00,
- 0x52, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x9c, 0x00, 0x32,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04,
- 0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0a, 0x01, 0x0b, 0x01, 0x0c,
- 0x01, 0x0d, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30,
- 0x30, 0x44, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
- 0x45, 0x31, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
- 0x45, 0x33, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
- 0x45, 0x35, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
- 0x45, 0x37, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
- 0x45, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xB8, 0x00, 0x00, 0x2C, 0x4B, 0xB8, 0x00, 0x09, 0x50, 0x58, 0xB1, 0x01, 0x01, 0x8E, 0x59, 0xB8,
+ 0x01, 0xFF, 0x85, 0xB8, 0x00, 0x44, 0x1D, 0xB9, 0x00, 0x09, 0x00, 0x03, 0x5F, 0x5E, 0x2D, 0xB8,
+ 0x00, 0x01, 0x2C, 0x20, 0x20, 0x45, 0x69, 0x44, 0xB0, 0x01, 0x60, 0x2D, 0xB8, 0x00, 0x02, 0x2C,
+ 0xB8, 0x00, 0x01, 0x2A, 0x21, 0x2D, 0xB8, 0x00, 0x03, 0x2C, 0x20, 0x46, 0xB0, 0x03, 0x25, 0x46,
+ 0x52, 0x58, 0x23, 0x59, 0x20, 0x8A, 0x20, 0x8A, 0x49, 0x64, 0x8A, 0x20, 0x46, 0x20, 0x68, 0x61,
+ 0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8A, 0x59, 0x2F,
+ 0x20, 0xB0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x59, 0x1B,
+ 0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x65, 0x59, 0x59, 0x3A, 0x2D, 0xB8, 0x00,
+ 0x04, 0x2C, 0x20, 0x46, 0xB0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8A, 0x59, 0x20, 0x46, 0x20,
+ 0x6A, 0x61, 0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x6A, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8A, 0x59,
+ 0x2F, 0xFD, 0x2D, 0xB8, 0x00, 0x05, 0x2C, 0x4B, 0x20, 0xB0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58,
+ 0xB0, 0x80, 0x44, 0x1B, 0xB0, 0x40, 0x44, 0x59, 0x1B, 0x21, 0x21, 0x20, 0x45, 0xB0, 0xC0, 0x50,
+ 0x58, 0xB0, 0xC0, 0x44, 0x1B, 0x21, 0x59, 0x59, 0x2D, 0xB8, 0x00, 0x06, 0x2C, 0x20, 0x20, 0x45,
+ 0x69, 0x44, 0xB0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0xB0, 0x01, 0x60, 0x2D,
+ 0xB8, 0x00, 0x07, 0x2C, 0xB8, 0x00, 0x06, 0x2A, 0x2D, 0xB8, 0x00, 0x08, 0x2C, 0x4B, 0x20, 0xB0,
+ 0x03, 0x26, 0x53, 0x58, 0xB0, 0x40, 0x1B, 0xB0, 0x00, 0x59, 0x8A, 0x8A, 0x20, 0xB0, 0x03, 0x26,
+ 0x53, 0x58, 0x23, 0x21, 0xB0, 0x80, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03, 0x26,
+ 0x53, 0x58, 0x23, 0x21, 0xB8, 0x00, 0xC0, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03,
+ 0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x00, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0,
+ 0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x40, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20,
+ 0xB8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xB0, 0x03, 0x25, 0x45, 0xB8, 0x01, 0x80, 0x50, 0x58, 0x23,
+ 0x21, 0xB8, 0x01, 0x80, 0x23, 0x21, 0x1B, 0xB0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59,
+ 0x1B, 0x21, 0x59, 0x44, 0x2D, 0xB8, 0x00, 0x09, 0x2C, 0x4B, 0x53, 0x58, 0x45, 0x44, 0x1B, 0x21,
+ 0x21, 0x59, 0x2D, 0x00, 0xB8, 0x00, 0x00, 0x2B, 0x00, 0xBA, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07,
+ 0x2B, 0xB8, 0x00, 0x00, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x70,
+ 0x00, 0xDC, 0x01, 0x34, 0x01, 0x7C, 0x01, 0xA2, 0x01, 0xF4, 0x02, 0x3C, 0x02, 0xA8, 0x03, 0x4C,
+ 0x03, 0xE2, 0x04, 0x20, 0x04, 0x58, 0x04, 0x9A, 0x04, 0xEE, 0x05, 0x32, 0x05, 0x64, 0x05, 0x80,
+ 0x05, 0xC6, 0x05, 0xF6, 0x06, 0x54, 0x06, 0xB2, 0x07, 0x38, 0x07, 0x60, 0x07, 0x82, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0xA4, 0xFF, 0xFF, 0x03, 0x5C, 0x03, 0x09, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00,
+ 0x13, 0x11, 0x21, 0x11, 0x25, 0x21, 0x11, 0x21, 0xCD, 0x02, 0x66, 0xFD, 0x71, 0x02, 0xB8, 0xFD,
+ 0x48, 0x02, 0xE0, 0xFD, 0x48, 0x02, 0xB8, 0x29, 0xFC, 0xF6, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14,
+ 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1F, 0x00, 0x2F, 0x00, 0x39, 0x00, 0x00,
+ 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x32, 0x1E,
+ 0x02, 0x14, 0x0E, 0x02, 0x22, 0x2E, 0x02, 0x34, 0x3E, 0x01, 0x13, 0x12, 0x37, 0x33, 0x13, 0x12,
+ 0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30, 0x27, 0x26, 0x2F, 0x01,
+ 0x06, 0x07, 0x06, 0x32, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77,
+ 0x46, 0x46, 0x77, 0xFE, 0x9E, 0xC8, 0xB7, 0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0x4E,
+ 0x4E, 0x83, 0x23, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E, 0xD1, 0x2B, 0x37, 0x33,
+ 0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03, 0x3F, 0x46, 0x77, 0xA4,
+ 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x4E, 0x83, 0xB7, 0xC8, 0xB7,
+ 0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0xFD, 0x64, 0x01, 0x1A, 0xEB, 0xFE, 0xFE, 0xFE,
+ 0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C, 0x02, 0xBE, 0x02, 0x00,
+ 0x00, 0x05, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2F,
+ 0x00, 0x3A, 0x00, 0x44, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x02,
+ 0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x01,
+ 0x16, 0x17, 0x14, 0x06, 0x07, 0x06, 0x2B, 0x01, 0x19, 0x01, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16,
+ 0x07, 0x06, 0x0F, 0x01, 0x36, 0x37, 0x34, 0x2E, 0x01, 0x27, 0x23, 0x15, 0x33, 0x32, 0x27, 0x32,
+ 0x37, 0x36, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x45, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
+ 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE,
+ 0xF4, 0xE2, 0x01, 0xF7, 0x61, 0x01, 0x4E, 0x3E, 0x29, 0xAF, 0x4E, 0x81, 0x8B, 0x1D, 0x3C, 0x1F,
+ 0x19, 0x04, 0x06, 0x39, 0x57, 0x44, 0x01, 0x1B, 0x2D, 0x51, 0x46, 0x46, 0x47, 0x66, 0x70, 0x16,
+ 0x1F, 0x01, 0x2C, 0x08, 0x4B, 0x4C, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4,
+ 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2,
+ 0x84, 0x84, 0x01, 0x6D, 0x21, 0x5B, 0x40, 0x50, 0x05, 0x03, 0x01, 0x03, 0x01, 0x05, 0x01, 0x05,
+ 0x09, 0x30, 0x25, 0x29, 0x40, 0x21, 0xC2, 0x06, 0x3E, 0x1A, 0x21, 0x0B, 0x01, 0x8C, 0xE1, 0x0A,
+ 0x0E, 0x54, 0x0B, 0x02, 0x79, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC,
+ 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x38, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E,
+ 0x02, 0x34, 0x2E, 0x02, 0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E,
+ 0x01, 0x20, 0x26, 0x36, 0x34, 0x3F, 0x01, 0x27, 0x26, 0x27, 0x33, 0x17, 0x16, 0x33, 0x36, 0x3F,
+ 0x02, 0x32, 0x14, 0x06, 0x16, 0x12, 0x14, 0x2B, 0x01, 0x27, 0x26, 0x06, 0x0F, 0x01, 0x23, 0x45,
+ 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
+ 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x7B, 0x58, 0x58, 0x4D, 0x4F, 0x05, 0x7A,
+ 0x34, 0x34, 0x02, 0x01, 0x33, 0x32, 0x3C, 0x3C, 0xA1, 0x01, 0xB0, 0x3E, 0x3F, 0x39, 0x3B, 0x02,
+ 0x3A, 0x38, 0x3F, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
+ 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x60,
+ 0x02, 0x87, 0x88, 0x79, 0x7A, 0x06, 0x54, 0x54, 0x01, 0x53, 0x53, 0x01, 0x01, 0xFB, 0x04, 0xFE,
+ 0xF8, 0x02, 0x5B, 0x5A, 0x03, 0x59, 0x59, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC,
+ 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E,
+ 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E,
+ 0x01, 0x10, 0x36, 0x01, 0x35, 0x27, 0x26, 0x34, 0x3B, 0x01, 0x17, 0x16, 0x36, 0x3F, 0x01, 0x33,
+ 0x03, 0x15, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
+ 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01,
+ 0x36, 0x5E, 0x5F, 0x3C, 0x3D, 0x3D, 0x3D, 0x03, 0x3B, 0x3B, 0x77, 0xBE, 0x68, 0x03, 0x3F, 0x46,
+ 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0xFE,
+ 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6E, 0x96, 0x95, 0x01, 0x67, 0x67,
+ 0x03, 0x66, 0x65, 0xFE, 0xD3, 0xDA, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC,
+ 0x03, 0x4B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x12, 0x00, 0x00, 0x01, 0x21, 0x22, 0x15, 0x30, 0x11,
+ 0x21, 0x17, 0x21, 0x11, 0x10, 0x25, 0x21, 0x01, 0x11, 0x33, 0x11, 0x21, 0x15, 0x03, 0xBB, 0xFD,
+ 0x77, 0xED, 0x03, 0x76, 0x31, 0xFC, 0x28, 0x01, 0x1E, 0x02, 0xBA, 0xFD, 0x5C, 0x68, 0x01, 0x08,
+ 0x03, 0x1A, 0xEE, 0xFD, 0xC2, 0x31, 0x02, 0x6F, 0x01, 0x1E, 0x01, 0xFD, 0x36, 0x02, 0x07, 0xFE,
+ 0x50, 0x57, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC, 0x03, 0x4B, 0x00, 0x06,
+ 0x00, 0x0C, 0x00, 0x27, 0x00, 0x32, 0x00, 0x00, 0x05, 0x11, 0x34, 0x27, 0x30, 0x21, 0x11, 0x07,
+ 0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x07,
+ 0x06, 0x07, 0x06, 0x07, 0x1E, 0x02, 0x15, 0x07, 0x23, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x13,
+ 0x36, 0x35, 0x34, 0x27, 0x26, 0x27, 0x23, 0x15, 0x33, 0x36, 0x03, 0xBB, 0xED, 0xFD, 0x77, 0x31,
+ 0x02, 0xBA, 0x01, 0x1E, 0xFD, 0x2A, 0x77, 0x76, 0x15, 0x49, 0x20, 0x35, 0x08, 0x04, 0x06, 0x13,
+ 0x66, 0x0C, 0x01, 0x1F, 0x2E, 0x65, 0x3D, 0x3D, 0x2A, 0x56, 0x28, 0x2E, 0x19, 0x99, 0x3C, 0x20,
+ 0x10, 0x56, 0x4F, 0x46, 0x47, 0x12, 0x02, 0x3E, 0xED, 0x01, 0xFC, 0xD4, 0x31, 0x03, 0x8E, 0xFE,
+ 0xE1, 0xFD, 0x91, 0xC4, 0x02, 0x07, 0x01, 0x04, 0x13, 0x21, 0x44, 0x1D, 0x19, 0x58, 0x15, 0x02,
+ 0x01, 0x13, 0x2D, 0xA2, 0x01, 0x01, 0x3D, 0x81, 0x1A, 0x01, 0x01, 0xDA, 0x01, 0x2D, 0x08, 0x3A,
+ 0x29, 0x0F, 0x08, 0x01, 0x85, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC,
+ 0x03, 0x13, 0x00, 0x09, 0x00, 0x11, 0x00, 0x26, 0x00, 0x32, 0x00, 0x00, 0x37, 0x21, 0x34, 0x10,
+ 0x35, 0x34, 0x27, 0x21, 0x04, 0x11, 0x23, 0x10, 0x25, 0x21, 0x16, 0x15, 0x11, 0x21, 0x37, 0x35,
+ 0x37, 0x36, 0x22, 0x2B, 0x01, 0x3D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x0F, 0x01, 0x3B, 0x01, 0x1D,
+ 0x01, 0x2B, 0x01, 0x25, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x2B, 0x01, 0x45,
+ 0x03, 0x76, 0x45, 0xFE, 0x2D, 0xFE, 0xA2, 0x31, 0x01, 0x8F, 0x01, 0xD3, 0x76, 0xFC, 0x28, 0xA7,
+ 0x68, 0x68, 0x01, 0x5B, 0x5D, 0x90, 0x91, 0x6C, 0x6D, 0x71, 0x70, 0xA0, 0xA0, 0x01, 0x75, 0x27,
+ 0x28, 0x63, 0x63, 0x8B, 0x8A, 0x27, 0x69, 0x01, 0xA4, 0x69, 0x44, 0x01, 0x02, 0xFE, 0xA4, 0x01,
+ 0x8C, 0x03, 0x01, 0x75, 0xFD, 0x58, 0xBB, 0x24, 0x80, 0x80, 0x21, 0x21, 0x1F, 0x1E, 0x85, 0x86,
+ 0x20, 0x22, 0xC3, 0xC3, 0xA1, 0xA3, 0x20, 0x22, 0x00, 0x05, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC,
+ 0x03, 0x13, 0x00, 0x08, 0x00, 0x10, 0x00, 0x2B, 0x00, 0x37, 0x00, 0x44, 0x00, 0x00, 0x37, 0x21,
+ 0x11, 0x10, 0x25, 0x30, 0x21, 0x06, 0x15, 0x03, 0x11, 0x34, 0x37, 0x21, 0x04, 0x19, 0x01, 0x01,
+ 0x35, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17,
+ 0x16, 0x23, 0x2F, 0x01, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x23, 0x37, 0x32, 0x36, 0x37, 0x36, 0x35,
+ 0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x05, 0x35, 0x37, 0x36, 0x26, 0x2B, 0x01, 0x35, 0x21, 0x15,
+ 0x03, 0x17, 0x15, 0x45, 0x03, 0x76, 0xFE, 0xA2, 0xFE, 0x2D, 0x45, 0x31, 0x76, 0x01, 0xD3, 0x01,
+ 0x8F, 0xFE, 0x1E, 0x65, 0x6F, 0x15, 0x46, 0x10, 0x05, 0x04, 0x0D, 0x4F, 0x09, 0x09, 0x1F, 0x1D,
+ 0x3A, 0x06, 0x01, 0x30, 0x2F, 0x22, 0x37, 0x1E, 0x29, 0x14, 0x4E, 0x82, 0x34, 0x19, 0x0E, 0x13,
+ 0x0A, 0x22, 0x07, 0x38, 0x37, 0xFE, 0x3E, 0x68, 0x68, 0x01, 0x5C, 0x5C, 0x01, 0x20, 0xD8, 0xE1,
+ 0x27, 0x01, 0x5D, 0x01, 0x5B, 0x03, 0x01, 0x44, 0xFD, 0x58, 0x02, 0xA8, 0x75, 0x01, 0x03, 0xFE,
+ 0x74, 0xFE, 0x71, 0x01, 0x5C, 0xC5, 0x01, 0x04, 0x0C, 0x43, 0x15, 0x1D, 0x44, 0x10, 0x04, 0x06,
+ 0x14, 0x2B, 0x56, 0x10, 0x01, 0x01, 0x34, 0x52, 0x1C, 0x01, 0x01, 0xA5, 0xE3, 0x04, 0x06, 0x0A,
+ 0x20, 0x2C, 0x04, 0x01, 0x65, 0xE3, 0x47, 0x80, 0x80, 0x01, 0x42, 0x3D, 0xFE, 0xF5, 0x01, 0x41,
+ 0x00, 0x04, 0x00, 0x14, 0x00, 0x52, 0x03, 0xEC, 0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x64,
+ 0x00, 0x70, 0x00, 0x00, 0x25, 0x11, 0x21, 0x22, 0x15, 0x30, 0x15, 0x14, 0x33, 0x11, 0x21, 0x32,
+ 0x15, 0x11, 0x14, 0x27, 0x21, 0x22, 0x26, 0x3D, 0x01, 0x34, 0x36, 0x13, 0x26, 0x27, 0x26, 0x27,
+ 0x26, 0x37, 0x33, 0x36, 0x37, 0x36, 0x33, 0x16, 0x17, 0x16, 0x17, 0x16, 0x37, 0x36, 0x37, 0x36,
+ 0x35, 0x34, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x34, 0x37, 0x36, 0x37,
+ 0x36, 0x37, 0x36, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x0F, 0x01, 0x22, 0x06, 0x23,
+ 0x27, 0x26, 0x27, 0x26, 0x23, 0x22, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16,
+ 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x27, 0x37, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B,
+ 0x01, 0x1D, 0x01, 0x2B, 0x01, 0x03, 0xBB, 0xFD, 0x2A, 0xA0, 0xA0, 0x02, 0xEE, 0x19, 0x19, 0xFD,
+ 0x12, 0x57, 0x7A, 0x7A, 0xCA, 0x38, 0x1D, 0x16, 0x08, 0x03, 0x01, 0x02, 0x0F, 0x0C, 0x1E, 0x01,
+ 0x02, 0x04, 0x0C, 0x2B, 0x0F, 0x0E, 0x18, 0x0C, 0x09, 0x04, 0x15, 0x32, 0x23, 0x12, 0x1C, 0x0E,
+ 0x09, 0x03, 0x01, 0x01, 0x09, 0x21, 0x0F, 0x14, 0x2E, 0x2A, 0x13, 0x0F, 0x0C, 0x08, 0x0B, 0x05,
+ 0x02, 0x01, 0x02, 0x03, 0x36, 0x03, 0x02, 0x03, 0x08, 0x0D, 0x23, 0x16, 0x0E, 0x10, 0x01, 0x01,
+ 0x07, 0x0B, 0x32, 0x25, 0x13, 0x26, 0x0F, 0x09, 0x01, 0x01, 0x0F, 0x11, 0x24, 0x21, 0x2A, 0xE3,
+ 0x20, 0x20, 0x52, 0x50, 0x71, 0x71, 0x84, 0x02, 0x00, 0xAF, 0xA2, 0xAF, 0x02, 0x32, 0x19, 0xFD,
+ 0xCE, 0x19, 0x01, 0x84, 0x5C, 0xA2, 0x5C, 0x85, 0xFE, 0x29, 0x04, 0x1E, 0x18, 0x26, 0x0F, 0x01,
+ 0x02, 0x01, 0x03, 0x05, 0x0B, 0x29, 0x06, 0x02, 0x03, 0x04, 0x11, 0x0B, 0x0D, 0x0A, 0x06, 0x12,
+ 0x0D, 0x0A, 0x07, 0x0C, 0x18, 0x0D, 0x10, 0x06, 0x18, 0x05, 0x27, 0x14, 0x09, 0x03, 0x0A, 0x0D,
+ 0x06, 0x09, 0x09, 0x0D, 0x0F, 0x14, 0x0C, 0x06, 0x03, 0x02, 0x04, 0x10, 0x0A, 0x11, 0x08, 0x09,
+ 0x0E, 0x0C, 0x07, 0x0C, 0x0C, 0x0A, 0x07, 0x0F, 0x20, 0x11, 0x18, 0x1E, 0x1A, 0x1E, 0x0C, 0x0B,
+ 0x03, 0xAA, 0xA5, 0x89, 0x8A, 0x1C, 0x1B, 0x00, 0x00, 0x05, 0x00, 0x14, 0x00, 0x53, 0x03, 0xEC,
+ 0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x2E, 0x00, 0x38, 0x00, 0x65, 0x00, 0x00, 0x01, 0x30,
+ 0x21, 0x11, 0x21, 0x32, 0x3D, 0x01, 0x34, 0x27, 0x32, 0x16, 0x1D, 0x01, 0x14, 0x06, 0x23, 0x21,
+ 0x26, 0x35, 0x11, 0x34, 0x33, 0x01, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07,
+ 0x17, 0x1E, 0x01, 0x1F, 0x01, 0x23, 0x2A, 0x01, 0x2E, 0x01, 0x23, 0x27, 0x15, 0x37, 0x32, 0x37,
+ 0x36, 0x27, 0x2E, 0x01, 0x2B, 0x01, 0x15, 0x05, 0x26, 0x27, 0x37, 0x32, 0x3F, 0x01, 0x16, 0x17,
+ 0x1E, 0x01, 0x37, 0x36, 0x27, 0x2E, 0x04, 0x37, 0x3E, 0x01, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14,
+ 0x06, 0x27, 0x26, 0x27, 0x26, 0x0E, 0x01, 0x1E, 0x02, 0x17, 0x16, 0x06, 0x07, 0x06, 0x07, 0x06,
+ 0x03, 0x1B, 0xFD, 0x2A, 0x02, 0xD6, 0xA0, 0xA0, 0x57, 0x7A, 0x7A, 0x57, 0xFD, 0x12, 0x19, 0x19,
+ 0x01, 0xD3, 0x47, 0x44, 0x11, 0x3E, 0x18, 0x21, 0x0B, 0x0C, 0x43, 0x04, 0x17, 0x1C, 0x1E, 0x16,
+ 0x26, 0x26, 0x03, 0x4D, 0x18, 0x1E, 0x11, 0x25, 0x3A, 0x0C, 0x22, 0x08, 0x03, 0x1B, 0x3E, 0x29,
+ 0xFE, 0xAC, 0x0D, 0x04, 0x02, 0x02, 0x1E, 0x1D, 0x03, 0x02, 0x0C, 0x4C, 0x13, 0x20, 0x07, 0x04,
+ 0x1B, 0x56, 0x2D, 0x1C, 0x01, 0x02, 0x44, 0x35, 0x49, 0x1F, 0x10, 0x03, 0x41, 0x01, 0x06, 0x0A,
+ 0x16, 0x3C, 0x18, 0x0C, 0x16, 0x5D, 0x15, 0x33, 0x03, 0x2B, 0x1E, 0x34, 0x59, 0x02, 0x84, 0xFE,
+ 0x00, 0xAF, 0xA2, 0xAF, 0x32, 0x85, 0x5C, 0xA2, 0x5C, 0x84, 0x01, 0x17, 0x02, 0x32, 0x19, 0xFE,
+ 0x2F, 0x01, 0x45, 0x01, 0x02, 0x19, 0x22, 0x32, 0x39, 0x0B, 0x08, 0x0F, 0x27, 0x2F, 0x24, 0x75,
+ 0x12, 0x01, 0x88, 0xBB, 0x04, 0x09, 0x2A, 0x0F, 0x0D, 0x53, 0x8A, 0x17, 0x1E, 0x04, 0x03, 0x03,
+ 0x0C, 0x04, 0x26, 0x0E, 0x0C, 0x14, 0x1A, 0x0E, 0x0E, 0x16, 0x16, 0x2C, 0x1A, 0x2D, 0x2D, 0x2A,
+ 0x16, 0x1D, 0x06, 0x04, 0x01, 0x1A, 0x09, 0x11, 0x09, 0x17, 0x18, 0x0D, 0x17, 0x0C, 0x1B, 0x71,
+ 0x1B, 0x12, 0x01, 0x03, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F,
+ 0x00, 0x1B, 0x00, 0x27, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02,
+ 0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13,
+ 0x33, 0x35, 0x33, 0x15, 0x33, 0x15, 0x23, 0x15, 0x23, 0x35, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77,
+ 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84,
+ 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC5, 0x4E, 0xC5, 0xC4, 0x50, 0xC4, 0x03, 0x3F,
+ 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
+ 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0xC4, 0xC5, 0x4E, 0xC5, 0xC5,
+ 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x1F,
+ 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24,
+ 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x35, 0x21, 0x15, 0x02,
+ 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C,
+ 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8, 0x03, 0x3F,
+ 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
+ 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0x71, 0x4E, 0x4E, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x1B, 0x00, 0x25,
+ 0x00, 0x00, 0x00, 0x20, 0x0E, 0x01, 0x10, 0x1E, 0x01, 0x20, 0x3E, 0x01, 0x10, 0x26, 0x01, 0x12,
+ 0x37, 0x33, 0x13, 0x12, 0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30,
+ 0x27, 0x26, 0x2F, 0x01, 0x06, 0x07, 0x06, 0x32, 0x02, 0x86, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2,
+ 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xFD, 0xA0, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E,
+ 0xD1, 0x2B, 0x37, 0x33, 0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03,
+ 0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0x9A, 0x01, 0x1A,
+ 0xEB, 0xFE, 0xFE, 0xFE, 0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C,
+ 0x02, 0xBE, 0x02, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B,
+ 0x00, 0x20, 0x00, 0x2B, 0x00, 0x35, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10,
+ 0x0E, 0x01, 0x20, 0x26, 0x01, 0x30, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x23, 0x27,
+ 0x19, 0x01, 0x33, 0x32, 0x37, 0x3E, 0x01, 0x35, 0x26, 0x07, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x1E,
+ 0x02, 0x15, 0x06, 0x27, 0x23, 0x35, 0x33, 0x16, 0x17, 0x16, 0x14, 0x07, 0x06, 0x14, 0x84, 0xE2,
+ 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x01, 0xF7, 0x0A, 0x3A, 0x05, 0x04, 0x19,
+ 0x20, 0x3B, 0x1D, 0x8B, 0x81, 0x4E, 0xAF, 0x29, 0x3E, 0x4E, 0x01, 0xAE, 0x0D, 0x47, 0x46, 0x46,
+ 0x52, 0x2C, 0x1B, 0x01, 0xB7, 0x27, 0x4C, 0x4C, 0x07, 0x2C, 0x1E, 0x16, 0xFE, 0x01, 0x0C, 0xE2,
+ 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x6D, 0x06, 0x21, 0x40, 0x2A, 0x24, 0x30,
+ 0x09, 0x05, 0x01, 0xFE, 0xFB, 0xFE, 0xFD, 0x03, 0x05, 0x4F, 0x41, 0x5B, 0x9B, 0x01, 0x8C, 0x01,
+ 0x0B, 0x21, 0x1A, 0x3E, 0xDA, 0x79, 0x01, 0x01, 0x0B, 0x54, 0x0E, 0x0A, 0x00, 0x02, 0x00, 0x14,
+ 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x29, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01,
+ 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x36, 0x14, 0x3B, 0x01, 0x37, 0x36, 0x37, 0x36,
+ 0x1F, 0x01, 0x33, 0x32, 0x34, 0x02, 0x26, 0x36, 0x34, 0x23, 0x0F, 0x01, 0x06, 0x07, 0x22, 0x2F,
+ 0x01, 0x23, 0x16, 0x1F, 0x01, 0x07, 0x14, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE,
+ 0xF4, 0xE2, 0x7B, 0x3D, 0x3F, 0x38, 0x3A, 0x01, 0x02, 0x3A, 0x39, 0x3F, 0x3E, 0xB0, 0x01, 0xA1,
+ 0x3C, 0x3C, 0x32, 0x33, 0x01, 0x02, 0x34, 0x34, 0x7A, 0x05, 0x4F, 0x4D, 0x58, 0xFE, 0x01, 0x0C,
+ 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x62, 0x02, 0x59, 0x59, 0x02, 0x01, 0x5A,
+ 0x5B, 0x02, 0x01, 0x08, 0x04, 0xFB, 0x01, 0x01, 0x53, 0x53, 0x01, 0x54, 0x54, 0x06, 0x7A, 0x79,
+ 0x88, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B,
+ 0x00, 0x1B, 0x00, 0x00, 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36,
+ 0x01, 0x15, 0x33, 0x35, 0x13, 0x23, 0x07, 0x0E, 0x01, 0x2F, 0x01, 0x23, 0x22, 0x16, 0x1F, 0x01,
+ 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x36, 0x68,
+ 0xBE, 0x77, 0x3B, 0x3C, 0x02, 0x3D, 0x3D, 0x3D, 0x3D, 0x01, 0x5F, 0x5E, 0x03, 0x70, 0x84, 0xE2,
+ 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6D, 0xDA, 0x01, 0x2D, 0x65,
+ 0x66, 0x03, 0x67, 0x67, 0x01, 0x95, 0x96, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC,
+ 0x03, 0x4A, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x05, 0x21, 0x11, 0x10, 0x05, 0x21, 0x01, 0x21,
+ 0x35, 0x21, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x14, 0x02, 0xC4, 0xFD, 0x5C, 0x01, 0x70,
+ 0xFE, 0xF8, 0x68, 0x41, 0x02, 0x77, 0x01, 0x14, 0x01, 0xFD, 0x38, 0x57, 0x01, 0xB0, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC, 0x03, 0x49, 0x00, 0x05, 0x00, 0x20, 0x00, 0x2B,
+ 0x00, 0x00, 0x17, 0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F, 0x01,
+ 0x33, 0x37, 0x2E, 0x02, 0x27, 0x34, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26,
+ 0x2B, 0x01, 0x05, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x16, 0x17, 0x16, 0x15, 0x14, 0x14, 0x02, 0xC4,
+ 0x01, 0x14, 0xFD, 0x2A, 0x69, 0x19, 0x2E, 0x28, 0x56, 0x2A, 0x3D, 0x3D, 0x01, 0x65, 0x2C, 0x20,
+ 0x0D, 0x66, 0x13, 0x06, 0x04, 0x09, 0x34, 0x20, 0x49, 0x15, 0x76, 0x77, 0x01, 0x02, 0x0C, 0x47,
+ 0x46, 0x4F, 0x56, 0x10, 0x20, 0x41, 0x03, 0x8A, 0xFE, 0xED, 0xFD, 0x89, 0xC2, 0xDA, 0x01, 0x01,
+ 0x1A, 0x81, 0x3D, 0x01, 0x01, 0xA3, 0x2C, 0x13, 0x01, 0x02, 0x13, 0x5A, 0x1A, 0x1C, 0x44, 0x21,
+ 0x13, 0x04, 0x01, 0xDA, 0x02, 0x85, 0x01, 0x08, 0x0F, 0x29, 0x3A, 0x00, 0x00, 0x03, 0x00, 0x14,
+ 0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x08, 0x00, 0x15, 0x00, 0x1B, 0x00, 0x00, 0x05, 0x21,
+ 0x11, 0x10, 0x21, 0x30, 0x21, 0x32, 0x15, 0x01, 0x21, 0x35, 0x23, 0x13, 0x35, 0x21, 0x15, 0x33,
+ 0x32, 0x22, 0x0F, 0x01, 0x05, 0x21, 0x35, 0x23, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x8A,
+ 0x01, 0xEC, 0x62, 0xFC, 0xCF, 0x01, 0x40, 0xE1, 0xD9, 0xFE, 0xDF, 0x5D, 0x5C, 0x01, 0x67, 0x68,
+ 0x01, 0x75, 0x01, 0x15, 0xC6, 0x4F, 0x05, 0x01, 0x89, 0x01, 0x8A, 0x63, 0xFD, 0xE1, 0x42, 0x01,
+ 0x0B, 0x3D, 0x42, 0x80, 0x80, 0x48, 0x42, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14,
+ 0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x07, 0x00, 0x22, 0x00, 0x2F, 0x00, 0x3C, 0x00, 0x00,
+ 0x17, 0x11, 0x34, 0x37, 0x21, 0x20, 0x19, 0x01, 0x01, 0x15, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F,
+ 0x02, 0x32, 0x35, 0x26, 0x27, 0x26, 0x27, 0x26, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26,
+ 0x23, 0x27, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14, 0x07, 0x0E, 0x01, 0x05,
+ 0x21, 0x35, 0x27, 0x13, 0x35, 0x21, 0x15, 0x33, 0x32, 0x14, 0x0F, 0x01, 0x14, 0x62, 0x01, 0xEC,
+ 0x01, 0x8A, 0xFE, 0x1E, 0x4E, 0x14, 0x29, 0x1E, 0x37, 0x22, 0x2F, 0x2F, 0x06, 0x3A, 0x1D, 0x1F,
+ 0x09, 0x09, 0x4E, 0x0E, 0x04, 0x05, 0x0F, 0x47, 0x15, 0x6F, 0x65, 0x82, 0x34, 0x37, 0x38, 0x07,
+ 0x23, 0x09, 0x13, 0x0D, 0x1A, 0xFD, 0xD6, 0x01, 0x40, 0xE1, 0xD8, 0xFE, 0xE0, 0x5C, 0x5C, 0x67,
+ 0x68, 0x05, 0x02, 0xB0, 0x62, 0x01, 0xFE, 0x76, 0xFE, 0x77, 0x01, 0x56, 0xC5, 0xA5, 0x01, 0x01,
+ 0x1C, 0x52, 0x34, 0x01, 0x01, 0x0E, 0x58, 0x2C, 0x13, 0x06, 0x04, 0x0F, 0x45, 0x1E, 0x14, 0x42,
+ 0x0D, 0x04, 0x01, 0xA7, 0x65, 0x01, 0x04, 0x2C, 0x21, 0x09, 0x07, 0x03, 0xE3, 0x41, 0x01, 0x01,
+ 0x0B, 0x3D, 0x42, 0x01, 0x80, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC,
+ 0x02, 0xAB, 0x00, 0x08, 0x00, 0x37, 0x00, 0x3D, 0x00, 0x00, 0x13, 0x30, 0x21, 0x11, 0x21, 0x22,
+ 0x3D, 0x01, 0x34, 0x05, 0x37, 0x34, 0x27, 0x26, 0x27, 0x26, 0x07, 0x06, 0x07, 0x0E, 0x01, 0x17,
+ 0x1E, 0x01, 0x17, 0x16, 0x14, 0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x22, 0x17,
+ 0x1E, 0x01, 0x17, 0x16, 0x37, 0x36, 0x27, 0x26, 0x27, 0x2E, 0x02, 0x37, 0x36, 0x33, 0x32, 0x1F,
+ 0x02, 0x33, 0x35, 0x23, 0x11, 0x23, 0xD6, 0x03, 0x16, 0xFC, 0xEA, 0xC2, 0x01, 0xC6, 0x02, 0x01,
+ 0x0C, 0x3A, 0x2B, 0x2D, 0x13, 0x10, 0x2B, 0x01, 0x33, 0x17, 0x55, 0x15, 0x04, 0x09, 0x14, 0x58,
+ 0x0C, 0x04, 0x02, 0x02, 0x26, 0x14, 0x01, 0x03, 0x08, 0x33, 0x38, 0x5F, 0x20, 0x10, 0x01, 0x03,
+ 0x3C, 0x12, 0x59, 0x11, 0x01, 0x02, 0x39, 0x2C, 0x09, 0x02, 0x9D, 0xE2, 0xA2, 0x40, 0x02, 0xAB,
+ 0xFD, 0xB2, 0xD2, 0xAA, 0xD2, 0xDC, 0x03, 0x07, 0x0B, 0x38, 0x10, 0x0C, 0x09, 0x04, 0x08, 0x19,
+ 0x6C, 0x17, 0x0B, 0x17, 0x11, 0x07, 0x17, 0x0A, 0x1A, 0x0A, 0x29, 0x0C, 0x04, 0x04, 0x02, 0x10,
+ 0x25, 0x37, 0x04, 0x06, 0x37, 0x1D, 0x1C, 0x3F, 0x19, 0x08, 0x16, 0x13, 0x0B, 0x1F, 0x2B, 0x04,
+ 0xE9, 0x37, 0x01, 0x13, 0x00, 0x04, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC, 0x02, 0xAB, 0x00, 0x07,
+ 0x00, 0x1F, 0x00, 0x2A, 0x00, 0x58, 0x00, 0x00, 0x01, 0x32, 0x1D, 0x01, 0x14, 0x23, 0x21, 0x11,
+ 0x01, 0x33, 0x35, 0x17, 0x1E, 0x03, 0x3B, 0x01, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x36, 0x37, 0x36,
+ 0x27, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x16, 0x17, 0x16, 0x07,
+ 0x06, 0x05, 0x16, 0x37, 0x36, 0x37, 0x3E, 0x01, 0x27, 0x2E, 0x03, 0x3E, 0x01, 0x17, 0x16, 0x17,
+ 0x30, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x06, 0x1E, 0x03, 0x17, 0x16,
+ 0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x07, 0x06, 0x23, 0x07, 0x16, 0x03, 0x2A, 0xC2, 0xC2, 0xFC,
+ 0xEA, 0x01, 0xEC, 0x41, 0x11, 0x1F, 0x17, 0x4D, 0x02, 0x27, 0x26, 0x16, 0x1E, 0x1C, 0x17, 0x04,
+ 0x43, 0x0C, 0x0B, 0x21, 0x18, 0x3E, 0x0F, 0x46, 0x47, 0x66, 0x25, 0x29, 0x3E, 0x1B, 0x03, 0x08,
+ 0x22, 0x0C, 0xFE, 0x4D, 0x22, 0x59, 0x34, 0x1E, 0x2B, 0x03, 0x33, 0x16, 0x5C, 0x16, 0x0C, 0x18,
+ 0x3C, 0x16, 0x0B, 0x05, 0x22, 0x21, 0x01, 0x03, 0x10, 0x1F, 0x49, 0x36, 0x43, 0x02, 0x01, 0x1C,
+ 0x2D, 0x56, 0x1B, 0x04, 0x07, 0x20, 0x13, 0x4B, 0x0D, 0x01, 0x04, 0x1D, 0x1E, 0x02, 0x02, 0x04,
+ 0x02, 0xAB, 0xD2, 0xAA, 0xD2, 0x02, 0x4E, 0xFE, 0x39, 0x89, 0x01, 0x01, 0x11, 0x75, 0x01, 0x25,
+ 0x2F, 0x27, 0x0F, 0x08, 0x0C, 0x38, 0x33, 0x21, 0x19, 0x02, 0x01, 0x8A, 0x53, 0x0D, 0x0F, 0x2A,
+ 0x09, 0x04, 0x8A, 0x3A, 0x03, 0x01, 0x12, 0x1B, 0x71, 0x1B, 0x0C, 0x17, 0x0D, 0x18, 0x17, 0x09,
+ 0x11, 0x09, 0x1A, 0x01, 0x01, 0x07, 0x1E, 0x15, 0x29, 0x01, 0x2D, 0x2D, 0x1A, 0x2C, 0x16, 0x16,
+ 0x0D, 0x0F, 0x1A, 0x14, 0x0C, 0x0D, 0x27, 0x04, 0x0C, 0x03, 0x03, 0x04, 0x1E, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x00,
+ 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x15, 0x33, 0x15,
+ 0x33, 0x35, 0x33, 0x35, 0x23, 0x35, 0x23, 0x15, 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2,
+ 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC4, 0x50, 0xC4, 0xC5, 0x4E, 0x03, 0x70, 0x84, 0xE2, 0xFE,
+ 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0x4F, 0xC5, 0xC5, 0x4E, 0xC5, 0xC4,
+ 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x00,
+ 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x21, 0x35, 0x21,
+ 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8,
+ 0xFE, 0x28, 0x03, 0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE,
+ 0x71, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xAE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x15, 0x00, 0x2C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10,
+ 0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x85, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0xAF, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x04, 0x00, 0x10, 0x00, 0xE2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0D,
+ 0x01, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, 0x01, 0x3F, 0x00, 0x03,
+ 0x00, 0x01, 0x04, 0x09, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
+ 0x00, 0x01, 0x00, 0x20, 0x00, 0x42, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0E,
+ 0x00, 0x75, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x20, 0x00, 0x8D, 0x00, 0x03,
+ 0x00, 0x01, 0x04, 0x09, 0x00, 0x04, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
+ 0x00, 0x05, 0x00, 0x1A, 0x00, 0xF3, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x20,
+ 0x01, 0x1D, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6D,
+ 0x00, 0x75, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x20, 0x00, 0x50,
+ 0x00, 0x72, 0x00, 0x6F, 0x00, 0x6A, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x00, 0x59, 0x75,
+ 0x7A, 0x75, 0x20, 0x45, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72, 0x20, 0x50, 0x72, 0x6F, 0x6A,
+ 0x65, 0x63, 0x74, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53,
+ 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69,
+ 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74,
+ 0x65, 0x6E, 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00,
+ 0x6C, 0x00, 0x61, 0x00, 0x72, 0x00, 0x00, 0x52, 0x65, 0x67, 0x75, 0x6C, 0x61, 0x72, 0x00, 0x00,
+ 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00,
+ 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00,
+ 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F,
+ 0x6E, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53,
+ 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F,
+ 0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E,
+ 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00,
+ 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00,
+ 0x00, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x31, 0x2E, 0x30, 0x30, 0x30, 0x00, 0x00,
+ 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00,
+ 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00,
+ 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F,
+ 0x6E, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xB5, 0x00, 0x32,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04,
+ 0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0A, 0x01, 0x0B, 0x01, 0x0C,
+ 0x01, 0x0D, 0x01, 0x0E, 0x01, 0x0F, 0x01, 0x10, 0x01, 0x11, 0x01, 0x12, 0x01, 0x13, 0x01, 0x14,
+ 0x01, 0x15, 0x01, 0x16, 0x01, 0x17, 0x01, 0x18, 0x01, 0x19, 0x01, 0x1A, 0x01, 0x1B, 0x07, 0x75,
+ 0x6E, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x30, 0x30, 0x30, 0x44, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x41, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x31, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x41, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x33, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x41, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x35, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x41, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x37, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x41, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x39, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x42, 0x33, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x42, 0x34, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x31, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x33, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x35, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x37, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x39, 0x07, 0x75,
+ 0x6E, 0x69, 0x45, 0x30, 0x45, 0x46, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x46, 0x30, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x0F,
}};
} // namespace FileSys::SystemArchive::SharedFontData
diff --git a/src/core/file_sys/system_archive/data/font_nintendo_extended.h b/src/core/file_sys/system_archive/data/font_nintendo_extended.h
index 2089f3db9..edb9df914 100644
--- a/src/core/file_sys/system_archive/data/font_nintendo_extended.h
+++ b/src/core/file_sys/system_archive/data/font_nintendo_extended.h
@@ -8,6 +8,6 @@
namespace FileSys::SystemArchive::SharedFontData {
-extern const std::array<unsigned char, 2932> FONT_NINTENDO_EXTENDED;
+extern const std::array<unsigned char, 6024> FONT_NINTENDO_EXTENDED;
} // namespace FileSys::SystemArchive::SharedFontData
diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp
index aa313de66..7bfbc9a67 100644
--- a/src/core/file_sys/system_archive/system_version.cpp
+++ b/src/core/file_sys/system_archive/system_version.cpp
@@ -12,17 +12,17 @@ namespace SystemVersionData {
// This section should reflect the best system version to describe yuzu's HLE api.
// TODO(DarkLordZach): Update when HLE gets better.
-constexpr u8 VERSION_MAJOR = 10;
+constexpr u8 VERSION_MAJOR = 11;
constexpr u8 VERSION_MINOR = 0;
-constexpr u8 VERSION_MICRO = 2;
+constexpr u8 VERSION_MICRO = 0;
-constexpr u8 REVISION_MAJOR = 1;
+constexpr u8 REVISION_MAJOR = 5;
constexpr u8 REVISION_MINOR = 0;
constexpr char PLATFORM_STRING[] = "NX";
-constexpr char VERSION_HASH[] = "f90143fa8bbc061d4f68c35f95f04f8080c0ecdc";
-constexpr char DISPLAY_VERSION[] = "10.0.2";
-constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 10.0.2-1.0";
+constexpr char VERSION_HASH[] = "34197eba8810e2edd5e9dfcfbde7b340882e856d";
+constexpr char DISPLAY_VERSION[] = "11.0.0";
+constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.0-5.0";
} // namespace SystemVersionData
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index a4c3f67c4..f497e9396 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -169,11 +169,12 @@ VfsDirectory::~VfsDirectory() = default;
std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
u8 out{};
- std::size_t size = Read(&out, 1, offset);
- if (size == 1)
+ const std::size_t size = Read(&out, sizeof(u8), offset);
+ if (size == 1) {
return out;
+ }
- return {};
+ return std::nullopt;
}
std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
@@ -202,7 +203,7 @@ std::string VfsFile::GetFullPath() const {
return GetContainingDirectory()->GetFullPath() + "/" + GetName();
}
-std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const {
+VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
@@ -230,7 +231,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) co
return dir->GetFile(vec.back());
}
-std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) const {
+VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
if (IsRoot()) {
return GetFileRelative(path);
}
@@ -238,7 +239,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) co
return GetParentDirectory()->GetFileAbsolute(path);
}
-std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const {
+VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const {
auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
@@ -260,7 +261,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_vie
return dir;
}
-std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
+VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
if (IsRoot()) {
return GetDirectoryRelative(path);
}
@@ -268,14 +269,14 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(std::string_vie
return GetParentDirectory()->GetDirectoryAbsolute(path);
}
-std::shared_ptr<VfsFile> VfsDirectory::GetFile(std::string_view name) const {
+VirtualFile VfsDirectory::GetFile(std::string_view name) const {
const auto& files = GetFiles();
const auto iter = std::find_if(files.begin(), files.end(),
[&name](const auto& file1) { return name == file1->GetName(); });
return iter == files.end() ? nullptr : *iter;
}
-std::shared_ptr<VfsDirectory> VfsDirectory::GetSubdirectory(std::string_view name) const {
+VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const {
const auto& subs = GetSubdirectories();
const auto iter = std::find_if(subs.begin(), subs.end(),
[&name](const auto& file1) { return name == file1->GetName(); });
@@ -300,7 +301,7 @@ std::size_t VfsDirectory::GetSize() const {
return file_total + subdir_total;
}
-std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) {
+VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) {
auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
@@ -323,7 +324,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path)
return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
}
-std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) {
+VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) {
if (IsRoot()) {
return CreateFileRelative(path);
}
@@ -331,7 +332,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path)
return GetParentDirectory()->CreateFileAbsolute(path);
}
-std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) {
+VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) {
auto vec = Common::FS::SplitPathComponents(path);
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
vec.end());
@@ -354,7 +355,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_
return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
}
-std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
+VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
if (IsRoot()) {
return CreateDirectoryRelative(path);
}
@@ -445,27 +446,27 @@ bool ReadOnlyVfsDirectory::IsReadable() const {
return true;
}
-std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
+VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
return nullptr;
}
-std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
+VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
return nullptr;
}
-std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
+VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
return nullptr;
}
-std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
+VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
return nullptr;
}
-std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
+VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
return nullptr;
}
-std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
+VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
return nullptr;
}
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 954094772..afd64e95c 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -91,7 +91,7 @@ public:
// Resizes the file to new_size. Returns whether or not the operation was successful.
virtual bool Resize(std::size_t new_size) = 0;
// Gets a pointer to the directory containing this file, returning nullptr if there is none.
- virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0;
+ virtual VirtualDir GetContainingDirectory() const = 0;
// Returns whether or not the file can be written to.
virtual bool IsWritable() const = 0;
@@ -183,27 +183,27 @@ public:
// Retrives the file located at path as if the current directory was root. Returns nullptr if
// not found.
- virtual std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const;
+ virtual VirtualFile GetFileRelative(std::string_view path) const;
// Calls GetFileRelative(path) on the root of the current directory.
- virtual std::shared_ptr<VfsFile> GetFileAbsolute(std::string_view path) const;
+ virtual VirtualFile GetFileAbsolute(std::string_view path) const;
// Retrives the directory located at path as if the current directory was root. Returns nullptr
// if not found.
- virtual std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const;
+ virtual VirtualDir GetDirectoryRelative(std::string_view path) const;
// Calls GetDirectoryRelative(path) on the root of the current directory.
- virtual std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(std::string_view path) const;
+ virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const;
// Returns a vector containing all of the files in this directory.
- virtual std::vector<std::shared_ptr<VfsFile>> GetFiles() const = 0;
+ virtual std::vector<VirtualFile> GetFiles() const = 0;
// Returns the file with filename matching name. Returns nullptr if directory dosen't have a
// file with name.
- virtual std::shared_ptr<VfsFile> GetFile(std::string_view name) const;
+ virtual VirtualFile GetFile(std::string_view name) const;
// Returns a vector containing all of the subdirectories in this directory.
- virtual std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const = 0;
+ virtual std::vector<VirtualDir> GetSubdirectories() const = 0;
// Returns the directory with name matching name. Returns nullptr if directory dosen't have a
// directory with name.
- virtual std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const;
+ virtual VirtualDir GetSubdirectory(std::string_view name) const;
// Returns whether or not the directory can be written to.
virtual bool IsWritable() const = 0;
@@ -219,31 +219,31 @@ public:
virtual std::size_t GetSize() const;
// Returns the parent directory of this directory. Returns nullptr if this directory is root or
// has no parent.
- virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0;
+ virtual VirtualDir GetParentDirectory() const = 0;
// Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
// if the operation failed.
- virtual std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) = 0;
+ virtual VirtualDir CreateSubdirectory(std::string_view name) = 0;
// Creates a new file with name name. Returns a pointer to the new file or nullptr if the
// operation failed.
- virtual std::shared_ptr<VfsFile> CreateFile(std::string_view name) = 0;
+ virtual VirtualFile CreateFile(std::string_view name) = 0;
// Creates a new file at the path relative to this directory. Also creates directories if
// they do not exist and is supported by this implementation. Returns nullptr on any failure.
- virtual std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path);
+ virtual VirtualFile CreateFileRelative(std::string_view path);
// Creates a new file at the path relative to root of this directory. Also creates directories
// if they do not exist and is supported by this implementation. Returns nullptr on any failure.
- virtual std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path);
+ virtual VirtualFile CreateFileAbsolute(std::string_view path);
// Creates a new directory at the path relative to this directory. Also creates directories if
// they do not exist and is supported by this implementation. Returns nullptr on any failure.
- virtual std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path);
+ virtual VirtualDir CreateDirectoryRelative(std::string_view path);
// Creates a new directory at the path relative to root of this directory. Also creates
// directories if they do not exist and is supported by this implementation. Returns nullptr on
// any failure.
- virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path);
+ virtual VirtualDir CreateDirectoryAbsolute(std::string_view path);
// Deletes the subdirectory with the given name and returns true on success.
virtual bool DeleteSubdirectory(std::string_view name) = 0;
@@ -280,12 +280,12 @@ class ReadOnlyVfsDirectory : public VfsDirectory {
public:
bool IsWritable() const override;
bool IsReadable() const override;
- std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
- std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
- std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path) override;
- std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
- std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path) override;
- std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
+ VirtualDir CreateSubdirectory(std::string_view name) override;
+ VirtualFile CreateFile(std::string_view name) override;
+ VirtualFile CreateFileAbsolute(std::string_view path) override;
+ VirtualFile CreateFileRelative(std::string_view path) override;
+ VirtualDir CreateDirectoryAbsolute(std::string_view path) override;
+ VirtualDir CreateDirectoryRelative(std::string_view path) override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteSubdirectoryRecursive(std::string_view name) override;
bool CleanSubdirectoryRecursive(std::string_view name) override;
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
index e0ff70174..3c5a7d87a 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -46,7 +46,7 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> f
if (files.size() == 1)
return files[0];
- return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
+ return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
@@ -71,20 +71,23 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
if (files.begin()->first != 0)
files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first));
- return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
+ return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
std::string ConcatenatedVfsFile::GetName() const {
- if (files.empty())
+ if (files.empty()) {
return "";
- if (!name.empty())
+ }
+ if (!name.empty()) {
return name;
+ }
return files.begin()->second->GetName();
}
std::size_t ConcatenatedVfsFile::GetSize() const {
- if (files.empty())
+ if (files.empty()) {
return 0;
+ }
return files.rbegin()->first + files.rbegin()->second->GetSize();
}
@@ -92,9 +95,10 @@ bool ConcatenatedVfsFile::Resize(std::size_t new_size) {
return false;
}
-std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
- if (files.empty())
+VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const {
+ if (files.empty()) {
return nullptr;
+ }
return files.begin()->second->GetContainingDirectory();
}
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
index 7a26343c0..287c72555 100644
--- a/src/core/file_sys/vfs_concat.h
+++ b/src/core/file_sys/vfs_concat.h
@@ -31,7 +31,7 @@ public:
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
- std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
+ VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
index 338e398da..434b03cec 100644
--- a/src/core/file_sys/vfs_layered.cpp
+++ b/src/core/file_sys/vfs_layered.cpp
@@ -20,10 +20,10 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dir
if (dirs.size() == 1)
return dirs[0];
- return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
+ return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
}
-std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
+VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
for (const auto& layer : dirs) {
const auto file = layer->GetFileRelative(path);
if (file != nullptr)
@@ -33,23 +33,23 @@ std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view p
return nullptr;
}
-std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
- std::string_view path) const {
+VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const {
std::vector<VirtualDir> out;
for (const auto& layer : dirs) {
auto dir = layer->GetDirectoryRelative(path);
- if (dir != nullptr)
+ if (dir != nullptr) {
out.push_back(std::move(dir));
+ }
}
return MakeLayeredDirectory(std::move(out));
}
-std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const {
+VirtualFile LayeredVfsDirectory::GetFile(std::string_view name) const {
return GetFileRelative(name);
}
-std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
+VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
return GetDirectoryRelative(name);
}
@@ -57,7 +57,7 @@ std::string LayeredVfsDirectory::GetFullPath() const {
return dirs[0]->GetFullPath();
}
-std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
+std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualFile> out;
for (const auto& layer : dirs) {
for (const auto& file : layer->GetFiles()) {
@@ -72,7 +72,7 @@ std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
return out;
}
-std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
+std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
std::vector<std::string> names;
for (const auto& layer : dirs) {
for (const auto& sd : layer->GetSubdirectories()) {
@@ -101,15 +101,15 @@ std::string LayeredVfsDirectory::GetName() const {
return name.empty() ? dirs[0]->GetName() : name;
}
-std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
+VirtualDir LayeredVfsDirectory::GetParentDirectory() const {
return dirs[0]->GetParentDirectory();
}
-std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
+VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
return nullptr;
}
-std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
+VirtualFile LayeredVfsDirectory::CreateFile(std::string_view name) {
return nullptr;
}
diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h
index 8a25c3428..6d7513ac6 100644
--- a/src/core/file_sys/vfs_layered.h
+++ b/src/core/file_sys/vfs_layered.h
@@ -21,20 +21,20 @@ public:
/// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
- std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
- std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
- std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
- std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
+ VirtualFile GetFileRelative(std::string_view path) const override;
+ VirtualDir GetDirectoryRelative(std::string_view path) const override;
+ VirtualFile GetFile(std::string_view name) const override;
+ VirtualDir GetSubdirectory(std::string_view name) const override;
std::string GetFullPath() const override;
- std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
- std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
- std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
- std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
- std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
+ VirtualDir GetParentDirectory() const override;
+ VirtualDir CreateSubdirectory(std::string_view name) override;
+ VirtualFile CreateFile(std::string_view name) override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
index c96f88488..056737b54 100644
--- a/src/core/file_sys/vfs_offset.cpp
+++ b/src/core/file_sys/vfs_offset.cpp
@@ -9,7 +9,7 @@
namespace FileSys {
-OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, std::size_t size_, std::size_t offset_,
+OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_,
std::string name_, VirtualDir parent_)
: file(file_), offset(offset_), size(size_), name(std::move(name_)),
parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {}
@@ -37,7 +37,7 @@ bool OffsetVfsFile::Resize(std::size_t new_size) {
return true;
}
-std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const {
+VirtualDir OffsetVfsFile::GetContainingDirectory() const {
return parent;
}
@@ -58,10 +58,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t
}
std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
- if (r_offset < size)
- return file->ReadByte(offset + r_offset);
+ if (r_offset >= size) {
+ return std::nullopt;
+ }
- return {};
+ return file->ReadByte(offset + r_offset);
}
std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h
index f7b7a3256..b2ccc5c7b 100644
--- a/src/core/file_sys/vfs_offset.h
+++ b/src/core/file_sys/vfs_offset.h
@@ -17,14 +17,14 @@ namespace FileSys {
// the size of this wrapper.
class OffsetVfsFile : public VfsFile {
public:
- OffsetVfsFile(std::shared_ptr<VfsFile> file, std::size_t size, std::size_t offset = 0,
+ OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0,
std::string new_name = "", VirtualDir new_parent = nullptr);
~OffsetVfsFile() override;
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
- std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
+ VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
@@ -42,7 +42,7 @@ public:
private:
std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
- std::shared_ptr<VfsFile> file;
+ VirtualFile file;
std::size_t offset;
std::size_t size;
std::string name;
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 488687ba9..a44ce6288 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -133,8 +133,11 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_
}
cache.erase(old_path);
- file->Open(new_path, "r+b");
- cache.insert_or_assign(new_path, std::move(file));
+ if (file->Open(new_path, "r+b")) {
+ cache.insert_or_assign(new_path, std::move(file));
+ } else {
+ LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path);
+ }
} else {
UNREACHABLE();
return nullptr;
@@ -214,9 +217,12 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
}
auto file = cached.lock();
- file->Open(file_new_path, "r+b");
cache.erase(file_old_path);
- cache.insert_or_assign(std::move(file_new_path), std::move(file));
+ if (file->Open(file_new_path, "r+b")) {
+ cache.insert_or_assign(std::move(file_new_path), std::move(file));
+ } else {
+ LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path);
+ }
}
return OpenDirectory(new_path, Mode::ReadWrite);
@@ -263,7 +269,7 @@ bool RealVfsFile::Resize(std::size_t new_size) {
return backing->Resize(new_size);
}
-std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
+VirtualDir RealVfsFile::GetContainingDirectory() const {
return base.OpenDirectory(parent_path, perms);
}
@@ -352,7 +358,7 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
RealVfsDirectory::~RealVfsDirectory() = default;
-std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
+VirtualFile RealVfsDirectory::GetFileRelative(std::string_view path) const {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) {
return nullptr;
@@ -360,7 +366,7 @@ std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path
return base.OpenFile(full_path, perms);
}
-std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
+VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) {
return nullptr;
@@ -368,20 +374,20 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string
return base.OpenDirectory(full_path, perms);
}
-std::shared_ptr<VfsFile> RealVfsDirectory::GetFile(std::string_view name) const {
+VirtualFile RealVfsDirectory::GetFile(std::string_view name) const {
return GetFileRelative(name);
}
-std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view name) const {
+VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
return GetDirectoryRelative(name);
}
-std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) {
+VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view path) {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
return base.CreateFile(full_path, perms);
}
-std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
+VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
return base.CreateDirectory(full_path, perms);
}
@@ -391,11 +397,11 @@ bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
return base.DeleteDirectory(full_path);
}
-std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const {
+std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
return IterateEntries<RealVfsFile, VfsFile>();
}
-std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const {
+std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
return IterateEntries<RealVfsDirectory, VfsDirectory>();
}
@@ -411,7 +417,7 @@ std::string RealVfsDirectory::GetName() const {
return path_components.back();
}
-std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
+VirtualDir RealVfsDirectory::GetParentDirectory() const {
if (path_components.size() <= 1) {
return nullptr;
}
@@ -419,12 +425,12 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
return base.OpenDirectory(parent_path, perms);
}
-std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(std::string_view name) {
+VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) {
const std::string subdir_path = (path + DIR_SEP).append(name);
return base.CreateDirectory(subdir_path, perms);
}
-std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(std::string_view name) {
+VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
const std::string file_path = (path + DIR_SEP).append(name);
return base.CreateFile(file_path, perms);
}
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index 0b537b22c..23e99865e 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -50,7 +50,7 @@ public:
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
- std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
+ VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
@@ -79,21 +79,21 @@ class RealVfsDirectory : public VfsDirectory {
public:
~RealVfsDirectory() override;
- std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
- std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
- std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
- std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
- std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
- std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
+ VirtualFile GetFileRelative(std::string_view path) const override;
+ VirtualDir GetDirectoryRelative(std::string_view path) const override;
+ VirtualFile GetFile(std::string_view name) const override;
+ VirtualDir GetSubdirectory(std::string_view name) const override;
+ VirtualFile CreateFileRelative(std::string_view path) override;
+ VirtualDir CreateDirectoryRelative(std::string_view path) override;
bool DeleteSubdirectoryRecursive(std::string_view name) override;
- std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
- std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
- std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
- std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
- std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
+ VirtualDir GetParentDirectory() const override;
+ VirtualDir CreateSubdirectory(std::string_view name) override;
+ VirtualFile CreateFile(std::string_view name) override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
index 9f5a90b1b..c840b24b9 100644
--- a/src/core/file_sys/vfs_static.h
+++ b/src/core/file_sys/vfs_static.h
@@ -31,7 +31,7 @@ public:
return true;
}
- std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
+ VirtualDir GetContainingDirectory() const override {
return parent;
}
@@ -54,9 +54,11 @@ public:
}
std::optional<u8> ReadByte(std::size_t offset) const override {
- if (offset < size)
- return value;
- return {};
+ if (offset >= size) {
+ return std::nullopt;
+ }
+
+ return value;
}
std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index 75fc04302..c1ec1e645 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -25,7 +25,7 @@ bool VectorVfsFile::Resize(size_t new_size) {
return true;
}
-std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const {
+VirtualDir VectorVfsFile::GetContainingDirectory() const {
return parent;
}
@@ -68,11 +68,11 @@ VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
VectorVfsDirectory::~VectorVfsDirectory() = default;
-std::vector<std::shared_ptr<VfsFile>> VectorVfsDirectory::GetFiles() const {
+std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const {
return files;
}
-std::vector<std::shared_ptr<VfsDirectory>> VectorVfsDirectory::GetSubdirectories() const {
+std::vector<VirtualDir> VectorVfsDirectory::GetSubdirectories() const {
return dirs;
}
@@ -88,7 +88,7 @@ std::string VectorVfsDirectory::GetName() const {
return name;
}
-std::shared_ptr<VfsDirectory> VectorVfsDirectory::GetParentDirectory() const {
+VirtualDir VectorVfsDirectory::GetParentDirectory() const {
return parent;
}
@@ -116,11 +116,11 @@ bool VectorVfsDirectory::Rename(std::string_view name_) {
return true;
}
-std::shared_ptr<VfsDirectory> VectorVfsDirectory::CreateSubdirectory(std::string_view name) {
+VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view name) {
return nullptr;
}
-std::shared_ptr<VfsFile> VectorVfsDirectory::CreateFile(std::string_view name) {
+VirtualFile VectorVfsDirectory::CreateFile(std::string_view name) {
return nullptr;
}
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index 95d3da2f2..2aff9ca34 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -17,9 +17,9 @@ namespace FileSys {
template <std::size_t size>
class ArrayVfsFile : public VfsFile {
public:
- explicit ArrayVfsFile(const std::array<u8, size>& data, std::string name = "",
- VirtualDir parent = nullptr)
- : data(data), name(std::move(name)), parent(std::move(parent)) {}
+ explicit ArrayVfsFile(const std::array<u8, size>& data_, std::string name_ = "",
+ VirtualDir parent_ = nullptr)
+ : data(data_), name(std::move(name_)), parent(std::move(parent_)) {}
std::string GetName() const override {
return name;
@@ -33,7 +33,7 @@ public:
return false;
}
- std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
+ VirtualDir GetContainingDirectory() const override {
return parent;
}
@@ -51,12 +51,12 @@ public:
return read;
}
- std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
+ std::size_t Write(const u8* data_, std::size_t length, std::size_t offset) override {
return 0;
}
- bool Rename(std::string_view name) override {
- this->name = name;
+ bool Rename(std::string_view new_name) override {
+ name = new_name;
return true;
}
@@ -82,7 +82,7 @@ public:
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
- std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
+ VirtualDir GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
@@ -106,17 +106,17 @@ public:
VirtualDir parent = nullptr);
~VectorVfsDirectory() override;
- std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
- std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
- std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
+ VirtualDir GetParentDirectory() const override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
- std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
- std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
+ VirtualDir CreateSubdirectory(std::string_view name) override;
+ VirtualFile CreateFile(std::string_view name) override;
virtual void AddFile(VirtualFile file);
virtual void AddDirectory(VirtualDir dir);
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index ccf5966d0..814fd5680 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -15,8 +15,9 @@
#include "common/hex_util.h"
#include "common/string_util.h"
#include "core/crypto/aes_util.h"
+#include "core/crypto/key_manager.h"
#include "core/crypto/xts_encryption_layer.h"
-#include "core/file_sys/partition_filesystem.h"
+#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
@@ -43,7 +44,9 @@ static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t
return true;
}
-NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) {
+NAX::NAX(VirtualFile file_)
+ : header(std::make_unique<NAXHeader>()),
+ file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
std::string path = Common::FS::SanitizePath(file->GetFullPath());
static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca",
std::regex_constants::ECMAScript |
@@ -60,7 +63,8 @@ NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::m
}
NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
- : header(std::make_unique<NAXHeader>()), file(std::move(file_)) {
+ : header(std::make_unique<NAXHeader>()),
+ file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
Core::Crypto::SHA256Hash hash{};
mbedtls_sha256_ret(nca_id.data(), nca_id.size(), hash.data(), 0);
status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
@@ -148,11 +152,11 @@ NAXContentType NAX::GetContentType() const {
return type;
}
-std::vector<std::shared_ptr<VfsFile>> NAX::GetFiles() const {
+std::vector<VirtualFile> NAX::GetFiles() const {
return {dec_file};
}
-std::vector<std::shared_ptr<VfsDirectory>> NAX::GetSubdirectories() const {
+std::vector<VirtualDir> NAX::GetSubdirectories() const {
return {};
}
@@ -160,7 +164,7 @@ std::string NAX::GetName() const {
return file->GetName();
}
-std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
+VirtualDir NAX::GetParentDirectory() const {
return file->GetContainingDirectory();
}
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index 563531bb6..63a032b68 100644
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.h
@@ -9,12 +9,16 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/crypto/key_manager.h"
-#include "core/file_sys/content_archive.h"
#include "core/file_sys/vfs.h"
-#include "core/loader/loader.h"
+
+namespace Loader {
+enum class ResultStatus : u16;
+}
namespace FileSys {
+class NCA;
+
struct NAXHeader {
std::array<u8, 0x20> hmac;
u64_le magic;
@@ -43,13 +47,13 @@ public:
NAXContentType GetContentType() const;
- std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
+ std::vector<VirtualFile> GetFiles() const override;
- std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
std::string GetName() const override;
- std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
+ VirtualDir GetParentDirectory() const override;
private:
Loader::ResultStatus Parse(std::string_view path);
@@ -62,6 +66,6 @@ private:
VirtualFile dec_file;
- Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ Core::Crypto::KeyManager& keys;
};
} // namespace FileSys
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
new file mode 100644
index 000000000..03bbedf8b
--- /dev/null
+++ b/src/core/frontend/applets/controller.cpp
@@ -0,0 +1,81 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/frontend/applets/controller.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Core::Frontend {
+
+ControllerApplet::~ControllerApplet() = default;
+
+DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_)
+ : service_manager{service_manager_} {}
+
+DefaultControllerApplet::~DefaultControllerApplet() = default;
+
+void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callback,
+ const ControllerParameters& parameters) const {
+ LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
+
+ auto& npad =
+ service_manager.GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ auto& players = Settings::values.players.GetValue();
+
+ const std::size_t min_supported_players =
+ parameters.enable_single_mode ? 1 : parameters.min_players;
+
+ // Disconnect Handheld first.
+ npad.DisconnectNpadAtIndex(8);
+
+ // Deduce the best configuration based on the input parameters.
+ for (std::size_t index = 0; index < players.size() - 2; ++index) {
+ // First, disconnect all controllers regardless of the value of keep_controllers_connected.
+ // This makes it easy to connect the desired controllers.
+ npad.DisconnectNpadAtIndex(index);
+
+ // Only connect the minimum number of required players.
+ if (index >= min_supported_players) {
+ continue;
+ }
+
+ // Connect controllers based on the following priority list from highest to lowest priority:
+ // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
+ if (parameters.allow_pro_controller) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
+ } else if (parameters.allow_dual_joycons) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
+ } else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
+ // Assign left joycons to even player indices and right joycons to odd player indices.
+ // We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
+ // a right Joycon for Player 2 in 2 Player Assist mode.
+ if (index % 2 == 0) {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
+ } else {
+ npad.AddNewControllerAt(
+ npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
+ }
+ } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
+ !Settings::values.use_docked_mode.GetValue()) {
+ // We should *never* reach here under any normal circumstances.
+ npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
+ index);
+ } else {
+ UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
+ }
+ }
+
+ callback();
+}
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h
new file mode 100644
index 000000000..b0626a0f9
--- /dev/null
+++ b/src/core/frontend/applets/controller.h
@@ -0,0 +1,57 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+
+#include "common/common_types.h"
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Core::Frontend {
+
+using BorderColor = std::array<u8, 4>;
+using ExplainText = std::array<char, 0x81>;
+
+struct ControllerParameters {
+ s8 min_players{};
+ s8 max_players{};
+ bool keep_controllers_connected{};
+ bool enable_single_mode{};
+ bool enable_border_color{};
+ std::vector<BorderColor> border_colors{};
+ bool enable_explain_text{};
+ std::vector<ExplainText> explain_text{};
+ bool allow_pro_controller{};
+ bool allow_handheld{};
+ bool allow_dual_joycons{};
+ bool allow_left_joycon{};
+ bool allow_right_joycon{};
+ bool allow_gamecube_controller{};
+};
+
+class ControllerApplet {
+public:
+ virtual ~ControllerApplet();
+
+ virtual void ReconfigureControllers(std::function<void()> callback,
+ const ControllerParameters& parameters) const = 0;
+};
+
+class DefaultControllerApplet final : public ControllerApplet {
+public:
+ explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_);
+ ~DefaultControllerApplet() override;
+
+ void ReconfigureControllers(std::function<void()> callback,
+ const ControllerParameters& parameters) const override;
+
+private:
+ Service::SM::ServiceManager& service_manager;
+};
+
+} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/error.cpp b/src/core/frontend/applets/error.cpp
index 4002a9211..dceb20ff8 100644
--- a/src/core/frontend/applets/error.cpp
+++ b/src/core/frontend/applets/error.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
#include "core/frontend/applets/error.h"
namespace Core::Frontend {
@@ -10,7 +11,7 @@ ErrorApplet::~ErrorApplet() = default;
void DefaultErrorApplet::ShowError(ResultCode error, std::function<void()> finished) const {
LOG_CRITICAL(Service_Fatal, "Application requested error display: {:04}-{:04} (raw={:08X})",
- static_cast<u32>(error.module.Value()), error.description.Value(), error.raw);
+ error.module.Value(), error.description.Value(), error.raw);
}
void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
@@ -18,7 +19,7 @@ void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::s
LOG_CRITICAL(
Service_Fatal,
"Application requested error display: {:04X}-{:04X} (raw={:08X}) with timestamp={:016X}",
- static_cast<u32>(error.module.Value()), error.description.Value(), error.raw, time.count());
+ error.module.Value(), error.description.Value(), error.raw, time.count());
}
void DefaultErrorApplet::ShowCustomErrorText(ResultCode error, std::string main_text,
@@ -26,7 +27,7 @@ void DefaultErrorApplet::ShowCustomErrorText(ResultCode error, std::string main_
std::function<void()> finished) const {
LOG_CRITICAL(Service_Fatal,
"Application requested custom error with error_code={:04X}-{:04X} (raw={:08X})",
- static_cast<u32>(error.module.Value()), error.description.Value(), error.raw);
+ error.module.Value(), error.description.Value(), error.raw);
LOG_CRITICAL(Service_Fatal, " Main Text: {}", main_text);
LOG_CRITICAL(Service_Fatal, " Detail Text: {}", detail_text);
}
diff --git a/src/core/frontend/applets/general_frontend.cpp b/src/core/frontend/applets/general_frontend.cpp
index c30b36de7..7483ffb76 100644
--- a/src/core/frontend/applets/general_frontend.cpp
+++ b/src/core/frontend/applets/general_frontend.cpp
@@ -53,72 +53,4 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con
finished();
}
-ECommerceApplet::~ECommerceApplet() = default;
-
-DefaultECommerceApplet::~DefaultECommerceApplet() = default;
-
-void DefaultECommerceApplet::ShowApplicationInformation(
- std::function<void()> finished, u64 title_id, std::optional<u128> user_id,
- std::optional<bool> full_display, std::optional<std::string> extra_parameter) {
- const auto value = user_id.value_or(u128{});
- LOG_INFO(Service_AM,
- "Application requested frontend show application information for EShop, "
- "title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}",
- title_id, value[1], value[0],
- full_display.has_value() ? fmt::format("{}", *full_display) : "null",
- extra_parameter.value_or("null"));
- finished();
-}
-
-void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id,
- std::optional<bool> full_display) {
- const auto value = user_id.value_or(u128{});
- LOG_INFO(Service_AM,
- "Application requested frontend show add on content list for EShop, "
- "title_id={:016X}, user_id={:016X}{:016X}, full_display={}",
- title_id, value[1], value[0],
- full_display.has_value() ? fmt::format("{}", *full_display) : "null");
- finished();
-}
-
-void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id) {
- const auto value = user_id.value_or(u128{});
- LOG_INFO(Service_AM,
- "Application requested frontend show subscription list for EShop, title_id={:016X}, "
- "user_id={:016X}{:016X}",
- title_id, value[1], value[0]);
- finished();
-}
-
-void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id) {
- const auto value = user_id.value_or(u128{});
- LOG_INFO(
- Service_AM,
- "Application requested frontend show consumable item list for EShop, title_id={:016X}, "
- "user_id={:016X}{:016X}",
- title_id, value[1], value[0]);
- finished();
-}
-
-void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id,
- bool full_display) {
- LOG_INFO(Service_AM,
- "Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, "
- "full_display={}",
- user_id[1], user_id[0], full_display);
- finished();
-}
-
-void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id,
- bool full_display) {
- LOG_INFO(Service_AM,
- "Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, "
- "full_display={}",
- user_id[1], user_id[0], full_display);
- finished();
-}
-
} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/general_frontend.h b/src/core/frontend/applets/general_frontend.h
index 4b63f828e..b713b14ee 100644
--- a/src/core/frontend/applets/general_frontend.h
+++ b/src/core/frontend/applets/general_frontend.h
@@ -58,55 +58,4 @@ public:
void ShowAllPhotos(std::function<void()> finished) const override;
};
-class ECommerceApplet {
-public:
- virtual ~ECommerceApplet();
-
- // Shows a page with application icons, description, name, and price.
- virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id = {},
- std::optional<bool> full_display = {},
- std::optional<std::string> extra_parameter = {}) = 0;
-
- // Shows a page with all of the add on content available for a game, with name, description, and
- // price.
- virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id = {},
- std::optional<bool> full_display = {}) = 0;
-
- // Shows a page with all of the subscriptions (recurring payments) for a game, with name,
- // description, price, and renewal period.
- virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id = {}) = 0;
-
- // Shows a page with a list of any additional game related purchasable items (DLC,
- // subscriptions, etc) for a particular game, with name, description, type, and price.
- virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id = {}) = 0;
-
- // Shows the home page of the shop.
- virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0;
-
- // Shows the user settings page of the shop.
- virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0;
-};
-
-class DefaultECommerceApplet : public ECommerceApplet {
-public:
- ~DefaultECommerceApplet() override;
-
- void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id, std::optional<bool> full_display,
- std::optional<std::string> extra_parameter) override;
- void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id,
- std::optional<bool> full_display) override;
- void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id) override;
- void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
- std::optional<u128> user_id) override;
- void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override;
- void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override;
-};
-
} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp
index 528295ffc..50db6a654 100644
--- a/src/core/frontend/applets/web_browser.cpp
+++ b/src/core/frontend/applets/web_browser.cpp
@@ -11,14 +11,22 @@ WebBrowserApplet::~WebBrowserApplet() = default;
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
-void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename,
- std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) {
- LOG_INFO(Service_AM,
- "(STUBBED) called - No suitable web browser implementation found to open website page "
- "at '{}'!",
- filename);
- finished_callback();
+void DefaultWebBrowserApplet::OpenLocalWebPage(
+ std::string_view local_url, std::function<void()> extract_romfs_callback,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const {
+ LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open local web page at {}",
+ local_url);
+
+ callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
+}
+
+void DefaultWebBrowserApplet::OpenExternalWebPage(
+ std::string_view external_url,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const {
+ LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open external web page at {}",
+ external_url);
+
+ callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
}
} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h
index 110e33bc4..1c5ef19a9 100644
--- a/src/core/frontend/applets/web_browser.h
+++ b/src/core/frontend/applets/web_browser.h
@@ -7,22 +7,34 @@
#include <functional>
#include <string_view>
+#include "core/hle/service/am/applets/web_types.h"
+
namespace Core::Frontend {
class WebBrowserApplet {
public:
virtual ~WebBrowserApplet();
- virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) = 0;
+ virtual void OpenLocalWebPage(
+ std::string_view local_url, std::function<void()> extract_romfs_callback,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0;
+
+ virtual void OpenExternalWebPage(
+ std::string_view external_url,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0;
};
class DefaultWebBrowserApplet final : public WebBrowserApplet {
public:
~DefaultWebBrowserApplet() override;
- void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) override;
+ void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)>
+ callback) const override;
+
+ void OpenExternalWebPage(std::string_view external_url,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)>
+ callback) const override;
};
} // namespace Core::Frontend
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 9a081fbd4..ee7a58b1c 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -21,21 +21,18 @@ public:
std::mutex mutex;
- bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false
-
- float touch_x = 0.0f; ///< Touchpad X-position
- float touch_y = 0.0f; ///< Touchpad Y-position
+ Input::TouchStatus status;
private:
class Device : public Input::TouchDevice {
public:
explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {}
- std::tuple<float, float, bool> GetStatus() const override {
+ Input::TouchStatus GetStatus() const override {
if (auto state = touch_state.lock()) {
std::lock_guard guard{state->mutex};
- return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed);
+ return state->status;
}
- return std::make_tuple(0.0f, 0.0f, false);
+ return {};
}
private:
@@ -79,34 +76,44 @@ std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsi
return std::make_tuple(new_x, new_y);
}
-void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
- if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
+void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y, std::size_t id) {
+ if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
return;
+ }
+ if (id >= touch_state->status.size()) {
+ return;
+ }
std::lock_guard guard{touch_state->mutex};
- touch_state->touch_x = static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
- (framebuffer_layout.screen.right - framebuffer_layout.screen.left);
- touch_state->touch_y = static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
- (framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
-
- touch_state->touch_pressed = true;
+ const float x =
+ static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
+ static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
+ const float y =
+ static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
+ static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
+
+ touch_state->status[id] = std::make_tuple(x, y, true);
}
-void EmuWindow::TouchReleased() {
+void EmuWindow::TouchReleased(std::size_t id) {
+ if (id >= touch_state->status.size()) {
+ return;
+ }
std::lock_guard guard{touch_state->mutex};
- touch_state->touch_pressed = false;
- touch_state->touch_x = 0;
- touch_state->touch_y = 0;
+ touch_state->status[id] = std::make_tuple(0.0f, 0.0f, false);
}
-void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
- if (!touch_state->touch_pressed)
+void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y, std::size_t id) {
+ if (id >= touch_state->status.size()) {
+ return;
+ }
+ if (!std::get<2>(touch_state->status[id]))
return;
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
- TouchPressed(framebuffer_x, framebuffer_y);
+ TouchPressed(framebuffer_x, framebuffer_y, id);
}
void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 3e8780243..2436c6580 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -102,8 +102,8 @@ public:
float render_surface_scale = 1.0f;
};
- /// Polls window events
- virtual void PollEvents() = 0;
+ /// Called from GPU thread when a frame is displayed.
+ virtual void OnFrameDisplayed() {}
/**
* Returns a GraphicsContext that the frontend provides to be used for rendering.
@@ -117,18 +117,23 @@ public:
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
* @param framebuffer_x Framebuffer x-coordinate that was pressed
* @param framebuffer_y Framebuffer y-coordinate that was pressed
+ * @param id Touch event ID
*/
- void TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y);
+ void TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y, std::size_t id);
- /// Signal that a touch released event has occurred (e.g. mouse click released)
- void TouchReleased();
+ /**
+ * Signal that a touch released event has occurred (e.g. mouse click released)
+ * @param id Touch event ID
+ */
+ void TouchReleased(std::size_t id);
/**
* Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window)
* @param framebuffer_x Framebuffer x-coordinate
* @param framebuffer_y Framebuffer y-coordinate
+ * @param id Touch event ID
*/
- void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
+ void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y, std::size_t id);
/**
* Returns currently active configuration.
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index c1fbc235b..b9a270a55 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -14,8 +14,8 @@ namespace Layout {
template <class T>
static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area,
float screen_aspect_ratio) {
- float scale = std::min(static_cast<float>(window_area.GetWidth()),
- window_area.GetHeight() / screen_aspect_ratio);
+ const float scale = std::min(static_cast<float>(window_area.GetWidth()),
+ static_cast<float>(window_area.GetHeight()) / screen_aspect_ratio);
return Common::Rectangle<T>{0, 0, static_cast<T>(std::round(scale)),
static_cast<T>(std::round(scale * screen_aspect_ratio))};
}
@@ -27,7 +27,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
// so just calculate them both even if the other isn't showing.
FramebufferLayout res{width, height, false, {}};
- const float window_aspect_ratio = static_cast<float>(height) / width;
+ const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
const float emulation_aspect_ratio = EmulationAspectRatio(
static_cast<AspectRatio>(Settings::values.aspect_ratio.GetValue()), window_aspect_ratio);
@@ -47,7 +47,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
u32 width, height;
- if (Settings::values.use_docked_mode) {
+ if (Settings::values.use_docked_mode.GetValue()) {
width = ScreenDocked::Width * res_scale;
height = ScreenDocked::Height * res_scale;
} else {
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 91ecc30ab..e2e3bbbb3 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -4,6 +4,7 @@
#pragma once
+#include "common/common_types.h"
#include "common/math_util.h"
namespace Layout {
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 2b098b7c6..88ebc6497 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -21,6 +21,11 @@ enum class AnalogDirection : u8 {
UP,
DOWN,
};
+struct AnalogProperties {
+ float deadzone;
+ float range;
+ float threshold;
+};
/// An abstract class template for an input device (a button, an analog input, etc.).
template <typename StatusType>
@@ -30,7 +35,18 @@ public:
virtual StatusType GetStatus() const {
return {};
}
- virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const {
+ virtual StatusType GetRawStatus() const {
+ return GetStatus();
+ }
+ virtual AnalogProperties GetAnalogProperties() const {
+ return {};
+ }
+ virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const {
+ return {};
+ }
+ virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low,
+ [[maybe_unused]] f32 amp_high,
+ [[maybe_unused]] f32 freq_high) const {
return {};
}
};
@@ -119,11 +135,18 @@ using ButtonDevice = InputDevice<bool>;
using AnalogDevice = InputDevice<std::tuple<float, float>>;
/**
- * A motion device is an input device that returns a tuple of accelerometer state vector and
- * gyroscope state vector.
+ * A vibration device is an input device that returns an unsigned byte as status.
+ * It represents whether the vibration device supports vibration or not.
+ * If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
+ */
+using VibrationDevice = InputDevice<u8>;
+
+/**
+ * A motion status is an object that returns a tuple of accelerometer state vector,
+ * gyroscope state vector, rotation state vector and orientation state matrix.
*
* For both vectors:
- * x+ is the same direction as LEFT on D-pad.
+ * x+ is the same direction as RIGHT on D-pad.
* y+ is normal to the touch screen, pointing outward.
* z+ is the same direction as UP on D-pad.
*
@@ -133,14 +156,34 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>;
* For gyroscope state vector:
* Orientation is determined by right-hand rule.
* Units: deg/sec
+ *
+ * For rotation state vector
+ * Units: rotations
+ *
+ * For orientation state matrix
+ * x vector
+ * y vector
+ * z vector
+ */
+using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
+ std::array<Common::Vec3f, 3>>;
+
+/**
+ * A motion device is an input device that returns a motion status object
+ */
+using MotionDevice = InputDevice<MotionStatus>;
+
+/**
+ * A touch status is an object that returns an array of 16 tuple elements of two floats and a bool.
+ * The floats are x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is
+ * pressed.
*/
-using MotionDevice = InputDevice<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>;
+using TouchStatus = std::array<std::tuple<float, float, bool>, 16>;
/**
- * A touch device is an input device that returns a tuple of two floats and a bool. The floats are
- * x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is pressed.
+ * A touch device is an input device that returns a touch status object
*/
-using TouchDevice = InputDevice<std::tuple<float, float, bool>>;
+using TouchDevice = InputDevice<TouchStatus>;
/**
* A mouse device is an input device that returns a tuple of two floats and four ints.
diff --git a/src/core/frontend/input_interpreter.cpp b/src/core/frontend/input_interpreter.cpp
new file mode 100644
index 000000000..ec5fe660e
--- /dev/null
+++ b/src/core/frontend/input_interpreter.cpp
@@ -0,0 +1,49 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/frontend/input_interpreter.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+
+InputInterpreter::InputInterpreter(Core::System& system)
+ : npad{system.ServiceManager()
+ .GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)} {}
+
+InputInterpreter::~InputInterpreter() = default;
+
+void InputInterpreter::PollInput() {
+ const u32 button_state = npad.GetAndResetPressState();
+
+ previous_index = current_index;
+ current_index = (current_index + 1) % button_states.size();
+
+ button_states[current_index] = button_state;
+}
+
+bool InputInterpreter::IsButtonPressed(HIDButton button) const {
+ return (button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
+}
+
+bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const {
+ const bool current_press =
+ (button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
+ const bool previous_press =
+ (button_states[previous_index] & (1U << static_cast<u8>(button))) != 0;
+
+ return current_press && !previous_press;
+}
+
+bool InputInterpreter::IsButtonHeld(HIDButton button) const {
+ u32 held_buttons{button_states[0]};
+
+ for (std::size_t i = 1; i < button_states.size(); ++i) {
+ held_buttons &= button_states[i];
+ }
+
+ return (held_buttons & (1U << static_cast<u8>(button))) != 0;
+}
diff --git a/src/core/frontend/input_interpreter.h b/src/core/frontend/input_interpreter.h
new file mode 100644
index 000000000..73fc47ffb
--- /dev/null
+++ b/src/core/frontend/input_interpreter.h
@@ -0,0 +1,141 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::HID {
+class Controller_NPad;
+}
+
+enum class HIDButton : u8 {
+ A,
+ B,
+ X,
+ Y,
+ LStick,
+ RStick,
+ L,
+ R,
+ ZL,
+ ZR,
+ Plus,
+ Minus,
+
+ DLeft,
+ DUp,
+ DRight,
+ DDown,
+
+ LStickLeft,
+ LStickUp,
+ LStickRight,
+ LStickDown,
+
+ RStickLeft,
+ RStickUp,
+ RStickRight,
+ RStickDown,
+
+ LeftSL,
+ LeftSR,
+
+ RightSL,
+ RightSR,
+};
+
+/**
+ * The InputInterpreter class interfaces with HID to retrieve button press states.
+ * Input is intended to be polled every 50ms so that a button is considered to be
+ * held down after 400ms has elapsed since the initial button press and subsequent
+ * repeated presses occur every 50ms.
+ */
+class InputInterpreter {
+public:
+ explicit InputInterpreter(Core::System& system);
+ virtual ~InputInterpreter();
+
+ /// Gets a button state from HID and inserts it into the array of button states.
+ void PollInput();
+
+ /**
+ * Checks whether the button is pressed.
+ *
+ * @param button The button to check.
+ *
+ * @returns True when the button is pressed.
+ */
+ [[nodiscard]] bool IsButtonPressed(HIDButton button) const;
+
+ /**
+ * Checks whether any of the buttons in the parameter list is pressed.
+ *
+ * @tparam HIDButton The buttons to check.
+ *
+ * @returns True when at least one of the buttons is pressed.
+ */
+ template <HIDButton... T>
+ [[nodiscard]] bool IsAnyButtonPressed() {
+ return (IsButtonPressed(T) || ...);
+ }
+
+ /**
+ * The specified button is considered to be pressed once
+ * if it is currently pressed and not pressed previously.
+ *
+ * @param button The button to check.
+ *
+ * @returns True when the button is pressed once.
+ */
+ [[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const;
+
+ /**
+ * Checks whether any of the buttons in the parameter list is pressed once.
+ *
+ * @tparam T The buttons to check.
+ *
+ * @returns True when at least one of the buttons is pressed once.
+ */
+ template <HIDButton... T>
+ [[nodiscard]] bool IsAnyButtonPressedOnce() const {
+ return (IsButtonPressedOnce(T) || ...);
+ }
+
+ /**
+ * The specified button is considered to be held down if it is pressed in all 9 button states.
+ *
+ * @param button The button to check.
+ *
+ * @returns True when the button is held down.
+ */
+ [[nodiscard]] bool IsButtonHeld(HIDButton button) const;
+
+ /**
+ * Checks whether any of the buttons in the parameter list is held down.
+ *
+ * @tparam T The buttons to check.
+ *
+ * @returns True when at least one of the buttons is held down.
+ */
+ template <HIDButton... T>
+ [[nodiscard]] bool IsAnyButtonHeld() const {
+ return (IsButtonHeld(T) || ...);
+ }
+
+private:
+ Service::HID::Controller_NPad& npad;
+
+ /// Stores 9 consecutive button states polled from HID.
+ std::array<u32, 9> button_states{};
+
+ std::size_t previous_index{};
+ std::size_t current_index{};
+};
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
deleted file mode 100644
index 79f22a403..000000000
--- a/src/core/gdbstub/gdbstub.cpp
+++ /dev/null
@@ -1,1397 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-// Originally written by Sven Peter <sven@fail0verflow.com> for anergistic.
-
-#include <algorithm>
-#include <atomic>
-#include <climits>
-#include <csignal>
-#include <cstdarg>
-#include <cstdio>
-#include <cstring>
-#include <map>
-#include <numeric>
-#include <fcntl.h>
-
-#ifdef _WIN32
-#include <winsock2.h>
-// winsock2.h needs to be included first to prevent winsock.h being included by other includes
-#include <io.h>
-#include <iphlpapi.h>
-#include <ws2tcpip.h>
-#define SHUT_RDWR 2
-#else
-#include <netinet/in.h>
-#include <sys/select.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-#endif
-
-#include "common/logging/log.h"
-#include "common/string_util.h"
-#include "common/swap.h"
-#include "core/arm/arm_interface.h"
-#include "core/core.h"
-#include "core/gdbstub/gdbstub.h"
-#include "core/hle/kernel/memory/page_table.h"
-#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/loader/loader.h"
-#include "core/memory.h"
-
-namespace GDBStub {
-namespace {
-constexpr int GDB_BUFFER_SIZE = 10000;
-
-constexpr char GDB_STUB_START = '$';
-constexpr char GDB_STUB_END = '#';
-constexpr char GDB_STUB_ACK = '+';
-constexpr char GDB_STUB_NACK = '-';
-
-#ifndef SIGTRAP
-constexpr u32 SIGTRAP = 5;
-#endif
-
-#ifndef SIGTERM
-constexpr u32 SIGTERM = 15;
-#endif
-
-#ifndef MSG_WAITALL
-constexpr u32 MSG_WAITALL = 8;
-#endif
-
-constexpr u32 LR_REGISTER = 30;
-constexpr u32 SP_REGISTER = 31;
-constexpr u32 PC_REGISTER = 32;
-constexpr u32 PSTATE_REGISTER = 33;
-constexpr u32 UC_ARM64_REG_Q0 = 34;
-constexpr u32 FPCR_REGISTER = 66;
-
-// For sample XML files see the GDB source /gdb/features
-// GDB also wants the l character at the start
-// This XML defines what the registers are for this specific ARM device
-constexpr char target_xml[] =
- R"(l<?xml version="1.0"?>
-<!DOCTYPE target SYSTEM "gdb-target.dtd">
-<target version="1.0">
- <feature name="org.gnu.gdb.aarch64.core">
- <reg name="x0" bitsize="64"/>
- <reg name="x1" bitsize="64"/>
- <reg name="x2" bitsize="64"/>
- <reg name="x3" bitsize="64"/>
- <reg name="x4" bitsize="64"/>
- <reg name="x5" bitsize="64"/>
- <reg name="x6" bitsize="64"/>
- <reg name="x7" bitsize="64"/>
- <reg name="x8" bitsize="64"/>
- <reg name="x9" bitsize="64"/>
- <reg name="x10" bitsize="64"/>
- <reg name="x11" bitsize="64"/>
- <reg name="x12" bitsize="64"/>
- <reg name="x13" bitsize="64"/>
- <reg name="x14" bitsize="64"/>
- <reg name="x15" bitsize="64"/>
- <reg name="x16" bitsize="64"/>
- <reg name="x17" bitsize="64"/>
- <reg name="x18" bitsize="64"/>
- <reg name="x19" bitsize="64"/>
- <reg name="x20" bitsize="64"/>
- <reg name="x21" bitsize="64"/>
- <reg name="x22" bitsize="64"/>
- <reg name="x23" bitsize="64"/>
- <reg name="x24" bitsize="64"/>
- <reg name="x25" bitsize="64"/>
- <reg name="x26" bitsize="64"/>
- <reg name="x27" bitsize="64"/>
- <reg name="x28" bitsize="64"/>
- <reg name="x29" bitsize="64"/>
- <reg name="x30" bitsize="64"/>
- <reg name="sp" bitsize="64" type="data_ptr"/>
-
- <reg name="pc" bitsize="64" type="code_ptr"/>
-
- <flags id="pstate_flags" size="4">
- <field name="SP" start="0" end="0"/>
- <field name="" start="1" end="1"/>
- <field name="EL" start="2" end="3"/>
- <field name="nRW" start="4" end="4"/>
- <field name="" start="5" end="5"/>
- <field name="F" start="6" end="6"/>
- <field name="I" start="7" end="7"/>
- <field name="A" start="8" end="8"/>
- <field name="D" start="9" end="9"/>
-
- <field name="IL" start="20" end="20"/>
- <field name="SS" start="21" end="21"/>
-
- <field name="V" start="28" end="28"/>
- <field name="C" start="29" end="29"/>
- <field name="Z" start="30" end="30"/>
- <field name="N" start="31" end="31"/>
- </flags>
- <reg name="pstate" bitsize="32" type="pstate_flags"/>
- </feature>
- <feature name="org.gnu.gdb.aarch64.fpu">
- </feature>
-</target>
-)";
-
-int gdbserver_socket = -1;
-bool defer_start = false;
-
-u8 command_buffer[GDB_BUFFER_SIZE];
-u32 command_length;
-
-u32 latest_signal = 0;
-bool memory_break = false;
-
-Kernel::Thread* current_thread = nullptr;
-u32 current_core = 0;
-
-// Binding to a port within the reserved ports range (0-1023) requires root permissions,
-// so default to a port outside of that range.
-u16 gdbstub_port = 24689;
-
-bool halt_loop = true;
-bool step_loop = false;
-bool send_trap = false;
-
-// If set to false, the server will never be started and no
-// gdbstub-related functions will be executed.
-std::atomic<bool> server_enabled(false);
-
-#ifdef _WIN32
-WSADATA InitData;
-#endif
-
-struct Breakpoint {
- bool active;
- VAddr addr;
- u64 len;
- std::array<u8, 4> inst;
-};
-
-using BreakpointMap = std::map<VAddr, Breakpoint>;
-BreakpointMap breakpoints_execute;
-BreakpointMap breakpoints_read;
-BreakpointMap breakpoints_write;
-
-struct Module {
- std::string name;
- VAddr beg;
- VAddr end;
-};
-
-std::vector<Module> modules;
-} // Anonymous namespace
-
-void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
- Module module;
- if (add_elf_ext) {
- Common::SplitPath(name, nullptr, &module.name, nullptr);
- module.name += ".elf";
- } else {
- module.name = std::move(name);
- }
- module.beg = beg;
- module.end = end;
- modules.push_back(std::move(module));
-}
-
-static Kernel::Thread* FindThreadById(s64 id) {
- const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
- for (auto& thread : threads) {
- if (thread->GetThreadID() == static_cast<u64>(id)) {
- current_core = thread->GetProcessorID();
- return thread.get();
- }
- }
- return nullptr;
-}
-
-static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) {
- if (!thread) {
- return 0;
- }
-
- const auto& thread_context = thread->GetContext64();
-
- if (id < SP_REGISTER) {
- return thread_context.cpu_registers[id];
- } else if (id == SP_REGISTER) {
- return thread_context.sp;
- } else if (id == PC_REGISTER) {
- return thread_context.pc;
- } else if (id == PSTATE_REGISTER) {
- return thread_context.pstate;
- } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
- return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0];
- } else {
- return 0;
- }
-}
-
-static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr) {
- if (!thread) {
- return;
- }
-
- auto& thread_context = thread->GetContext64();
-
- if (id < SP_REGISTER) {
- thread_context.cpu_registers[id] = val;
- } else if (id == SP_REGISTER) {
- thread_context.sp = val;
- } else if (id == PC_REGISTER) {
- thread_context.pc = val;
- } else if (id == PSTATE_REGISTER) {
- thread_context.pstate = static_cast<u32>(val);
- } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
- thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
- }
-}
-
-static u128 FpuRead(std::size_t id, Kernel::Thread* thread = nullptr) {
- if (!thread) {
- return u128{0};
- }
-
- auto& thread_context = thread->GetContext64();
-
- if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) {
- return thread_context.vector_registers[id - UC_ARM64_REG_Q0];
- } else if (id == FPCR_REGISTER) {
- return u128{thread_context.fpcr, 0};
- } else {
- return u128{0};
- }
-}
-
-static void FpuWrite(std::size_t id, u128 val, Kernel::Thread* thread = nullptr) {
- if (!thread) {
- return;
- }
-
- auto& thread_context = thread->GetContext64();
-
- if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) {
- thread_context.vector_registers[id - UC_ARM64_REG_Q0] = val;
- } else if (id == FPCR_REGISTER) {
- thread_context.fpcr = static_cast<u32>(val[0]);
- }
-}
-
-/**
- * Turns hex string character into the equivalent byte.
- *
- * @param hex Input hex character to be turned into byte.
- */
-static u8 HexCharToValue(u8 hex) {
- if (hex >= '0' && hex <= '9') {
- return hex - '0';
- } else if (hex >= 'a' && hex <= 'f') {
- return hex - 'a' + 0xA;
- } else if (hex >= 'A' && hex <= 'F') {
- return hex - 'A' + 0xA;
- }
-
- LOG_ERROR(Debug_GDBStub, "Invalid nibble: {} ({:02X})", hex, hex);
- return 0;
-}
-
-/**
- * Turn nibble of byte into hex string character.
- *
- * @param n Nibble to be turned into hex character.
- */
-static u8 NibbleToHex(u8 n) {
- n &= 0xF;
- if (n < 0xA) {
- return '0' + n;
- } else {
- return 'a' + n - 0xA;
- }
-}
-
-/**
- * Converts input hex string characters into an array of equivalent of u8 bytes.
- *
- * @param src Pointer to array of output hex string characters.
- * @param len Length of src array.
- */
-static u32 HexToInt(const u8* src, std::size_t len) {
- u32 output = 0;
- while (len-- > 0) {
- output = (output << 4) | HexCharToValue(src[0]);
- src++;
- }
- return output;
-}
-
-/**
- * Converts input hex string characters into an array of equivalent of u8 bytes.
- *
- * @param src Pointer to array of output hex string characters.
- * @param len Length of src array.
- */
-static u64 HexToLong(const u8* src, std::size_t len) {
- u64 output = 0;
- while (len-- > 0) {
- output = (output << 4) | HexCharToValue(src[0]);
- src++;
- }
- return output;
-}
-
-/**
- * Converts input array of u8 bytes into their equivalent hex string characters.
- *
- * @param dest Pointer to buffer to store output hex string characters.
- * @param src Pointer to array of u8 bytes.
- * @param len Length of src array.
- */
-static void MemToGdbHex(u8* dest, const u8* src, std::size_t len) {
- while (len-- > 0) {
- u8 tmp = *src++;
- *dest++ = NibbleToHex(tmp >> 4);
- *dest++ = NibbleToHex(tmp);
- }
-}
-
-/**
- * Converts input gdb-formatted hex string characters into an array of equivalent of u8 bytes.
- *
- * @param dest Pointer to buffer to store u8 bytes.
- * @param src Pointer to array of output hex string characters.
- * @param len Length of src array.
- */
-static void GdbHexToMem(u8* dest, const u8* src, std::size_t len) {
- while (len-- > 0) {
- *dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]);
- src += 2;
- }
-}
-
-/**
- * Convert a u32 into a gdb-formatted hex string.
- *
- * @param dest Pointer to buffer to store output hex string characters.
- * @param v Value to convert.
- */
-static void IntToGdbHex(u8* dest, u32 v) {
- for (int i = 0; i < 8; i += 2) {
- dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i)));
- dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1))));
- }
-}
-
-/**
- * Convert a u64 into a gdb-formatted hex string.
- *
- * @param dest Pointer to buffer to store output hex string characters.
- * @param v Value to convert.
- */
-static void LongToGdbHex(u8* dest, u64 v) {
- for (int i = 0; i < 16; i += 2) {
- dest[i + 1] = NibbleToHex(static_cast<u8>(v >> (4 * i)));
- dest[i] = NibbleToHex(static_cast<u8>(v >> (4 * (i + 1))));
- }
-}
-
-/**
- * Convert a gdb-formatted hex string into a u32.
- *
- * @param src Pointer to hex string.
- */
-static u32 GdbHexToInt(const u8* src) {
- u32 output = 0;
-
- for (int i = 0; i < 8; i += 2) {
- output = (output << 4) | HexCharToValue(src[7 - i - 1]);
- output = (output << 4) | HexCharToValue(src[7 - i]);
- }
-
- return output;
-}
-
-/**
- * Convert a gdb-formatted hex string into a u64.
- *
- * @param src Pointer to hex string.
- */
-static u64 GdbHexToLong(const u8* src) {
- u64 output = 0;
-
- for (int i = 0; i < 16; i += 2) {
- output = (output << 4) | HexCharToValue(src[15 - i - 1]);
- output = (output << 4) | HexCharToValue(src[15 - i]);
- }
-
- return output;
-}
-
-/**
- * Convert a gdb-formatted hex string into a u128.
- *
- * @param src Pointer to hex string.
- */
-static u128 GdbHexToU128(const u8* src) {
- u128 output;
-
- for (int i = 0; i < 16; i += 2) {
- output[0] = (output[0] << 4) | HexCharToValue(src[15 - i - 1]);
- output[0] = (output[0] << 4) | HexCharToValue(src[15 - i]);
- }
-
- for (int i = 0; i < 16; i += 2) {
- output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i - 1]);
- output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i]);
- }
-
- return output;
-}
-
-/// Read a byte from the gdb client.
-static u8 ReadByte() {
- u8 c;
- std::size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL);
- if (received_size != 1) {
- LOG_ERROR(Debug_GDBStub, "recv failed: {}", received_size);
- Shutdown();
- }
-
- return c;
-}
-
-/// Calculate the checksum of the current command buffer.
-static u8 CalculateChecksum(const u8* buffer, std::size_t length) {
- return static_cast<u8>(std::accumulate(buffer, buffer + length, u8{0},
- [](u8 lhs, u8 rhs) { return u8(lhs + rhs); }));
-}
-
-/**
- * Get the map of breakpoints for a given breakpoint type.
- *
- * @param type Type of breakpoint map.
- */
-static BreakpointMap& GetBreakpointMap(BreakpointType type) {
- switch (type) {
- case BreakpointType::Execute:
- return breakpoints_execute;
- case BreakpointType::Read:
- return breakpoints_read;
- case BreakpointType::Write:
- return breakpoints_write;
- default:
- return breakpoints_read;
- }
-}
-
-/**
- * Remove the breakpoint from the given address of the specified type.
- *
- * @param type Type of breakpoint.
- * @param addr Address of breakpoint.
- */
-static void RemoveBreakpoint(BreakpointType type, VAddr addr) {
- BreakpointMap& p = GetBreakpointMap(type);
-
- const auto bp = p.find(addr);
- if (bp == p.end()) {
- return;
- }
-
- LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}",
- bp->second.len, bp->second.addr, static_cast<int>(type));
-
- if (type == BreakpointType::Execute) {
- auto& system = Core::System::GetInstance();
- system.Memory().WriteBlock(bp->second.addr, bp->second.inst.data(), bp->second.inst.size());
- system.InvalidateCpuInstructionCaches();
- }
- p.erase(addr);
-}
-
-BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, BreakpointType type) {
- const BreakpointMap& p = GetBreakpointMap(type);
- const auto next_breakpoint = p.lower_bound(addr);
- BreakpointAddress breakpoint;
-
- if (next_breakpoint != p.end()) {
- breakpoint.address = next_breakpoint->first;
- breakpoint.type = type;
- } else {
- breakpoint.address = 0;
- breakpoint.type = BreakpointType::None;
- }
-
- return breakpoint;
-}
-
-bool CheckBreakpoint(VAddr addr, BreakpointType type) {
- if (!IsConnected()) {
- return false;
- }
-
- const BreakpointMap& p = GetBreakpointMap(type);
- const auto bp = p.find(addr);
-
- if (bp == p.end()) {
- return false;
- }
-
- u64 len = bp->second.len;
-
- // IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints
- // no matter if it's a 4-byte or 2-byte instruction. When you execute a
- // Thumb instruction with a 4-byte breakpoint set, it will set a breakpoint on
- // two instructions instead of the single instruction you placed the breakpoint
- // on. So, as a way to make sure that execution breakpoints are only breaking
- // on the instruction that was specified, set the length of an execution
- // breakpoint to 1. This should be fine since the CPU should never begin executing
- // an instruction anywhere except the beginning of the instruction.
- if (type == BreakpointType::Execute) {
- len = 1;
- }
-
- if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) {
- LOG_DEBUG(Debug_GDBStub,
- "Found breakpoint type {} @ {:016X}, range: {:016X}"
- " - {:016X} ({:X} bytes)",
- static_cast<int>(type), addr, bp->second.addr, bp->second.addr + len, len);
- return true;
- }
-
- return false;
-}
-
-/**
- * Send packet to gdb client.
- *
- * @param packet Packet to be sent to client.
- */
-static void SendPacket(const char packet) {
- std::size_t sent_size = send(gdbserver_socket, &packet, 1, 0);
- if (sent_size != 1) {
- LOG_ERROR(Debug_GDBStub, "send failed");
- }
-}
-
-/**
- * Send reply to gdb client.
- *
- * @param reply Reply to be sent to client.
- */
-static void SendReply(const char* reply) {
- if (!IsConnected()) {
- return;
- }
-
- LOG_DEBUG(Debug_GDBStub, "Reply: {}", reply);
-
- memset(command_buffer, 0, sizeof(command_buffer));
-
- command_length = static_cast<u32>(strlen(reply));
- if (command_length + 4 > sizeof(command_buffer)) {
- LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply");
- return;
- }
-
- memcpy(command_buffer + 1, reply, command_length);
-
- u8 checksum = CalculateChecksum(command_buffer, command_length + 1);
- command_buffer[0] = GDB_STUB_START;
- command_buffer[command_length + 1] = GDB_STUB_END;
- command_buffer[command_length + 2] = NibbleToHex(checksum >> 4);
- command_buffer[command_length + 3] = NibbleToHex(checksum);
-
- u8* ptr = command_buffer;
- u32 left = command_length + 4;
- while (left > 0) {
- int sent_size = send(gdbserver_socket, reinterpret_cast<char*>(ptr), left, 0);
- if (sent_size < 0) {
- LOG_ERROR(Debug_GDBStub, "gdb: send failed");
- return Shutdown();
- }
-
- left -= sent_size;
- ptr += sent_size;
- }
-}
-
-/// Handle query command from gdb client.
-static void HandleQuery() {
- LOG_DEBUG(Debug_GDBStub, "gdb: query '{}'", command_buffer + 1);
-
- const char* query = reinterpret_cast<const char*>(command_buffer + 1);
-
- if (strcmp(query, "TStatus") == 0) {
- SendReply("T0");
- } else if (strncmp(query, "Supported", strlen("Supported")) == 0) {
- // PacketSize needs to be large enough for target xml
- std::string buffer = "PacketSize=2000;qXfer:features:read+;qXfer:threads:read+";
- if (!modules.empty()) {
- buffer += ";qXfer:libraries:read+";
- }
- SendReply(buffer.c_str());
- } else if (strncmp(query, "Xfer:features:read:target.xml:",
- strlen("Xfer:features:read:target.xml:")) == 0) {
- SendReply(target_xml);
- } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) {
- const VAddr base_address =
- Core::System::GetInstance().CurrentProcess()->PageTable().GetCodeRegionStart();
- std::string buffer = fmt::format("TextSeg={:0x}", base_address);
- SendReply(buffer.c_str());
- } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
- std::string val = "m";
- const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
- for (const auto& thread : threads) {
- val += fmt::format("{:x},", thread->GetThreadID());
- }
- val.pop_back();
- SendReply(val.c_str());
- } else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) {
- SendReply("l");
- } else if (strncmp(query, "Xfer:threads:read", strlen("Xfer:threads:read")) == 0) {
- std::string buffer;
- buffer += "l<?xml version=\"1.0\"?>";
- buffer += "<threads>";
- const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
- for (const auto& thread : threads) {
- buffer +=
- fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
- thread->GetThreadID(), thread->GetProcessorID(), thread->GetThreadID());
- }
- buffer += "</threads>";
- SendReply(buffer.c_str());
- } else if (strncmp(query, "Xfer:libraries:read", strlen("Xfer:libraries:read")) == 0) {
- std::string buffer;
- buffer += "l<?xml version=\"1.0\"?>";
- buffer += "<library-list>";
- for (const auto& module : modules) {
- buffer +=
- fmt::format(R"*("<library name = "{}"><segment address = "0x{:x}"/></library>)*",
- module.name, module.beg);
- }
- buffer += "</library-list>";
- SendReply(buffer.c_str());
- } else {
- SendReply("");
- }
-}
-
-/// Handle set thread command from gdb client.
-static void HandleSetThread() {
- int thread_id = -1;
- if (command_buffer[2] != '-') {
- thread_id = static_cast<int>(HexToInt(command_buffer + 2, command_length - 2));
- }
- if (thread_id >= 1) {
- current_thread = FindThreadById(thread_id);
- }
- if (!current_thread) {
- thread_id = 1;
- current_thread = FindThreadById(thread_id);
- }
- if (current_thread) {
- SendReply("OK");
- return;
- }
- SendReply("E01");
-}
-
-/// Handle thread alive command from gdb client.
-static void HandleThreadAlive() {
- int thread_id = static_cast<int>(HexToInt(command_buffer + 1, command_length - 1));
- if (thread_id == 0) {
- thread_id = 1;
- }
- if (FindThreadById(thread_id)) {
- SendReply("OK");
- return;
- }
- SendReply("E01");
-}
-
-/**
- * Send signal packet to client.
- *
- * @param signal Signal to be sent to client.
- */
-static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
- if (gdbserver_socket == -1) {
- return;
- }
-
- latest_signal = signal;
-
- if (!thread) {
- full = false;
- }
-
- std::string buffer;
- if (full) {
- buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};{:02x}:{:016x}", latest_signal,
- PC_REGISTER, Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER,
- Common::swap64(RegRead(SP_REGISTER, thread)), LR_REGISTER,
- Common::swap64(RegRead(LR_REGISTER, thread)));
- } else {
- buffer = fmt::format("T{:02x}", latest_signal);
- }
-
- if (thread) {
- buffer += fmt::format(";thread:{:x};", thread->GetThreadID());
- }
-
- SendReply(buffer.c_str());
-}
-
-/// Read command from gdb client.
-static void ReadCommand() {
- command_length = 0;
- memset(command_buffer, 0, sizeof(command_buffer));
-
- u8 c = ReadByte();
- if (c == '+') {
- // ignore ack
- return;
- } else if (c == 0x03) {
- LOG_INFO(Debug_GDBStub, "gdb: found break command");
- halt_loop = true;
- SendSignal(current_thread, SIGTRAP);
- return;
- } else if (c != GDB_STUB_START) {
- LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte {:02X}", c);
- return;
- }
-
- while ((c = ReadByte()) != GDB_STUB_END) {
- if (command_length >= sizeof(command_buffer)) {
- LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow");
- SendPacket(GDB_STUB_NACK);
- return;
- }
- command_buffer[command_length++] = c;
- }
-
- u8 checksum_received = HexCharToValue(ReadByte()) << 4;
- checksum_received |= HexCharToValue(ReadByte());
-
- u8 checksum_calculated = CalculateChecksum(command_buffer, command_length);
-
- if (checksum_received != checksum_calculated) {
- LOG_ERROR(Debug_GDBStub,
- "gdb: invalid checksum: calculated {:02X} and read {:02X} for ${}# (length: {})",
- checksum_calculated, checksum_received, command_buffer, command_length);
-
- command_length = 0;
-
- SendPacket(GDB_STUB_NACK);
- return;
- }
-
- SendPacket(GDB_STUB_ACK);
-}
-
-/// Check if there is data to be read from the gdb client.
-static bool IsDataAvailable() {
- if (!IsConnected()) {
- return false;
- }
-
- fd_set fd_socket;
-
- FD_ZERO(&fd_socket);
- FD_SET(static_cast<u32>(gdbserver_socket), &fd_socket);
-
- struct timeval t;
- t.tv_sec = 0;
- t.tv_usec = 0;
-
- if (select(gdbserver_socket + 1, &fd_socket, nullptr, nullptr, &t) < 0) {
- LOG_ERROR(Debug_GDBStub, "select failed");
- return false;
- }
-
- return FD_ISSET(gdbserver_socket, &fd_socket) != 0;
-}
-
-/// Send requested register to gdb client.
-static void ReadRegister() {
- static u8 reply[64];
- memset(reply, 0, sizeof(reply));
-
- u32 id = HexCharToValue(command_buffer[1]);
- if (command_buffer[2] != '\0') {
- id <<= 4;
- id |= HexCharToValue(command_buffer[2]);
- }
-
- if (id <= SP_REGISTER) {
- LongToGdbHex(reply, RegRead(id, current_thread));
- } else if (id == PC_REGISTER) {
- LongToGdbHex(reply, RegRead(id, current_thread));
- } else if (id == PSTATE_REGISTER) {
- IntToGdbHex(reply, static_cast<u32>(RegRead(id, current_thread)));
- } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) {
- u128 r = FpuRead(id, current_thread);
- LongToGdbHex(reply, r[0]);
- LongToGdbHex(reply + 16, r[1]);
- } else if (id == FPCR_REGISTER) {
- u128 r = FpuRead(id, current_thread);
- IntToGdbHex(reply, static_cast<u32>(r[0]));
- } else if (id == FPCR_REGISTER + 1) {
- u128 r = FpuRead(id, current_thread);
- IntToGdbHex(reply, static_cast<u32>(r[0] >> 32));
- }
-
- SendReply(reinterpret_cast<char*>(reply));
-}
-
-/// Send all registers to the gdb client.
-static void ReadRegisters() {
- static u8 buffer[GDB_BUFFER_SIZE - 4];
- memset(buffer, 0, sizeof(buffer));
-
- u8* bufptr = buffer;
-
- for (u32 reg = 0; reg <= SP_REGISTER; reg++) {
- LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread));
- }
-
- bufptr += 32 * 16;
-
- LongToGdbHex(bufptr, RegRead(PC_REGISTER, current_thread));
-
- bufptr += 16;
-
- IntToGdbHex(bufptr, static_cast<u32>(RegRead(PSTATE_REGISTER, current_thread)));
-
- bufptr += 8;
-
- u128 r;
-
- for (u32 reg = UC_ARM64_REG_Q0; reg < FPCR_REGISTER; reg++) {
- r = FpuRead(reg, current_thread);
- LongToGdbHex(bufptr + reg * 32, r[0]);
- LongToGdbHex(bufptr + reg * 32 + 16, r[1]);
- }
-
- bufptr += 32 * 32;
-
- r = FpuRead(FPCR_REGISTER, current_thread);
- IntToGdbHex(bufptr, static_cast<u32>(r[0]));
-
- bufptr += 8;
-
- SendReply(reinterpret_cast<char*>(buffer));
-}
-
-/// Modify data of register specified by gdb client.
-static void WriteRegister() {
- const u8* buffer_ptr = command_buffer + 3;
-
- u32 id = HexCharToValue(command_buffer[1]);
- if (command_buffer[2] != '=') {
- ++buffer_ptr;
- id <<= 4;
- id |= HexCharToValue(command_buffer[2]);
- }
-
- if (id <= SP_REGISTER) {
- RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
- } else if (id == PC_REGISTER) {
- RegWrite(id, GdbHexToLong(buffer_ptr), current_thread);
- } else if (id == PSTATE_REGISTER) {
- RegWrite(id, GdbHexToInt(buffer_ptr), current_thread);
- } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) {
- FpuWrite(id, GdbHexToU128(buffer_ptr), current_thread);
- } else if (id == FPCR_REGISTER) {
- } else if (id == FPCR_REGISTER + 1) {
- }
-
- // Update ARM context, skipping scheduler - no running threads at this point
- Core::System::GetInstance()
- .ArmInterface(current_core)
- .LoadContext(current_thread->GetContext64());
-
- SendReply("OK");
-}
-
-/// Modify all registers with data received from the client.
-static void WriteRegisters() {
- const u8* buffer_ptr = command_buffer + 1;
-
- if (command_buffer[0] != 'G')
- return SendReply("E01");
-
- for (u32 i = 0, reg = 0; reg <= FPCR_REGISTER; i++, reg++) {
- if (reg <= SP_REGISTER) {
- RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread);
- } else if (reg == PC_REGISTER) {
- RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread);
- } else if (reg == PSTATE_REGISTER) {
- RegWrite(PSTATE_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread);
- } else if (reg >= UC_ARM64_REG_Q0 && reg < FPCR_REGISTER) {
- RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread);
- } else if (reg == FPCR_REGISTER) {
- RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread);
- } else if (reg == FPCR_REGISTER + 1) {
- RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread);
- }
- }
-
- // Update ARM context, skipping scheduler - no running threads at this point
- Core::System::GetInstance()
- .ArmInterface(current_core)
- .LoadContext(current_thread->GetContext64());
-
- SendReply("OK");
-}
-
-/// Read location in memory specified by gdb client.
-static void ReadMemory() {
- static u8 reply[GDB_BUFFER_SIZE - 4];
-
- auto start_offset = command_buffer + 1;
- const auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
- const VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
-
- start_offset = addr_pos + 1;
- const u64 len =
- HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset));
-
- LOG_DEBUG(Debug_GDBStub, "gdb: addr: {:016X} len: {:016X}", addr, len);
-
- if (len * 2 > sizeof(reply)) {
- SendReply("E01");
- }
-
- auto& memory = Core::System::GetInstance().Memory();
- if (!memory.IsValidVirtualAddress(addr)) {
- return SendReply("E00");
- }
-
- std::vector<u8> data(len);
- memory.ReadBlock(addr, data.data(), len);
-
- MemToGdbHex(reply, data.data(), len);
- reply[len * 2] = '\0';
- SendReply(reinterpret_cast<char*>(reply));
-}
-
-/// Modify location in memory with data received from the gdb client.
-static void WriteMemory() {
- auto start_offset = command_buffer + 1;
- const auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
- const VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
-
- start_offset = addr_pos + 1;
- const auto len_pos = std::find(start_offset, command_buffer + command_length, ':');
- const u64 len = HexToLong(start_offset, static_cast<u64>(len_pos - start_offset));
-
- auto& system = Core::System::GetInstance();
- auto& memory = system.Memory();
- if (!memory.IsValidVirtualAddress(addr)) {
- return SendReply("E00");
- }
-
- std::vector<u8> data(len);
- GdbHexToMem(data.data(), len_pos + 1, len);
- memory.WriteBlock(addr, data.data(), len);
- system.InvalidateCpuInstructionCaches();
- SendReply("OK");
-}
-
-void Break(bool is_memory_break) {
- send_trap = true;
-
- memory_break = is_memory_break;
-}
-
-/// Tell the CPU that it should perform a single step.
-static void Step() {
- if (command_length > 1) {
- RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
- // Update ARM context, skipping scheduler - no running threads at this point
- Core::System::GetInstance()
- .ArmInterface(current_core)
- .LoadContext(current_thread->GetContext64());
- }
- step_loop = true;
- halt_loop = true;
- send_trap = true;
- Core::System::GetInstance().InvalidateCpuInstructionCaches();
-}
-
-/// Tell the CPU if we hit a memory breakpoint.
-bool IsMemoryBreak() {
- if (!IsConnected()) {
- return false;
- }
-
- return memory_break;
-}
-
-/// Tell the CPU to continue executing.
-static void Continue() {
- memory_break = false;
- step_loop = false;
- halt_loop = false;
- Core::System::GetInstance().InvalidateCpuInstructionCaches();
-}
-
-/**
- * Commit breakpoint to list of breakpoints.
- *
- * @param type Type of breakpoint.
- * @param addr Address of breakpoint.
- * @param len Length of breakpoint.
- */
-static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) {
- BreakpointMap& p = GetBreakpointMap(type);
-
- Breakpoint breakpoint;
- breakpoint.active = true;
- breakpoint.addr = addr;
- breakpoint.len = len;
-
- auto& system = Core::System::GetInstance();
- auto& memory = system.Memory();
- memory.ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size());
-
- static constexpr std::array<u8, 4> btrap{0x00, 0x7d, 0x20, 0xd4};
- if (type == BreakpointType::Execute) {
- memory.WriteBlock(addr, btrap.data(), btrap.size());
- system.InvalidateCpuInstructionCaches();
- }
- p.insert({addr, breakpoint});
-
- LOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:016X} bytes at {:016X}",
- static_cast<int>(type), breakpoint.len, breakpoint.addr);
-
- return true;
-}
-
-/// Handle add breakpoint command from gdb client.
-static void AddBreakpoint() {
- BreakpointType type;
-
- u8 type_id = HexCharToValue(command_buffer[1]);
- switch (type_id) {
- case 0:
- case 1:
- type = BreakpointType::Execute;
- break;
- case 2:
- type = BreakpointType::Write;
- break;
- case 3:
- type = BreakpointType::Read;
- break;
- case 4:
- type = BreakpointType::Access;
- break;
- default:
- return SendReply("E01");
- }
-
- auto start_offset = command_buffer + 3;
- auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
- VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
-
- start_offset = addr_pos + 1;
- u64 len =
- HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset));
-
- if (type == BreakpointType::Access) {
- // Access is made up of Read and Write types, so add both breakpoints
- type = BreakpointType::Read;
-
- if (!CommitBreakpoint(type, addr, len)) {
- return SendReply("E02");
- }
-
- type = BreakpointType::Write;
- }
-
- if (!CommitBreakpoint(type, addr, len)) {
- return SendReply("E02");
- }
-
- SendReply("OK");
-}
-
-/// Handle remove breakpoint command from gdb client.
-static void RemoveBreakpoint() {
- BreakpointType type;
-
- u8 type_id = HexCharToValue(command_buffer[1]);
- switch (type_id) {
- case 0:
- case 1:
- type = BreakpointType::Execute;
- break;
- case 2:
- type = BreakpointType::Write;
- break;
- case 3:
- type = BreakpointType::Read;
- break;
- case 4:
- type = BreakpointType::Access;
- break;
- default:
- return SendReply("E01");
- }
-
- auto start_offset = command_buffer + 3;
- auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
- VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
-
- if (type == BreakpointType::Access) {
- // Access is made up of Read and Write types, so add both breakpoints
- type = BreakpointType::Read;
- RemoveBreakpoint(type, addr);
-
- type = BreakpointType::Write;
- }
-
- RemoveBreakpoint(type, addr);
- SendReply("OK");
-}
-
-void HandlePacket() {
- if (!IsConnected()) {
- if (defer_start) {
- ToggleServer(true);
- }
- return;
- }
-
- if (!IsDataAvailable()) {
- return;
- }
-
- ReadCommand();
- if (command_length == 0) {
- return;
- }
-
- LOG_DEBUG(Debug_GDBStub, "Packet: {}", command_buffer);
-
- switch (command_buffer[0]) {
- case 'q':
- HandleQuery();
- break;
- case 'H':
- HandleSetThread();
- break;
- case '?':
- SendSignal(current_thread, latest_signal);
- break;
- case 'k':
- Shutdown();
- LOG_INFO(Debug_GDBStub, "killed by gdb");
- return;
- case 'g':
- ReadRegisters();
- break;
- case 'G':
- WriteRegisters();
- break;
- case 'p':
- ReadRegister();
- break;
- case 'P':
- WriteRegister();
- break;
- case 'm':
- ReadMemory();
- break;
- case 'M':
- WriteMemory();
- break;
- case 's':
- Step();
- return;
- case 'C':
- case 'c':
- Continue();
- return;
- case 'z':
- RemoveBreakpoint();
- break;
- case 'Z':
- AddBreakpoint();
- break;
- case 'T':
- HandleThreadAlive();
- break;
- default:
- SendReply("");
- break;
- }
-}
-
-void SetServerPort(u16 port) {
- gdbstub_port = port;
-}
-
-void ToggleServer(bool status) {
- if (status) {
- server_enabled = status;
-
- // Start server
- if (!IsConnected() && Core::System::GetInstance().IsPoweredOn()) {
- Init();
- }
- } else {
- // Stop server
- if (IsConnected()) {
- Shutdown();
- }
-
- server_enabled = status;
- }
-}
-
-void DeferStart() {
- defer_start = true;
-}
-
-static void Init(u16 port) {
- if (!server_enabled) {
- // Set the halt loop to false in case the user enabled the gdbstub mid-execution.
- // This way the CPU can still execute normally.
- halt_loop = false;
- step_loop = false;
- return;
- }
-
- // Setup initial gdbstub status
- halt_loop = true;
- step_loop = false;
-
- breakpoints_execute.clear();
- breakpoints_read.clear();
- breakpoints_write.clear();
-
- modules.clear();
-
- // Start gdb server
- LOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port);
-
- sockaddr_in saddr_server = {};
- saddr_server.sin_family = AF_INET;
- saddr_server.sin_port = htons(port);
- saddr_server.sin_addr.s_addr = INADDR_ANY;
-
-#ifdef _WIN32
- WSAStartup(MAKEWORD(2, 2), &InitData);
-#endif
-
- int tmpsock = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0));
- if (tmpsock == -1) {
- LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket");
- }
-
- // Set socket to SO_REUSEADDR so it can always bind on the same port
- int reuse_enabled = 1;
- if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled,
- sizeof(reuse_enabled)) < 0) {
- LOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option");
- }
-
- const sockaddr* server_addr = reinterpret_cast<const sockaddr*>(&saddr_server);
- socklen_t server_addrlen = sizeof(saddr_server);
- if (bind(tmpsock, server_addr, server_addrlen) < 0) {
- LOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket");
- }
-
- if (listen(tmpsock, 1) < 0) {
- LOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket");
- }
-
- // Wait for gdb to connect
- LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...");
- sockaddr_in saddr_client;
- sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client);
- socklen_t client_addrlen = sizeof(saddr_client);
- gdbserver_socket = static_cast<int>(accept(tmpsock, client_addr, &client_addrlen));
- if (gdbserver_socket < 0) {
- // In the case that we couldn't start the server for whatever reason, just start CPU
- // execution like normal.
- halt_loop = false;
- step_loop = false;
-
- LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client");
- } else {
- LOG_INFO(Debug_GDBStub, "Client connected.");
- saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr);
- }
-
- // Clean up temporary socket if it's still alive at this point.
- if (tmpsock != -1) {
- shutdown(tmpsock, SHUT_RDWR);
- }
-}
-
-void Init() {
- Init(gdbstub_port);
-}
-
-void Shutdown() {
- if (!server_enabled) {
- return;
- }
- defer_start = false;
-
- LOG_INFO(Debug_GDBStub, "Stopping GDB ...");
- if (gdbserver_socket != -1) {
- shutdown(gdbserver_socket, SHUT_RDWR);
- gdbserver_socket = -1;
- }
-
-#ifdef _WIN32
- WSACleanup();
-#endif
-
- LOG_INFO(Debug_GDBStub, "GDB stopped.");
-}
-
-bool IsServerEnabled() {
- return server_enabled;
-}
-
-bool IsConnected() {
- return IsServerEnabled() && gdbserver_socket != -1;
-}
-
-bool GetCpuHaltFlag() {
- return halt_loop;
-}
-
-bool GetCpuStepFlag() {
- return step_loop;
-}
-
-void SetCpuStepFlag(bool is_step) {
- step_loop = is_step;
-}
-
-void SendTrap(Kernel::Thread* thread, int trap) {
- if (!send_trap) {
- return;
- }
-
- current_thread = thread;
- SendSignal(thread, trap);
-
- halt_loop = true;
- send_trap = false;
-}
-}; // namespace GDBStub
diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h
deleted file mode 100644
index 8fe3c320b..000000000
--- a/src/core/gdbstub/gdbstub.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-// Originally written by Sven Peter <sven@fail0verflow.com> for anergistic.
-
-#pragma once
-
-#include <string>
-#include "common/common_types.h"
-#include "core/hle/kernel/thread.h"
-
-namespace GDBStub {
-
-/// Breakpoint Method
-enum class BreakpointType {
- None, ///< None
- Execute, ///< Execution Breakpoint
- Read, ///< Read Breakpoint
- Write, ///< Write Breakpoint
- Access ///< Access (R/W) Breakpoint
-};
-
-struct BreakpointAddress {
- VAddr address;
- BreakpointType type;
-};
-
-/**
- * Set the port the gdbstub should use to listen for connections.
- *
- * @param port Port to listen for connection
- */
-void SetServerPort(u16 port);
-
-/**
- * Starts or stops the server if possible.
- *
- * @param status Set the server to enabled or disabled.
- */
-void ToggleServer(bool status);
-
-/// Start the gdbstub server.
-void Init();
-
-/**
- * Defer initialization of the gdbstub to the first packet processing functions.
- * This avoids a case where the gdbstub thread is frozen after initialization
- * and fails to respond in time to packets.
- */
-void DeferStart();
-
-/// Stop gdbstub server.
-void Shutdown();
-
-/// Checks if the gdbstub server is enabled.
-bool IsServerEnabled();
-
-/// Returns true if there is an active socket connection.
-bool IsConnected();
-
-/// Register module.
-void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext = true);
-
-/**
- * Signal to the gdbstub server that it should halt CPU execution.
- *
- * @param is_memory_break If true, the break resulted from a memory breakpoint.
- */
-void Break(bool is_memory_break = false);
-
-/// Determine if there was a memory breakpoint.
-bool IsMemoryBreak();
-
-/// Read and handle packet from gdb client.
-void HandlePacket();
-
-/**
- * Get the nearest breakpoint of the specified type at the given address.
- *
- * @param addr Address to search from.
- * @param type Type of breakpoint.
- */
-BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, GDBStub::BreakpointType type);
-
-/**
- * Check if a breakpoint of the specified type exists at the given address.
- *
- * @param addr Address of breakpoint.
- * @param type Type of breakpoint.
- */
-bool CheckBreakpoint(VAddr addr, GDBStub::BreakpointType type);
-
-/// If set to true, the CPU will halt at the beginning of the next CPU loop.
-bool GetCpuHaltFlag();
-
-/// If set to true and the CPU is halted, the CPU will step one instruction.
-bool GetCpuStepFlag();
-
-/**
- * When set to true, the CPU will step one instruction when the CPU is halted next.
- *
- * @param is_step
- */
-void SetCpuStepFlag(bool is_step);
-
-/**
- * Send trap signal from thread back to the gdbstub server.
- *
- * @param thread Sending thread.
- * @param trap Trap no.
- */
-void SendTrap(Kernel::Thread* thread, int trap);
-} // namespace GDBStub
diff --git a/src/core/hardware_properties.h b/src/core/hardware_properties.h
index 456b41e1b..176a72c67 100644
--- a/src/core/hardware_properties.h
+++ b/src/core/hardware_properties.h
@@ -4,8 +4,10 @@
#pragma once
+#include <array>
#include <tuple>
+#include "common/bit_util.h"
#include "common/common_types.h"
namespace Core {
@@ -18,34 +20,12 @@ constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch cpu frequency is 1020MHz u
constexpr u64 CNTFREQ = 19200000; // Switch's hardware clock speed
constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores
-} // namespace Hardware
-
-constexpr u32 INVALID_HOST_THREAD_ID = 0xFFFFFFFF;
-
-struct EmuThreadHandle {
- u32 host_handle;
- u32 guest_handle;
-
- u64 GetRaw() const {
- return (static_cast<u64>(host_handle) << 32) | guest_handle;
- }
-
- bool operator==(const EmuThreadHandle& rhs) const {
- return std::tie(host_handle, guest_handle) == std::tie(rhs.host_handle, rhs.guest_handle);
- }
-
- bool operator!=(const EmuThreadHandle& rhs) const {
- return !operator==(rhs);
- }
-
- static constexpr EmuThreadHandle InvalidHandle() {
- constexpr u32 invalid_handle = 0xFFFFFFFF;
- return {invalid_handle, invalid_handle};
- }
-
- bool IsInvalid() const {
- return (*this) == InvalidHandle();
- }
+// Virtual to Physical core map.
+constexpr std::array<s32, Common::BitSize<u64>()> VirtualToPhysicalCoreMap{
+ 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
};
+} // namespace Hardware
+
} // namespace Core
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 7ce313190..55b1716e4 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -146,7 +146,7 @@ static_assert(sizeof(BufferDescriptorC) == 8, "BufferDescriptorC size is incorre
struct DataPayloadHeader {
u32_le magic;
- INSERT_PADDING_WORDS(1);
+ INSERT_PADDING_WORDS_NOINIT(1);
};
static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadHeader size is incorrect");
@@ -160,7 +160,7 @@ struct DomainMessageHeader {
// Used when responding to an IPC request, Server -> Client.
struct {
u32_le num_objects;
- INSERT_UNION_PADDING_WORDS(3);
+ INSERT_PADDING_WORDS_NOINIT(3);
};
// Used when performing an IPC request, Client -> Server.
@@ -171,10 +171,10 @@ struct DomainMessageHeader {
BitField<16, 16, u32> size;
};
u32_le object_id;
- INSERT_UNION_PADDING_WORDS(2);
+ INSERT_PADDING_WORDS_NOINIT(2);
};
- std::array<u32, 4> raw{};
+ std::array<u32, 4> raw;
};
};
static_assert(sizeof(DomainMessageHeader) == 16, "DomainMessageHeader size is incorrect");
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 1b503331f..56cc911d1 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -12,7 +12,6 @@
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
-#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
@@ -38,10 +37,11 @@ public:
explicit RequestHelperBase(Kernel::HLERequestContext& context)
: context(&context), cmdbuf(context.CommandBuffer()) {}
- void Skip(unsigned size_in_words, bool set_to_null) {
- if (set_to_null)
+ void Skip(u32 size_in_words, bool set_to_null) {
+ if (set_to_null) {
memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
- index += size_in_words;
+ }
+ index += static_cast<ptrdiff_t>(size_in_words);
}
/**
@@ -49,15 +49,15 @@ public:
*/
void AlignWithPadding() {
if (index & 3) {
- Skip(4 - (index & 3), true);
+ Skip(static_cast<u32>(4 - (index & 3)), true);
}
}
- unsigned GetCurrentOffset() const {
- return static_cast<unsigned>(index);
+ u32 GetCurrentOffset() const {
+ return static_cast<u32>(index);
}
- void SetCurrentOffset(unsigned offset) {
+ void SetCurrentOffset(u32 offset) {
index = static_cast<ptrdiff_t>(offset);
}
};
@@ -72,14 +72,12 @@ public:
AlwaysMoveHandles = 1,
};
- explicit ResponseBuilder(u32* command_buffer) : RequestHelperBase(command_buffer) {}
-
explicit ResponseBuilder(Kernel::HLERequestContext& context, u32 normal_params_size,
u32 num_handles_to_copy = 0, u32 num_objects_to_move = 0,
Flags flags = Flags::None)
-
: RequestHelperBase(context), normal_params_size(normal_params_size),
- num_handles_to_copy(num_handles_to_copy), num_objects_to_move(num_objects_to_move) {
+ num_handles_to_copy(num_handles_to_copy),
+ num_objects_to_move(num_objects_to_move), kernel{context.kernel} {
memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH);
@@ -89,7 +87,7 @@ public:
// The entire size of the raw data section in u32 units, including the 16 bytes of mandatory
// padding.
- u32 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size;
+ u64 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size;
u32 num_handles_to_move{};
u32 num_domain_objects{};
@@ -105,7 +103,7 @@ public:
raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects;
}
- header.data_size.Assign(raw_data_size);
+ header.data_size.Assign(static_cast<u32>(raw_data_size));
if (num_handles_to_copy || num_handles_to_move) {
header.enable_handle_descriptor.Assign(1);
}
@@ -139,7 +137,6 @@ public:
if (context->Session()->IsDomain()) {
context->AddDomainObject(std::move(iface));
} else {
- auto& kernel = Core::System::GetInstance().Kernel();
auto [client, server] = Kernel::Session::Create(kernel, iface->GetServiceName());
context->AddMoveObject(std::move(client));
iface->ClientConnected(std::move(server));
@@ -169,8 +166,23 @@ public:
ValidateHeader();
}
+ void PushImpl(s8 value);
+ void PushImpl(s16 value);
+ void PushImpl(s32 value);
+ void PushImpl(s64 value);
+ void PushImpl(u8 value);
+ void PushImpl(u16 value);
+ void PushImpl(u32 value);
+ void PushImpl(u64 value);
+ void PushImpl(float value);
+ void PushImpl(double value);
+ void PushImpl(bool value);
+ void PushImpl(ResultCode value);
+
template <typename T>
- void Push(T value);
+ void Push(T value) {
+ return PushImpl(value);
+ }
template <typename First, typename... Other>
void Push(const First& first_value, const Other&... other_values);
@@ -213,17 +225,16 @@ private:
u32 num_handles_to_copy{};
u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent
std::ptrdiff_t datapayload_index{};
+ Kernel::KernelCore& kernel;
};
/// Push ///
-template <>
-inline void ResponseBuilder::Push(s32 value) {
+inline void ResponseBuilder::PushImpl(s32 value) {
cmdbuf[index++] = static_cast<u32>(value);
}
-template <>
-inline void ResponseBuilder::Push(u32 value) {
+inline void ResponseBuilder::PushImpl(u32 value) {
cmdbuf[index++] = value;
}
@@ -235,62 +246,52 @@ void ResponseBuilder::PushRaw(const T& value) {
index += (sizeof(T) + 3) / 4; // round up to word length
}
-template <>
-inline void ResponseBuilder::Push(ResultCode value) {
+inline void ResponseBuilder::PushImpl(ResultCode value) {
// Result codes are actually 64-bit in the IPC buffer, but only the high part is discarded.
Push(value.raw);
Push<u32>(0);
}
-template <>
-inline void ResponseBuilder::Push(s8 value) {
+inline void ResponseBuilder::PushImpl(s8 value) {
PushRaw(value);
}
-template <>
-inline void ResponseBuilder::Push(s16 value) {
+inline void ResponseBuilder::PushImpl(s16 value) {
PushRaw(value);
}
-template <>
-inline void ResponseBuilder::Push(s64 value) {
- Push(static_cast<u32>(value));
- Push(static_cast<u32>(value >> 32));
+inline void ResponseBuilder::PushImpl(s64 value) {
+ PushImpl(static_cast<u32>(value));
+ PushImpl(static_cast<u32>(value >> 32));
}
-template <>
-inline void ResponseBuilder::Push(u8 value) {
+inline void ResponseBuilder::PushImpl(u8 value) {
PushRaw(value);
}
-template <>
-inline void ResponseBuilder::Push(u16 value) {
+inline void ResponseBuilder::PushImpl(u16 value) {
PushRaw(value);
}
-template <>
-inline void ResponseBuilder::Push(u64 value) {
- Push(static_cast<u32>(value));
- Push(static_cast<u32>(value >> 32));
+inline void ResponseBuilder::PushImpl(u64 value) {
+ PushImpl(static_cast<u32>(value));
+ PushImpl(static_cast<u32>(value >> 32));
}
-template <>
-inline void ResponseBuilder::Push(float value) {
+inline void ResponseBuilder::PushImpl(float value) {
u32 integral;
std::memcpy(&integral, &value, sizeof(u32));
- Push(integral);
+ PushImpl(integral);
}
-template <>
-inline void ResponseBuilder::Push(double value) {
+inline void ResponseBuilder::PushImpl(double value) {
u64 integral;
std::memcpy(&integral, &value, sizeof(u64));
- Push(integral);
+ PushImpl(integral);
}
-template <>
-inline void ResponseBuilder::Push(bool value) {
- Push(static_cast<u8>(value));
+inline void ResponseBuilder::PushImpl(bool value) {
+ PushImpl(static_cast<u8>(value));
}
template <typename First, typename... Other>
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
deleted file mode 100644
index b882eaa0f..000000000
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <vector>
-
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "core/arm/exclusive_monitor.h"
-#include "core/core.h"
-#include "core/hle/kernel/address_arbiter.h"
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/time_manager.h"
-#include "core/hle/result.h"
-#include "core/memory.h"
-
-namespace Kernel {
-
-// Wake up num_to_wake (or all) threads in a vector.
-void AddressArbiter::WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads,
- s32 num_to_wake) {
- // Only process up to 'target' threads, unless 'target' is <= 0, in which case process
- // them all.
- std::size_t last = waiting_threads.size();
- if (num_to_wake > 0) {
- last = std::min(last, static_cast<std::size_t>(num_to_wake));
- }
-
- // Signal the waiting threads.
- for (std::size_t i = 0; i < last; i++) {
- waiting_threads[i]->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
- RemoveThread(waiting_threads[i]);
- waiting_threads[i]->WaitForArbitration(false);
- waiting_threads[i]->ResumeFromWait();
- }
-}
-
-AddressArbiter::AddressArbiter(Core::System& system) : system{system} {}
-AddressArbiter::~AddressArbiter() = default;
-
-ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 value,
- s32 num_to_wake) {
- switch (type) {
- case SignalType::Signal:
- return SignalToAddressOnly(address, num_to_wake);
- case SignalType::IncrementAndSignalIfEqual:
- return IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
- case SignalType::ModifyByWaitingCountAndSignalIfEqual:
- return ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, num_to_wake);
- default:
- return ERR_INVALID_ENUM_VALUE;
- }
-}
-
-ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
- SchedulerLock lock(system.Kernel());
- const std::vector<std::shared_ptr<Thread>> waiting_threads =
- GetThreadsWaitingOnAddress(address);
- WakeThreads(waiting_threads, num_to_wake);
- return RESULT_SUCCESS;
-}
-
-ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value,
- s32 num_to_wake) {
- SchedulerLock lock(system.Kernel());
- auto& memory = system.Memory();
-
- // Ensure that we can write to the address.
- if (!memory.IsValidVirtualAddress(address)) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- const std::size_t current_core = system.CurrentCoreIndex();
- auto& monitor = system.Monitor();
- u32 current_value;
- do {
- current_value = monitor.ExclusiveRead32(current_core, address);
-
- if (current_value != static_cast<u32>(value)) {
- return ERR_INVALID_STATE;
- }
- current_value++;
- } while (!monitor.ExclusiveWrite32(current_core, address, current_value));
-
- return SignalToAddressOnly(address, num_to_wake);
-}
-
-ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
- s32 num_to_wake) {
- SchedulerLock lock(system.Kernel());
- auto& memory = system.Memory();
-
- // Ensure that we can write to the address.
- if (!memory.IsValidVirtualAddress(address)) {
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- // Get threads waiting on the address.
- const std::vector<std::shared_ptr<Thread>> waiting_threads =
- GetThreadsWaitingOnAddress(address);
-
- const std::size_t current_core = system.CurrentCoreIndex();
- auto& monitor = system.Monitor();
- s32 updated_value;
- do {
- updated_value = monitor.ExclusiveRead32(current_core, address);
-
- if (updated_value != value) {
- return ERR_INVALID_STATE;
- }
- // Determine the modified value depending on the waiting count.
- if (num_to_wake <= 0) {
- if (waiting_threads.empty()) {
- updated_value = value + 1;
- } else {
- updated_value = value - 1;
- }
- } else {
- if (waiting_threads.empty()) {
- updated_value = value + 1;
- } else if (waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
- updated_value = value - 1;
- } else {
- updated_value = value;
- }
- }
- } while (!monitor.ExclusiveWrite32(current_core, address, updated_value));
-
- WakeThreads(waiting_threads, num_to_wake);
- return RESULT_SUCCESS;
-}
-
-ResultCode AddressArbiter::WaitForAddress(VAddr address, ArbitrationType type, s32 value,
- s64 timeout_ns) {
- switch (type) {
- case ArbitrationType::WaitIfLessThan:
- return WaitForAddressIfLessThan(address, value, timeout_ns, false);
- case ArbitrationType::DecrementAndWaitIfLessThan:
- return WaitForAddressIfLessThan(address, value, timeout_ns, true);
- case ArbitrationType::WaitIfEqual:
- return WaitForAddressIfEqual(address, value, timeout_ns);
- default:
- return ERR_INVALID_ENUM_VALUE;
- }
-}
-
-ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
- bool should_decrement) {
- auto& memory = system.Memory();
- auto& kernel = system.Kernel();
- Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
-
- Handle event_handle = InvalidHandle;
- {
- SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
-
- if (current_thread->IsPendingTermination()) {
- lock.CancelSleep();
- return ERR_THREAD_TERMINATING;
- }
-
- // Ensure that we can read the address.
- if (!memory.IsValidVirtualAddress(address)) {
- lock.CancelSleep();
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- s32 current_value = static_cast<s32>(memory.Read32(address));
- if (current_value >= value) {
- lock.CancelSleep();
- return ERR_INVALID_STATE;
- }
-
- current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
-
- s32 decrement_value;
-
- const std::size_t current_core = system.CurrentCoreIndex();
- auto& monitor = system.Monitor();
- do {
- current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address));
- if (should_decrement) {
- decrement_value = current_value - 1;
- } else {
- decrement_value = current_value;
- }
- } while (
- !monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(decrement_value)));
-
- // Short-circuit without rescheduling, if timeout is zero.
- if (timeout == 0) {
- lock.CancelSleep();
- return RESULT_TIMEOUT;
- }
-
- current_thread->SetArbiterWaitAddress(address);
- InsertThread(SharedFrom(current_thread));
- current_thread->SetStatus(ThreadStatus::WaitArb);
- current_thread->WaitForArbitration(true);
- }
-
- if (event_handle != InvalidHandle) {
- auto& time_manager = kernel.TimeManager();
- time_manager.UnscheduleTimeEvent(event_handle);
- }
-
- {
- SchedulerLock lock(kernel);
- if (current_thread->IsWaitingForArbitration()) {
- RemoveThread(SharedFrom(current_thread));
- current_thread->WaitForArbitration(false);
- }
- }
-
- return current_thread->GetSignalingResult();
-}
-
-ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
- auto& memory = system.Memory();
- auto& kernel = system.Kernel();
- Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
-
- Handle event_handle = InvalidHandle;
- {
- SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
-
- if (current_thread->IsPendingTermination()) {
- lock.CancelSleep();
- return ERR_THREAD_TERMINATING;
- }
-
- // Ensure that we can read the address.
- if (!memory.IsValidVirtualAddress(address)) {
- lock.CancelSleep();
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- s32 current_value = static_cast<s32>(memory.Read32(address));
- if (current_value != value) {
- lock.CancelSleep();
- return ERR_INVALID_STATE;
- }
-
- // Short-circuit without rescheduling, if timeout is zero.
- if (timeout == 0) {
- lock.CancelSleep();
- return RESULT_TIMEOUT;
- }
-
- current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
- current_thread->SetArbiterWaitAddress(address);
- InsertThread(SharedFrom(current_thread));
- current_thread->SetStatus(ThreadStatus::WaitArb);
- current_thread->WaitForArbitration(true);
- }
-
- if (event_handle != InvalidHandle) {
- auto& time_manager = kernel.TimeManager();
- time_manager.UnscheduleTimeEvent(event_handle);
- }
-
- {
- SchedulerLock lock(kernel);
- if (current_thread->IsWaitingForArbitration()) {
- RemoveThread(SharedFrom(current_thread));
- current_thread->WaitForArbitration(false);
- }
- }
-
- return current_thread->GetSignalingResult();
-}
-
-void AddressArbiter::HandleWakeupThread(std::shared_ptr<Thread> thread) {
- ASSERT(thread->GetStatus() == ThreadStatus::WaitArb);
- RemoveThread(thread);
- thread->SetArbiterWaitAddress(0);
-}
-
-void AddressArbiter::InsertThread(std::shared_ptr<Thread> thread) {
- const VAddr arb_addr = thread->GetArbiterWaitAddress();
- std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[arb_addr];
-
- const auto iter =
- std::find_if(thread_list.cbegin(), thread_list.cend(), [&thread](const auto& entry) {
- return entry->GetPriority() >= thread->GetPriority();
- });
-
- if (iter == thread_list.cend()) {
- thread_list.push_back(std::move(thread));
- } else {
- thread_list.insert(iter, std::move(thread));
- }
-}
-
-void AddressArbiter::RemoveThread(std::shared_ptr<Thread> thread) {
- const VAddr arb_addr = thread->GetArbiterWaitAddress();
- std::list<std::shared_ptr<Thread>>& thread_list = arb_threads[arb_addr];
-
- const auto iter = std::find_if(thread_list.cbegin(), thread_list.cend(),
- [&thread](const auto& entry) { return thread == entry; });
-
- if (iter != thread_list.cend()) {
- thread_list.erase(iter);
- }
-}
-
-std::vector<std::shared_ptr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(
- VAddr address) const {
- const auto iter = arb_threads.find(address);
- if (iter == arb_threads.cend()) {
- return {};
- }
-
- const std::list<std::shared_ptr<Thread>>& thread_list = iter->second;
- return {thread_list.cbegin(), thread_list.cend()};
-}
-} // namespace Kernel
diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h
deleted file mode 100644
index 0b05d533c..000000000
--- a/src/core/hle/kernel/address_arbiter.h
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <list>
-#include <memory>
-#include <unordered_map>
-#include <vector>
-
-#include "common/common_types.h"
-
-union ResultCode;
-
-namespace Core {
-class System;
-}
-
-namespace Kernel {
-
-class Thread;
-
-class AddressArbiter {
-public:
- enum class ArbitrationType {
- WaitIfLessThan = 0,
- DecrementAndWaitIfLessThan = 1,
- WaitIfEqual = 2,
- };
-
- enum class SignalType {
- Signal = 0,
- IncrementAndSignalIfEqual = 1,
- ModifyByWaitingCountAndSignalIfEqual = 2,
- };
-
- explicit AddressArbiter(Core::System& system);
- ~AddressArbiter();
-
- AddressArbiter(const AddressArbiter&) = delete;
- AddressArbiter& operator=(const AddressArbiter&) = delete;
-
- AddressArbiter(AddressArbiter&&) = default;
- AddressArbiter& operator=(AddressArbiter&&) = delete;
-
- /// Signals an address being waited on with a particular signaling type.
- ResultCode SignalToAddress(VAddr address, SignalType type, s32 value, s32 num_to_wake);
-
- /// Waits on an address with a particular arbitration type.
- ResultCode WaitForAddress(VAddr address, ArbitrationType type, s32 value, s64 timeout_ns);
-
- /// Removes a thread from the container and resets its address arbiter adress to 0
- void HandleWakeupThread(std::shared_ptr<Thread> thread);
-
-private:
- /// Signals an address being waited on.
- ResultCode SignalToAddressOnly(VAddr address, s32 num_to_wake);
-
- /// Signals an address being waited on and increments its value if equal to the value argument.
- ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
-
- /// Signals an address being waited on and modifies its value based on waiting thread count if
- /// equal to the value argument.
- ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
- s32 num_to_wake);
-
- /// Waits on an address if the value passed is less than the argument value,
- /// optionally decrementing.
- ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
- bool should_decrement);
-
- /// Waits on an address if the value passed is equal to the argument value.
- ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
-
- /// Wake up num_to_wake (or all) threads in a vector.
- void WakeThreads(const std::vector<std::shared_ptr<Thread>>& waiting_threads, s32 num_to_wake);
-
- /// Insert a thread into the address arbiter container
- void InsertThread(std::shared_ptr<Thread> thread);
-
- /// Removes a thread from the address arbiter container
- void RemoveThread(std::shared_ptr<Thread> thread);
-
- // Gets the threads waiting on an address.
- std::vector<std::shared_ptr<Thread>> GetThreadsWaitingOnAddress(VAddr address) const;
-
- /// List of threads waiting for a address arbiter
- std::unordered_map<VAddr, std::list<std::shared_ptr<Thread>>> arb_threads;
-
- Core::System& system;
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index 8aff2227a..0b6957e31 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -4,11 +4,11 @@
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/session.h"
+#include "core/hle/kernel/svc_results.h"
namespace Kernel {
@@ -21,7 +21,7 @@ std::shared_ptr<ServerPort> ClientPort::GetServerPort() const {
ResultVal<std::shared_ptr<ClientSession>> ClientPort::Connect() {
if (active_sessions >= max_sessions) {
- return ERR_MAX_CONNECTIONS_REACHED;
+ return ResultMaxConnectionsReached;
}
active_sessions++;
@@ -33,9 +33,6 @@ ResultVal<std::shared_ptr<ClientSession>> ClientPort::Connect() {
server_port->AppendPendingSession(std::move(server));
}
- // Wake the threads waiting on the ServerPort
- server_port->Signal();
-
return MakeResult(std::move(client));
}
diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h
index 9762bbf0d..77559ebf9 100644
--- a/src/core/hle/kernel/client_port.h
+++ b/src/core/hle/kernel/client_port.h
@@ -51,6 +51,8 @@ public:
*/
void ConnectionClosed();
+ void Finalize() override {}
+
private:
std::shared_ptr<ServerPort> server_port; ///< ServerPort associated with this client port.
u32 max_sessions = 0; ///< Maximum number of simultaneous sessions the port can have
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 5ab204b9b..e230f365a 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -3,16 +3,16 @@
// Refer to the license.txt file included.
#include "core/hle/kernel/client_session.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
-#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/svc_results.h"
#include "core/hle/result.h"
namespace Kernel {
-ClientSession::ClientSession(KernelCore& kernel) : SynchronizationObject{kernel} {}
+ClientSession::ClientSession(KernelCore& kernel) : KSynchronizationObject{kernel} {}
ClientSession::~ClientSession() {
// This destructor will be called automatically when the last ClientSession handle is closed by
@@ -22,15 +22,6 @@ ClientSession::~ClientSession() {
}
}
-bool ClientSession::ShouldWait(const Thread* thread) const {
- UNIMPLEMENTED();
- return {};
-}
-
-void ClientSession::Acquire(Thread* thread) {
- UNIMPLEMENTED();
-}
-
bool ClientSession::IsSignaled() const {
UNIMPLEMENTED();
return true;
@@ -47,15 +38,16 @@ ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kern
return MakeResult(std::move(client_session));
}
-ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread,
- Core::Memory::Memory& memory) {
+ResultCode ClientSession::SendSyncRequest(std::shared_ptr<KThread> thread,
+ Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing) {
// Keep ServerSession alive until we're done working with it.
if (!parent->Server()) {
- return ERR_SESSION_CLOSED_BY_REMOTE;
+ return ResultSessionClosedByRemote;
}
// Signal the server session that new data is available
- return parent->Server()->HandleSyncRequest(std::move(thread), memory);
+ return parent->Server()->HandleSyncRequest(std::move(thread), memory, core_timing);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index c5f760d7d..85aafeaf4 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -7,7 +7,7 @@
#include <memory>
#include <string>
-#include "core/hle/kernel/synchronization_object.h"
+#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/result.h"
union ResultCode;
@@ -16,13 +16,17 @@ namespace Core::Memory {
class Memory;
}
+namespace Core::Timing {
+class CoreTiming;
+}
+
namespace Kernel {
class KernelCore;
class Session;
-class Thread;
+class KThread;
-class ClientSession final : public SynchronizationObject {
+class ClientSession final : public KSynchronizationObject {
public:
explicit ClientSession(KernelCore& kernel);
~ClientSession() override;
@@ -42,14 +46,13 @@ public:
return HANDLE_TYPE;
}
- ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory);
-
- bool ShouldWait(const Thread* thread) const override;
-
- void Acquire(Thread* thread) override;
+ ResultCode SendSyncRequest(std::shared_ptr<KThread> thread, Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing);
bool IsSignaled() const override;
+ void Finalize() override {}
+
private:
static ResultVal<std::shared_ptr<ClientSession>> Create(KernelCore& kernel,
std::shared_ptr<Session> parent,
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
deleted file mode 100644
index d4e5d88cf..000000000
--- a/src/core/hle/kernel/errors.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "core/hle/result.h"
-
-namespace Kernel {
-
-// Confirmed Switch kernel error codes
-
-constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7};
-constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14};
-constexpr ResultCode ERR_THREAD_TERMINATING{ErrorModule::Kernel, 59};
-constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101};
-constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102};
-constexpr ResultCode ERR_OUT_OF_RESOURCES{ErrorModule::Kernel, 103};
-constexpr ResultCode ERR_OUT_OF_MEMORY{ErrorModule::Kernel, 104};
-constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105};
-constexpr ResultCode ERR_INVALID_ADDRESS_STATE{ErrorModule::Kernel, 106};
-constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS{ErrorModule::Kernel, 108};
-constexpr ResultCode ERR_INVALID_MEMORY_RANGE{ErrorModule::Kernel, 110};
-constexpr ResultCode ERR_INVALID_PROCESSOR_ID{ErrorModule::Kernel, 113};
-constexpr ResultCode ERR_INVALID_THREAD_PRIORITY{ErrorModule::Kernel, 112};
-constexpr ResultCode ERR_INVALID_HANDLE{ErrorModule::Kernel, 114};
-constexpr ResultCode ERR_INVALID_POINTER{ErrorModule::Kernel, 115};
-constexpr ResultCode ERR_INVALID_COMBINATION{ErrorModule::Kernel, 116};
-constexpr ResultCode RESULT_TIMEOUT{ErrorModule::Kernel, 117};
-constexpr ResultCode ERR_SYNCHRONIZATION_CANCELED{ErrorModule::Kernel, 118};
-constexpr ResultCode ERR_OUT_OF_RANGE{ErrorModule::Kernel, 119};
-constexpr ResultCode ERR_INVALID_ENUM_VALUE{ErrorModule::Kernel, 120};
-constexpr ResultCode ERR_NOT_FOUND{ErrorModule::Kernel, 121};
-constexpr ResultCode ERR_BUSY{ErrorModule::Kernel, 122};
-constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE{ErrorModule::Kernel, 123};
-constexpr ResultCode ERR_INVALID_STATE{ErrorModule::Kernel, 125};
-constexpr ResultCode ERR_RESERVED_VALUE{ErrorModule::Kernel, 126};
-constexpr ResultCode ERR_RESOURCE_LIMIT_EXCEEDED{ErrorModule::Kernel, 132};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp
new file mode 100644
index 000000000..c6838649f
--- /dev/null
+++ b/src/core/hle/kernel/global_scheduler_context.cpp
@@ -0,0 +1,52 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <mutex>
+
+#include "common/assert.h"
+#include "core/core.h"
+#include "core/hle/kernel/global_scheduler_context.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/kernel.h"
+
+namespace Kernel {
+
+GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel)
+ : kernel{kernel}, scheduler_lock{kernel} {}
+
+GlobalSchedulerContext::~GlobalSchedulerContext() = default;
+
+void GlobalSchedulerContext::AddThread(std::shared_ptr<KThread> thread) {
+ std::scoped_lock lock{global_list_guard};
+ thread_list.push_back(std::move(thread));
+}
+
+void GlobalSchedulerContext::RemoveThread(std::shared_ptr<KThread> thread) {
+ std::scoped_lock lock{global_list_guard};
+ thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
+ thread_list.end());
+}
+
+void GlobalSchedulerContext::PreemptThreads() {
+ // The priority levels at which the global scheduler preempts threads every 10 ms. They are
+ // ordered from Core 0 to Core 3.
+ static constexpr std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities{
+ 59,
+ 59,
+ 59,
+ 63,
+ };
+
+ ASSERT(IsLocked());
+ for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
+ const u32 priority = preemption_priorities[core_id];
+ kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority);
+ }
+}
+
+bool GlobalSchedulerContext::IsLocked() const {
+ return scheduler_lock.IsLockedByCurrentThread();
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h
new file mode 100644
index 000000000..11592843e
--- /dev/null
+++ b/src/core/hle/kernel/global_scheduler_context.h
@@ -0,0 +1,86 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/spin_lock.h"
+#include "core/hardware_properties.h"
+#include "core/hle/kernel/k_priority_queue.h"
+#include "core/hle/kernel/k_scheduler_lock.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/svc_types.h"
+
+namespace Kernel {
+
+class KernelCore;
+class SchedulerLock;
+
+using KSchedulerPriorityQueue =
+ KPriorityQueue<KThread, Core::Hardware::NUM_CPU_CORES, Svc::LowestThreadPriority,
+ Svc::HighestThreadPriority>;
+
+static constexpr s32 HighestCoreMigrationAllowedPriority = 2;
+static_assert(Svc::LowestThreadPriority >= HighestCoreMigrationAllowedPriority);
+static_assert(Svc::HighestThreadPriority <= HighestCoreMigrationAllowedPriority);
+
+class GlobalSchedulerContext final {
+ friend class KScheduler;
+
+public:
+ using LockType = KAbstractSchedulerLock<KScheduler>;
+
+ explicit GlobalSchedulerContext(KernelCore& kernel);
+ ~GlobalSchedulerContext();
+
+ /// Adds a new thread to the scheduler
+ void AddThread(std::shared_ptr<KThread> thread);
+
+ /// Removes a thread from the scheduler
+ void RemoveThread(std::shared_ptr<KThread> thread);
+
+ /// Returns a list of all threads managed by the scheduler
+ [[nodiscard]] const std::vector<std::shared_ptr<KThread>>& GetThreadList() const {
+ return thread_list;
+ }
+
+ /**
+ * Rotates the scheduling queues of threads at a preemption priority and then does
+ * some core rebalancing. Preemption priorities can be found in the array
+ * 'preemption_priorities'.
+ *
+ * @note This operation happens every 10ms.
+ */
+ void PreemptThreads();
+
+ /// Returns true if the global scheduler lock is acquired
+ bool IsLocked() const;
+
+ [[nodiscard]] LockType& SchedulerLock() {
+ return scheduler_lock;
+ }
+
+ [[nodiscard]] const LockType& SchedulerLock() const {
+ return scheduler_lock;
+ }
+
+private:
+ friend class KScopedSchedulerLock;
+ friend class KScopedSchedulerLockAndSleep;
+
+ KernelCore& kernel;
+
+ std::atomic_bool scheduler_update_needed{};
+ KSchedulerPriorityQueue priority_queue;
+ LockType scheduler_lock;
+
+ /// Lists all thread ids that aren't deleted/etc.
+ std::vector<std::shared_ptr<KThread>> thread_list;
+ Common::SpinLock global_list_guard{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp
index fb30b6f8b..f96d34078 100644
--- a/src/core/hle/kernel/handle_table.cpp
+++ b/src/core/hle/kernel/handle_table.cpp
@@ -6,12 +6,12 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/svc_results.h"
namespace Kernel {
namespace {
@@ -33,7 +33,7 @@ HandleTable::~HandleTable() = default;
ResultCode HandleTable::SetSize(s32 handle_table_size) {
if (static_cast<u32>(handle_table_size) > MAX_COUNT) {
LOG_ERROR(Kernel, "Handle table size {} is greater than {}", handle_table_size, MAX_COUNT);
- return ERR_OUT_OF_MEMORY;
+ return ResultOutOfMemory;
}
// Values less than or equal to zero indicate to use the maximum allowable
@@ -53,7 +53,7 @@ ResultVal<Handle> HandleTable::Create(std::shared_ptr<Object> obj) {
const u16 slot = next_free_slot;
if (slot >= table_size) {
LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use.");
- return ERR_HANDLE_TABLE_FULL;
+ return ResultHandleTableFull;
}
next_free_slot = generations[slot];
@@ -76,7 +76,7 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
std::shared_ptr<Object> object = GetGeneric(handle);
if (object == nullptr) {
LOG_ERROR(Kernel, "Tried to duplicate invalid handle: {:08X}", handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
return Create(std::move(object));
}
@@ -84,11 +84,15 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
ResultCode HandleTable::Close(Handle handle) {
if (!IsValid(handle)) {
LOG_ERROR(Kernel, "Handle is not valid! handle={:08X}", handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
const u16 slot = GetSlot(handle);
+ if (objects[slot].use_count() == 1) {
+ objects[slot]->Finalize();
+ }
+
objects[slot] = nullptr;
generations[slot] = next_free_slot;
@@ -105,7 +109,7 @@ bool HandleTable::IsValid(Handle handle) const {
std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
- return SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
+ return SharedFrom(kernel.CurrentScheduler()->GetCurrentThread());
} else if (handle == CurrentProcess) {
return SharedFrom(kernel.CurrentProcess());
}
@@ -118,7 +122,7 @@ std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
void HandleTable::Clear() {
for (u16 i = 0; i < table_size; ++i) {
- generations[i] = i + 1;
+ generations[i] = static_cast<u16>(i + 1);
objects[i] = nullptr;
}
next_free_slot = 0;
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 81f85643b..161d9f782 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -14,18 +14,19 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/server_session.h"
-#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/time_manager.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/memory.h"
namespace Kernel {
@@ -45,47 +46,9 @@ void SessionRequestHandler::ClientDisconnected(
boost::range::remove_erase(connected_sessions, server_session);
}
-std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
- const std::string& reason, u64 timeout, WakeupCallback&& callback,
- std::shared_ptr<WritableEvent> writable_event) {
- // Put the client thread to sleep until the wait event is signaled or the timeout expires.
-
- if (!writable_event) {
- // Create event if not provided
- const auto pair = WritableEvent::CreateEventPair(kernel, "HLE Pause Event: " + reason);
- writable_event = pair.writable;
- }
-
- {
- Handle event_handle = InvalidHandle;
- SchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout);
- thread->SetHLECallback(
- [context = *this, callback](std::shared_ptr<Thread> thread) mutable -> bool {
- ThreadWakeupReason reason = thread->GetSignalingResult() == RESULT_TIMEOUT
- ? ThreadWakeupReason::Timeout
- : ThreadWakeupReason::Signal;
- callback(thread, context, reason);
- context.WriteToOutgoingCommandBuffer(*thread);
- return true;
- });
- const auto readable_event{writable_event->GetReadableEvent()};
- writable_event->Clear();
- thread->SetHLESyncObject(readable_event.get());
- thread->SetStatus(ThreadStatus::WaitHLEEvent);
- thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
- readable_event->AddWaitingThread(thread);
- lock.Release();
- thread->SetHLETimeEvent(event_handle);
- }
-
- is_thread_waiting = true;
-
- return writable_event;
-}
-
HLERequestContext::HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory,
std::shared_ptr<ServerSession> server_session,
- std::shared_ptr<Thread> thread)
+ std::shared_ptr<KThread> thread)
: server_session(std::move(server_session)),
thread(std::move(thread)), kernel{kernel}, memory{memory} {
cmd_buf[0] = 0;
@@ -219,7 +182,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const HandleTabl
return RESULT_SUCCESS;
}
-ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
+ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(KThread& thread) {
auto& owner_process = *thread.GetOwnerProcess();
auto& handle_table = owner_process.GetHandleTable();
@@ -375,6 +338,28 @@ std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) cons
return 0;
}
+bool HLERequestContext::CanReadBuffer(std::size_t buffer_index) const {
+ const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
+ BufferDescriptorA()[buffer_index].Size()};
+
+ if (is_buffer_a) {
+ return BufferDescriptorA().size() > buffer_index;
+ } else {
+ return BufferDescriptorX().size() > buffer_index;
+ }
+}
+
+bool HLERequestContext::CanWriteBuffer(std::size_t buffer_index) const {
+ const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
+ BufferDescriptorB()[buffer_index].Size()};
+
+ if (is_buffer_b) {
+ return BufferDescriptorB().size() > buffer_index;
+ } else {
+ return BufferDescriptorC().size() > buffer_index;
+ }
+}
+
std::string HLERequestContext::Description() const {
if (!command_header) {
return "No command header available";
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index f3277b766..9a769781b 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -24,6 +24,10 @@ namespace Core::Memory {
class Memory;
}
+namespace IPC {
+class ResponseBuilder;
+}
+
namespace Service {
class ServiceFrameworkBase;
}
@@ -36,9 +40,9 @@ class HLERequestContext;
class KernelCore;
class Process;
class ServerSession;
-class Thread;
-class ReadableEvent;
-class WritableEvent;
+class KThread;
+class KReadableEvent;
+class KWritableEvent;
enum class ThreadWakeupReason;
@@ -106,7 +110,7 @@ class HLERequestContext {
public:
explicit HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory,
std::shared_ptr<ServerSession> session,
- std::shared_ptr<Thread> thread);
+ std::shared_ptr<KThread> thread);
~HLERequestContext();
/// Returns a pointer to the IPC command buffer for this request.
@@ -122,32 +126,12 @@ public:
return server_session;
}
- using WakeupCallback = std::function<void(
- std::shared_ptr<Thread> thread, HLERequestContext& context, ThreadWakeupReason reason)>;
-
- /**
- * Puts the specified guest thread to sleep until the returned event is signaled or until the
- * specified timeout expires.
- * @param reason Reason for pausing the thread, to be used for debugging purposes.
- * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback
- * invoked with a Timeout reason.
- * @param callback Callback to be invoked when the thread is resumed. This callback must write
- * the entire command response once again, regardless of the state of it before this function
- * was called.
- * @param writable_event Event to use to wake up the thread. If unspecified, an event will be
- * created.
- * @returns Event that when signaled will resume the thread and call the callback function.
- */
- std::shared_ptr<WritableEvent> SleepClientThread(
- const std::string& reason, u64 timeout, WakeupCallback&& callback,
- std::shared_ptr<WritableEvent> writable_event = nullptr);
-
/// Populates this context with data from the requesting process/thread.
ResultCode PopulateFromIncomingCommandBuffer(const HandleTable& handle_table,
u32_le* src_cmdbuf);
/// Writes data from this context back to the requesting process/thread.
- ResultCode WriteToOutgoingCommandBuffer(Thread& thread);
+ ResultCode WriteToOutgoingCommandBuffer(KThread& thread);
u32_le GetCommand() const {
return command;
@@ -220,6 +204,12 @@ public:
/// Helper function to get the size of the output buffer
std::size_t GetWriteBufferSize(std::size_t buffer_index = 0) const;
+ /// Helper function to test whether the input buffer at buffer_index can be read
+ bool CanReadBuffer(std::size_t buffer_index = 0) const;
+
+ /// Helper function to test whether the output buffer at buffer_index can be written
+ bool CanWriteBuffer(std::size_t buffer_index = 0) const;
+
template <typename T>
std::shared_ptr<T> GetCopyObject(std::size_t index) {
return DynamicObjectCast<T>(copy_objects.at(index));
@@ -274,11 +264,11 @@ public:
std::string Description() const;
- Thread& GetThread() {
+ KThread& GetThread() {
return *thread;
}
- const Thread& GetThread() const {
+ const KThread& GetThread() const {
return *thread;
}
@@ -287,11 +277,13 @@ public:
}
private:
+ friend class IPC::ResponseBuilder;
+
void ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, bool incoming);
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
std::shared_ptr<Kernel::ServerSession> server_session;
- std::shared_ptr<Thread> thread;
+ std::shared_ptr<KThread> thread;
// TODO(yuriks): Check common usage of this and optimize size accordingly
boost::container::small_vector<std::shared_ptr<Object>, 8> move_objects;
boost::container::small_vector<std::shared_ptr<Object>, 8> copy_objects;
diff --git a/src/core/hle/kernel/k_address_arbiter.cpp b/src/core/hle/kernel/k_address_arbiter.cpp
new file mode 100644
index 000000000..7018f56da
--- /dev/null
+++ b/src/core/hle/kernel/k_address_arbiter.cpp
@@ -0,0 +1,341 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/arm/exclusive_monitor.h"
+#include "core/core.h"
+#include "core/hle/kernel/k_address_arbiter.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/svc_results.h"
+#include "core/hle/kernel/time_manager.h"
+#include "core/memory.h"
+
+namespace Kernel {
+
+KAddressArbiter::KAddressArbiter(Core::System& system_)
+ : system{system_}, kernel{system.Kernel()} {}
+KAddressArbiter::~KAddressArbiter() = default;
+
+namespace {
+
+bool ReadFromUser(Core::System& system, s32* out, VAddr address) {
+ *out = system.Memory().Read32(address);
+ return true;
+}
+
+bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 value) {
+ auto& monitor = system.Monitor();
+ const auto current_core = system.CurrentCoreIndex();
+
+ // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
+ // TODO(bunnei): We should call CanAccessAtomic(..) here.
+
+ // Load the value from the address.
+ const s32 current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address));
+
+ // Compare it to the desired one.
+ if (current_value < value) {
+ // If less than, we want to try to decrement.
+ const s32 decrement_value = current_value - 1;
+
+ // Decrement and try to store.
+ if (!monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(decrement_value))) {
+ // If we failed to store, try again.
+ DecrementIfLessThan(system, out, address, value);
+ }
+ } else {
+ // Otherwise, clear our exclusive hold and finish
+ monitor.ClearExclusive();
+ }
+
+ // We're done.
+ *out = current_value;
+ return true;
+}
+
+bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32 new_value) {
+ auto& monitor = system.Monitor();
+ const auto current_core = system.CurrentCoreIndex();
+
+ // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
+ // TODO(bunnei): We should call CanAccessAtomic(..) here.
+
+ // Load the value from the address.
+ const s32 current_value = static_cast<s32>(monitor.ExclusiveRead32(current_core, address));
+
+ // Compare it to the desired one.
+ if (current_value == value) {
+ // If equal, we want to try to write the new value.
+
+ // Try to store.
+ if (!monitor.ExclusiveWrite32(current_core, address, static_cast<u32>(new_value))) {
+ // If we failed to store, try again.
+ UpdateIfEqual(system, out, address, value, new_value);
+ }
+ } else {
+ // Otherwise, clear our exclusive hold and finish.
+ monitor.ClearExclusive();
+ }
+
+ // We're done.
+ *out = current_value;
+ return true;
+}
+
+} // namespace
+
+ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) {
+ // Perform signaling.
+ s32 num_waiters{};
+ {
+ KScopedSchedulerLock sl(kernel);
+
+ auto it = thread_tree.nfind_light({addr, -1});
+ while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
+ (it->GetAddressArbiterKey() == addr)) {
+ KThread* target_thread = std::addressof(*it);
+ target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
+
+ ASSERT(target_thread->IsWaitingForAddressArbiter());
+ target_thread->Wakeup();
+
+ it = thread_tree.erase(it);
+ target_thread->ClearAddressArbiter();
+ ++num_waiters;
+ }
+ }
+ return RESULT_SUCCESS;
+}
+
+ResultCode KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count) {
+ // Perform signaling.
+ s32 num_waiters{};
+ {
+ KScopedSchedulerLock sl(kernel);
+
+ // Check the userspace value.
+ s32 user_value{};
+ if (!UpdateIfEqual(system, &user_value, addr, value, value + 1)) {
+ LOG_ERROR(Kernel, "Invalid current memory!");
+ return ResultInvalidCurrentMemory;
+ }
+ if (user_value != value) {
+ return ResultInvalidState;
+ }
+
+ auto it = thread_tree.nfind_light({addr, -1});
+ while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
+ (it->GetAddressArbiterKey() == addr)) {
+ KThread* target_thread = std::addressof(*it);
+ target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
+
+ ASSERT(target_thread->IsWaitingForAddressArbiter());
+ target_thread->Wakeup();
+
+ it = thread_tree.erase(it);
+ target_thread->ClearAddressArbiter();
+ ++num_waiters;
+ }
+ }
+ return RESULT_SUCCESS;
+}
+
+ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count) {
+ // Perform signaling.
+ s32 num_waiters{};
+ {
+ [[maybe_unused]] const KScopedSchedulerLock sl(kernel);
+
+ auto it = thread_tree.nfind_light({addr, -1});
+ // Determine the updated value.
+ s32 new_value{};
+ if (count <= 0) {
+ if (it != thread_tree.end() && it->GetAddressArbiterKey() == addr) {
+ new_value = value - 2;
+ } else {
+ new_value = value + 1;
+ }
+ } else {
+ if (it != thread_tree.end() && it->GetAddressArbiterKey() == addr) {
+ auto tmp_it = it;
+ s32 tmp_num_waiters{};
+ while (++tmp_it != thread_tree.end() && tmp_it->GetAddressArbiterKey() == addr) {
+ if (tmp_num_waiters++ >= count) {
+ break;
+ }
+ }
+
+ if (tmp_num_waiters < count) {
+ new_value = value - 1;
+ } else {
+ new_value = value;
+ }
+ } else {
+ new_value = value + 1;
+ }
+ }
+
+ // Check the userspace value.
+ s32 user_value{};
+ bool succeeded{};
+ if (value != new_value) {
+ succeeded = UpdateIfEqual(system, &user_value, addr, value, new_value);
+ } else {
+ succeeded = ReadFromUser(system, &user_value, addr);
+ }
+
+ if (!succeeded) {
+ LOG_ERROR(Kernel, "Invalid current memory!");
+ return ResultInvalidCurrentMemory;
+ }
+ if (user_value != value) {
+ return ResultInvalidState;
+ }
+
+ while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
+ (it->GetAddressArbiterKey() == addr)) {
+ KThread* target_thread = std::addressof(*it);
+ target_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
+
+ ASSERT(target_thread->IsWaitingForAddressArbiter());
+ target_thread->Wakeup();
+
+ it = thread_tree.erase(it);
+ target_thread->ClearAddressArbiter();
+ ++num_waiters;
+ }
+ }
+ return RESULT_SUCCESS;
+}
+
+ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout) {
+ // Prepare to wait.
+ KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+
+ {
+ KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
+
+ // Check that the thread isn't terminating.
+ if (cur_thread->IsTerminationRequested()) {
+ slp.CancelSleep();
+ return ResultTerminationRequested;
+ }
+
+ // Set the synced object.
+ cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
+
+ // Read the value from userspace.
+ s32 user_value{};
+ bool succeeded{};
+ if (decrement) {
+ succeeded = DecrementIfLessThan(system, &user_value, addr, value);
+ } else {
+ succeeded = ReadFromUser(system, &user_value, addr);
+ }
+
+ if (!succeeded) {
+ slp.CancelSleep();
+ return ResultInvalidCurrentMemory;
+ }
+
+ // Check that the value is less than the specified one.
+ if (user_value >= value) {
+ slp.CancelSleep();
+ return ResultInvalidState;
+ }
+
+ // Check that the timeout is non-zero.
+ if (timeout == 0) {
+ slp.CancelSleep();
+ return ResultTimedOut;
+ }
+
+ // Set the arbiter.
+ cur_thread->SetAddressArbiter(&thread_tree, addr);
+ thread_tree.insert(*cur_thread);
+ cur_thread->SetState(ThreadState::Waiting);
+ cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
+ }
+
+ // Cancel the timer wait.
+ kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
+
+ // Remove from the address arbiter.
+ {
+ KScopedSchedulerLock sl(kernel);
+
+ if (cur_thread->IsWaitingForAddressArbiter()) {
+ thread_tree.erase(thread_tree.iterator_to(*cur_thread));
+ cur_thread->ClearAddressArbiter();
+ }
+ }
+
+ // Get the result.
+ KSynchronizationObject* dummy{};
+ return cur_thread->GetWaitResult(&dummy);
+}
+
+ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
+ // Prepare to wait.
+ KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+
+ {
+ KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
+
+ // Check that the thread isn't terminating.
+ if (cur_thread->IsTerminationRequested()) {
+ slp.CancelSleep();
+ return ResultTerminationRequested;
+ }
+
+ // Set the synced object.
+ cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
+
+ // Read the value from userspace.
+ s32 user_value{};
+ if (!ReadFromUser(system, &user_value, addr)) {
+ slp.CancelSleep();
+ return ResultInvalidCurrentMemory;
+ }
+
+ // Check that the value is equal.
+ if (value != user_value) {
+ slp.CancelSleep();
+ return ResultInvalidState;
+ }
+
+ // Check that the timeout is non-zero.
+ if (timeout == 0) {
+ slp.CancelSleep();
+ return ResultTimedOut;
+ }
+
+ // Set the arbiter.
+ cur_thread->SetAddressArbiter(&thread_tree, addr);
+ thread_tree.insert(*cur_thread);
+ cur_thread->SetState(ThreadState::Waiting);
+ cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
+ }
+
+ // Cancel the timer wait.
+ kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
+
+ // Remove from the address arbiter.
+ {
+ KScopedSchedulerLock sl(kernel);
+
+ if (cur_thread->IsWaitingForAddressArbiter()) {
+ thread_tree.erase(thread_tree.iterator_to(*cur_thread));
+ cur_thread->ClearAddressArbiter();
+ }
+ }
+
+ // Get the result.
+ KSynchronizationObject* dummy{};
+ return cur_thread->GetWaitResult(&dummy);
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_address_arbiter.h b/src/core/hle/kernel/k_address_arbiter.h
new file mode 100644
index 000000000..8d379b524
--- /dev/null
+++ b/src/core/hle/kernel/k_address_arbiter.h
@@ -0,0 +1,70 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/k_condition_variable.h"
+#include "core/hle/kernel/svc_types.h"
+
+union ResultCode;
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+
+class KernelCore;
+
+class KAddressArbiter {
+public:
+ using ThreadTree = KConditionVariable::ThreadTree;
+
+ explicit KAddressArbiter(Core::System& system_);
+ ~KAddressArbiter();
+
+ [[nodiscard]] ResultCode SignalToAddress(VAddr addr, Svc::SignalType type, s32 value,
+ s32 count) {
+ switch (type) {
+ case Svc::SignalType::Signal:
+ return Signal(addr, count);
+ case Svc::SignalType::SignalAndIncrementIfEqual:
+ return SignalAndIncrementIfEqual(addr, value, count);
+ case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual:
+ return SignalAndModifyByWaitingCountIfEqual(addr, value, count);
+ }
+ UNREACHABLE();
+ return RESULT_UNKNOWN;
+ }
+
+ [[nodiscard]] ResultCode WaitForAddress(VAddr addr, Svc::ArbitrationType type, s32 value,
+ s64 timeout) {
+ switch (type) {
+ case Svc::ArbitrationType::WaitIfLessThan:
+ return WaitIfLessThan(addr, value, false, timeout);
+ case Svc::ArbitrationType::DecrementAndWaitIfLessThan:
+ return WaitIfLessThan(addr, value, true, timeout);
+ case Svc::ArbitrationType::WaitIfEqual:
+ return WaitIfEqual(addr, value, timeout);
+ }
+ UNREACHABLE();
+ return RESULT_UNKNOWN;
+ }
+
+private:
+ [[nodiscard]] ResultCode Signal(VAddr addr, s32 count);
+ [[nodiscard]] ResultCode SignalAndIncrementIfEqual(VAddr addr, s32 value, s32 count);
+ [[nodiscard]] ResultCode SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32 value, s32 count);
+ [[nodiscard]] ResultCode WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout);
+ [[nodiscard]] ResultCode WaitIfEqual(VAddr addr, s32 value, s64 timeout);
+
+ ThreadTree thread_tree;
+
+ Core::System& system;
+ KernelCore& kernel;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_affinity_mask.h b/src/core/hle/kernel/k_affinity_mask.h
new file mode 100644
index 000000000..b906895fc
--- /dev/null
+++ b/src/core/hle/kernel/k_affinity_mask.h
@@ -0,0 +1,58 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hardware_properties.h"
+
+namespace Kernel {
+
+class KAffinityMask {
+public:
+ constexpr KAffinityMask() = default;
+
+ [[nodiscard]] constexpr u64 GetAffinityMask() const {
+ return this->mask;
+ }
+
+ constexpr void SetAffinityMask(u64 new_mask) {
+ ASSERT((new_mask & ~AllowedAffinityMask) == 0);
+ this->mask = new_mask;
+ }
+
+ [[nodiscard]] constexpr bool GetAffinity(s32 core) const {
+ return (this->mask & GetCoreBit(core)) != 0;
+ }
+
+ constexpr void SetAffinity(s32 core, bool set) {
+ ASSERT(0 <= core && core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+
+ if (set) {
+ this->mask |= GetCoreBit(core);
+ } else {
+ this->mask &= ~GetCoreBit(core);
+ }
+ }
+
+ constexpr void SetAll() {
+ this->mask = AllowedAffinityMask;
+ }
+
+private:
+ [[nodiscard]] static constexpr u64 GetCoreBit(s32 core) {
+ ASSERT(0 <= core && core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+ return (1ULL << core);
+ }
+
+ static constexpr u64 AllowedAffinityMask = (1ULL << Core::Hardware::NUM_CPU_CORES) - 1;
+
+ u64 mask{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp
new file mode 100644
index 000000000..170d8fa0d
--- /dev/null
+++ b/src/core/hle/kernel/k_condition_variable.cpp
@@ -0,0 +1,345 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <vector>
+
+#include "core/arm/exclusive_monitor.h"
+#include "core/core.h"
+#include "core/hle/kernel/k_condition_variable.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/svc_common.h"
+#include "core/hle/kernel/svc_results.h"
+#include "core/memory.h"
+
+namespace Kernel {
+
+namespace {
+
+bool ReadFromUser(Core::System& system, u32* out, VAddr address) {
+ *out = system.Memory().Read32(address);
+ return true;
+}
+
+bool WriteToUser(Core::System& system, VAddr address, const u32* p) {
+ system.Memory().Write32(address, *p);
+ return true;
+}
+
+bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero,
+ u32 new_orr_mask) {
+ auto& monitor = system.Monitor();
+ const auto current_core = system.CurrentCoreIndex();
+
+ // Load the value from the address.
+ const auto expected = monitor.ExclusiveRead32(current_core, address);
+
+ // Orr in the new mask.
+ u32 value = expected | new_orr_mask;
+
+ // If the value is zero, use the if_zero value, otherwise use the newly orr'd value.
+ if (!expected) {
+ value = if_zero;
+ }
+
+ // Try to store.
+ if (!monitor.ExclusiveWrite32(current_core, address, value)) {
+ // If we failed to store, try again.
+ return UpdateLockAtomic(system, out, address, if_zero, new_orr_mask);
+ }
+
+ // We're done.
+ *out = expected;
+ return true;
+}
+
+} // namespace
+
+KConditionVariable::KConditionVariable(Core::System& system_)
+ : system{system_}, kernel{system.Kernel()} {}
+
+KConditionVariable::~KConditionVariable() = default;
+
+ResultCode KConditionVariable::SignalToAddress(VAddr addr) {
+ KThread* owner_thread = kernel.CurrentScheduler()->GetCurrentThread();
+
+ // Signal the address.
+ {
+ KScopedSchedulerLock sl(kernel);
+
+ // Remove waiter thread.
+ s32 num_waiters{};
+ KThread* next_owner_thread =
+ owner_thread->RemoveWaiterByKey(std::addressof(num_waiters), addr);
+
+ // Determine the next tag.
+ u32 next_value{};
+ if (next_owner_thread) {
+ next_value = next_owner_thread->GetAddressKeyValue();
+ if (num_waiters > 1) {
+ next_value |= Svc::HandleWaitMask;
+ }
+
+ next_owner_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
+ next_owner_thread->Wakeup();
+ }
+
+ // Write the value to userspace.
+ if (!WriteToUser(system, addr, std::addressof(next_value))) {
+ if (next_owner_thread) {
+ next_owner_thread->SetSyncedObject(nullptr, ResultInvalidCurrentMemory);
+ }
+
+ return ResultInvalidCurrentMemory;
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) {
+ KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+
+ // Wait for the address.
+ {
+ std::shared_ptr<KThread> owner_thread;
+ ASSERT(!owner_thread);
+ {
+ KScopedSchedulerLock sl(kernel);
+ cur_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
+
+ // Check if the thread should terminate.
+ R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);
+
+ {
+ // Read the tag from userspace.
+ u32 test_tag{};
+ R_UNLESS(ReadFromUser(system, std::addressof(test_tag), addr),
+ ResultInvalidCurrentMemory);
+
+ // If the tag isn't the handle (with wait mask), we're done.
+ R_UNLESS(test_tag == (handle | Svc::HandleWaitMask), RESULT_SUCCESS);
+
+ // Get the lock owner thread.
+ owner_thread = kernel.CurrentProcess()->GetHandleTable().Get<KThread>(handle);
+ R_UNLESS(owner_thread, ResultInvalidHandle);
+
+ // Update the lock.
+ cur_thread->SetAddressKey(addr, value);
+ owner_thread->AddWaiter(cur_thread);
+ cur_thread->SetState(ThreadState::Waiting);
+ cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
+ cur_thread->SetMutexWaitAddressForDebugging(addr);
+ }
+ }
+ ASSERT(owner_thread);
+ }
+
+ // Remove the thread as a waiter from the lock owner.
+ {
+ KScopedSchedulerLock sl(kernel);
+ KThread* owner_thread = cur_thread->GetLockOwner();
+ if (owner_thread != nullptr) {
+ owner_thread->RemoveWaiter(cur_thread);
+ }
+ }
+
+ // Get the wait result.
+ KSynchronizationObject* dummy{};
+ return cur_thread->GetWaitResult(std::addressof(dummy));
+}
+
+KThread* KConditionVariable::SignalImpl(KThread* thread) {
+ // Check pre-conditions.
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Update the tag.
+ VAddr address = thread->GetAddressKey();
+ u32 own_tag = thread->GetAddressKeyValue();
+
+ u32 prev_tag{};
+ bool can_access{};
+ {
+ // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
+ // TODO(bunnei): We should call CanAccessAtomic(..) here.
+ can_access = true;
+ if (can_access) {
+ UpdateLockAtomic(system, std::addressof(prev_tag), address, own_tag,
+ Svc::HandleWaitMask);
+ }
+ }
+
+ KThread* thread_to_close = nullptr;
+ if (can_access) {
+ if (prev_tag == InvalidHandle) {
+ // If nobody held the lock previously, we're all good.
+ thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
+ thread->Wakeup();
+ } else {
+ // Get the previous owner.
+ auto owner_thread = kernel.CurrentProcess()->GetHandleTable().Get<KThread>(
+ prev_tag & ~Svc::HandleWaitMask);
+
+ if (owner_thread) {
+ // Add the thread as a waiter on the owner.
+ owner_thread->AddWaiter(thread);
+ thread_to_close = owner_thread.get();
+ } else {
+ // The lock was tagged with a thread that doesn't exist.
+ thread->SetSyncedObject(nullptr, ResultInvalidState);
+ thread->Wakeup();
+ }
+ }
+ } else {
+ // If the address wasn't accessible, note so.
+ thread->SetSyncedObject(nullptr, ResultInvalidCurrentMemory);
+ thread->Wakeup();
+ }
+
+ return thread_to_close;
+}
+
+void KConditionVariable::Signal(u64 cv_key, s32 count) {
+ // Prepare for signaling.
+ constexpr int MaxThreads = 16;
+
+ // TODO(bunnei): This should just be Thread once we implement KAutoObject instead of using
+ // std::shared_ptr.
+ std::vector<std::shared_ptr<KThread>> thread_list;
+ std::array<KThread*, MaxThreads> thread_array;
+ s32 num_to_close{};
+
+ // Perform signaling.
+ s32 num_waiters{};
+ {
+ KScopedSchedulerLock sl(kernel);
+
+ auto it = thread_tree.nfind_light({cv_key, -1});
+ while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
+ (it->GetConditionVariableKey() == cv_key)) {
+ KThread* target_thread = std::addressof(*it);
+
+ if (KThread* thread = SignalImpl(target_thread); thread != nullptr) {
+ if (num_to_close < MaxThreads) {
+ thread_array[num_to_close++] = thread;
+ } else {
+ thread_list.push_back(SharedFrom(thread));
+ }
+ }
+
+ it = thread_tree.erase(it);
+ target_thread->ClearConditionVariable();
+ ++num_waiters;
+ }
+
+ // If we have no waiters, clear the has waiter flag.
+ if (it == thread_tree.end() || it->GetConditionVariableKey() != cv_key) {
+ const u32 has_waiter_flag{};
+ WriteToUser(system, cv_key, std::addressof(has_waiter_flag));
+ }
+ }
+
+ // Close threads in the array.
+ for (auto i = 0; i < num_to_close; ++i) {
+ thread_array[i]->Close();
+ }
+
+ // Close threads in the list.
+ for (auto it = thread_list.begin(); it != thread_list.end(); it = thread_list.erase(it)) {
+ (*it)->Close();
+ }
+}
+
+ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout) {
+ // Prepare to wait.
+ KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+
+ {
+ KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
+
+ // Set the synced object.
+ cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
+
+ // Check that the thread isn't terminating.
+ if (cur_thread->IsTerminationRequested()) {
+ slp.CancelSleep();
+ return ResultTerminationRequested;
+ }
+
+ // Update the value and process for the next owner.
+ {
+ // Remove waiter thread.
+ s32 num_waiters{};
+ KThread* next_owner_thread =
+ cur_thread->RemoveWaiterByKey(std::addressof(num_waiters), addr);
+
+ // Update for the next owner thread.
+ u32 next_value{};
+ if (next_owner_thread != nullptr) {
+ // Get the next tag value.
+ next_value = next_owner_thread->GetAddressKeyValue();
+ if (num_waiters > 1) {
+ next_value |= Svc::HandleWaitMask;
+ }
+
+ // Wake up the next owner.
+ next_owner_thread->SetSyncedObject(nullptr, RESULT_SUCCESS);
+ next_owner_thread->Wakeup();
+ }
+
+ // Write to the cv key.
+ {
+ const u32 has_waiter_flag = 1;
+ WriteToUser(system, key, std::addressof(has_waiter_flag));
+ // TODO(bunnei): We should call DataMemoryBarrier(..) here.
+ }
+
+ // Write the value to userspace.
+ if (!WriteToUser(system, addr, std::addressof(next_value))) {
+ slp.CancelSleep();
+ return ResultInvalidCurrentMemory;
+ }
+ }
+
+ // Update condition variable tracking.
+ {
+ cur_thread->SetConditionVariable(std::addressof(thread_tree), addr, key, value);
+ thread_tree.insert(*cur_thread);
+ }
+
+ // If the timeout is non-zero, set the thread as waiting.
+ if (timeout != 0) {
+ cur_thread->SetState(ThreadState::Waiting);
+ cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
+ cur_thread->SetMutexWaitAddressForDebugging(addr);
+ }
+ }
+
+ // Cancel the timer wait.
+ kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
+
+ // Remove from the condition variable.
+ {
+ KScopedSchedulerLock sl(kernel);
+
+ if (KThread* owner = cur_thread->GetLockOwner(); owner != nullptr) {
+ owner->RemoveWaiter(cur_thread);
+ }
+
+ if (cur_thread->IsWaitingForConditionVariable()) {
+ thread_tree.erase(thread_tree.iterator_to(*cur_thread));
+ cur_thread->ClearConditionVariable();
+ }
+ }
+
+ // Get the result.
+ KSynchronizationObject* dummy{};
+ return cur_thread->GetWaitResult(std::addressof(dummy));
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_condition_variable.h b/src/core/hle/kernel/k_condition_variable.h
new file mode 100644
index 000000000..861dbd420
--- /dev/null
+++ b/src/core/hle/kernel/k_condition_variable.h
@@ -0,0 +1,59 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/common_types.h"
+
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/result.h"
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+
+class KConditionVariable {
+public:
+ using ThreadTree = typename KThread::ConditionVariableThreadTreeType;
+
+ explicit KConditionVariable(Core::System& system_);
+ ~KConditionVariable();
+
+ // Arbitration
+ [[nodiscard]] ResultCode SignalToAddress(VAddr addr);
+ [[nodiscard]] ResultCode WaitForAddress(Handle handle, VAddr addr, u32 value);
+
+ // Condition variable
+ void Signal(u64 cv_key, s32 count);
+ [[nodiscard]] ResultCode Wait(VAddr addr, u64 key, u32 value, s64 timeout);
+
+private:
+ [[nodiscard]] KThread* SignalImpl(KThread* thread);
+
+ ThreadTree thread_tree;
+
+ Core::System& system;
+ KernelCore& kernel;
+};
+
+inline void BeforeUpdatePriority(const KernelCore& kernel, KConditionVariable::ThreadTree* tree,
+ KThread* thread) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ tree->erase(tree->iterator_to(*thread));
+}
+
+inline void AfterUpdatePriority(const KernelCore& kernel, KConditionVariable::ThreadTree* tree,
+ KThread* thread) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ tree->insert(*thread);
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_event.cpp b/src/core/hle/kernel/k_event.cpp
new file mode 100644
index 000000000..bb2fa4ad5
--- /dev/null
+++ b/src/core/hle/kernel/k_event.cpp
@@ -0,0 +1,32 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
+
+namespace Kernel {
+
+KEvent::KEvent(KernelCore& kernel, std::string&& name) : Object{kernel, std::move(name)} {}
+
+KEvent::~KEvent() = default;
+
+std::shared_ptr<KEvent> KEvent::Create(KernelCore& kernel, std::string&& name) {
+ return std::make_shared<KEvent>(kernel, std::move(name));
+}
+
+void KEvent::Initialize() {
+ // Create our sub events.
+ readable_event = std::make_shared<KReadableEvent>(kernel, GetName() + ":Readable");
+ writable_event = std::make_shared<KWritableEvent>(kernel, GetName() + ":Writable");
+
+ // Initialize our sub sessions.
+ readable_event->Initialize(this);
+ writable_event->Initialize(this);
+
+ // Mark initialized.
+ initialized = true;
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_event.h b/src/core/hle/kernel/k_event.h
new file mode 100644
index 000000000..2fb887129
--- /dev/null
+++ b/src/core/hle/kernel/k_event.h
@@ -0,0 +1,57 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/object.h"
+
+namespace Kernel {
+
+class KernelCore;
+class KReadableEvent;
+class KWritableEvent;
+
+class KEvent final : public Object {
+public:
+ explicit KEvent(KernelCore& kernel, std::string&& name);
+ ~KEvent() override;
+
+ static std::shared_ptr<KEvent> Create(KernelCore& kernel, std::string&& name);
+
+ void Initialize();
+
+ void Finalize() override {}
+
+ std::string GetTypeName() const override {
+ return "KEvent";
+ }
+
+ static constexpr HandleType HANDLE_TYPE = HandleType::Event;
+ HandleType GetHandleType() const override {
+ return HANDLE_TYPE;
+ }
+
+ std::shared_ptr<KReadableEvent>& GetReadableEvent() {
+ return readable_event;
+ }
+
+ std::shared_ptr<KWritableEvent>& GetWritableEvent() {
+ return writable_event;
+ }
+
+ const std::shared_ptr<KReadableEvent>& GetReadableEvent() const {
+ return readable_event;
+ }
+
+ const std::shared_ptr<KWritableEvent>& GetWritableEvent() const {
+ return writable_event;
+ }
+
+private:
+ std::shared_ptr<KReadableEvent> readable_event;
+ std::shared_ptr<KWritableEvent> writable_event;
+ bool initialized{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_light_condition_variable.h b/src/core/hle/kernel/k_light_condition_variable.h
new file mode 100644
index 000000000..362d0db28
--- /dev/null
+++ b/src/core/hle/kernel/k_light_condition_variable.h
@@ -0,0 +1,57 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_thread_queue.h"
+#include "core/hle/kernel/time_manager.h"
+
+namespace Kernel {
+class KernelCore;
+
+class KLightConditionVariable {
+public:
+ explicit KLightConditionVariable(KernelCore& kernel) : thread_queue(kernel), kernel(kernel) {}
+
+ void Wait(KLightLock* lock, s64 timeout = -1) {
+ WaitImpl(lock, timeout);
+ lock->Lock();
+ }
+
+ void Broadcast() {
+ KScopedSchedulerLock lk{kernel};
+ while (thread_queue.WakeupFrontThread() != nullptr) {
+ // We want to signal all threads, and so should continue waking up until there's nothing
+ // to wake.
+ }
+ }
+
+private:
+ void WaitImpl(KLightLock* lock, s64 timeout) {
+ KThread* owner = GetCurrentThreadPointer(kernel);
+
+ // Sleep the thread.
+ {
+ KScopedSchedulerLockAndSleep lk(kernel, owner, timeout);
+ lock->Unlock();
+
+ if (!thread_queue.SleepThread(owner)) {
+ lk.CancelSleep();
+ return;
+ }
+ }
+
+ // Cancel the task that the sleep setup.
+ kernel.TimeManager().UnscheduleTimeEvent(owner);
+ }
+ KThreadQueue thread_queue;
+ KernelCore& kernel;
+};
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_light_lock.cpp b/src/core/hle/kernel/k_light_lock.cpp
new file mode 100644
index 000000000..f974022e8
--- /dev/null
+++ b/src/core/hle/kernel/k_light_lock.cpp
@@ -0,0 +1,130 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/k_light_lock.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+
+namespace Kernel {
+
+void KLightLock::Lock() {
+ const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel));
+ const uintptr_t cur_thread_tag = (cur_thread | 1);
+
+ while (true) {
+ uintptr_t old_tag = tag.load(std::memory_order_relaxed);
+
+ while (!tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : old_tag | 1,
+ std::memory_order_acquire)) {
+ if ((old_tag | 1) == cur_thread_tag) {
+ return;
+ }
+ }
+
+ if ((old_tag == 0) || ((old_tag | 1) == cur_thread_tag)) {
+ break;
+ }
+
+ LockSlowPath(old_tag | 1, cur_thread);
+ }
+}
+
+void KLightLock::Unlock() {
+ const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel));
+ uintptr_t expected = cur_thread;
+ do {
+ if (expected != cur_thread) {
+ return UnlockSlowPath(cur_thread);
+ }
+ } while (!tag.compare_exchange_weak(expected, 0, std::memory_order_release));
+}
+
+void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
+ KThread* cur_thread = reinterpret_cast<KThread*>(_cur_thread);
+
+ // Pend the current thread waiting on the owner thread.
+ {
+ KScopedSchedulerLock sl{kernel};
+
+ // Ensure we actually have locking to do.
+ if (tag.load(std::memory_order_relaxed) != _owner) {
+ return;
+ }
+
+ // Add the current thread as a waiter on the owner.
+ KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ULL);
+ cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag)));
+ owner_thread->AddWaiter(cur_thread);
+
+ // Set thread states.
+ if (cur_thread->GetState() == ThreadState::Runnable) {
+ cur_thread->SetState(ThreadState::Waiting);
+ } else {
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
+ }
+
+ if (owner_thread->IsSuspended()) {
+ owner_thread->ContinueIfHasKernelWaiters();
+ }
+ }
+
+ // We're no longer waiting on the lock owner.
+ {
+ KScopedSchedulerLock sl{kernel};
+ KThread* owner_thread = cur_thread->GetLockOwner();
+ if (owner_thread) {
+ owner_thread->RemoveWaiter(cur_thread);
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
+ }
+ }
+}
+
+void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
+ KThread* owner_thread = reinterpret_cast<KThread*>(_cur_thread);
+
+ // Unlock.
+ {
+ KScopedSchedulerLock sl{kernel};
+
+ // Get the next owner.
+ s32 num_waiters = 0;
+ KThread* next_owner = owner_thread->RemoveWaiterByKey(
+ std::addressof(num_waiters), reinterpret_cast<uintptr_t>(std::addressof(tag)));
+
+ // Pass the lock to the next owner.
+ uintptr_t next_tag = 0;
+ if (next_owner) {
+ next_tag = reinterpret_cast<uintptr_t>(next_owner);
+ if (num_waiters > 1) {
+ next_tag |= 0x1;
+ }
+
+ if (next_owner->GetState() == ThreadState::Waiting) {
+ next_owner->SetState(ThreadState::Runnable);
+ } else {
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
+ }
+
+ if (next_owner->IsSuspended()) {
+ next_owner->ContinueIfHasKernelWaiters();
+ }
+ }
+
+ // We may have unsuspended in the process of acquiring the lock, so we'll re-suspend now if
+ // so.
+ if (owner_thread->IsSuspended()) {
+ owner_thread->TrySuspend();
+ }
+
+ // Write the new tag value.
+ tag.store(next_tag);
+ }
+}
+
+bool KLightLock::IsLockedByCurrentThread() const {
+ return (tag | 1ULL) == (reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel)) | 1ULL);
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_light_lock.h b/src/core/hle/kernel/k_light_lock.h
new file mode 100644
index 000000000..f4c45f76a
--- /dev/null
+++ b/src/core/hle/kernel/k_light_lock.h
@@ -0,0 +1,41 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_scoped_lock.h"
+
+namespace Kernel {
+
+class KernelCore;
+
+class KLightLock {
+public:
+ explicit KLightLock(KernelCore& kernel_) : kernel{kernel_} {}
+
+ void Lock();
+
+ void Unlock();
+
+ void LockSlowPath(uintptr_t owner, uintptr_t cur_thread);
+
+ void UnlockSlowPath(uintptr_t cur_thread);
+
+ bool IsLocked() const {
+ return tag != 0;
+ }
+
+ bool IsLockedByCurrentThread() const;
+
+private:
+ std::atomic<uintptr_t> tag{};
+ KernelCore& kernel;
+};
+
+using KScopedLightLock = KScopedLock<KLightLock>;
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h
new file mode 100644
index 000000000..4aa669d95
--- /dev/null
+++ b/src/core/hle/kernel/k_priority_queue.h
@@ -0,0 +1,451 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include <array>
+#include <bit>
+#include <concepts>
+
+#include "common/assert.h"
+#include "common/bit_set.h"
+#include "common/common_types.h"
+#include "common/concepts.h"
+
+namespace Kernel {
+
+class KThread;
+
+template <typename T>
+concept KPriorityQueueAffinityMask = !std::is_reference_v<T> && requires(T & t) {
+ { t.GetAffinityMask() }
+ ->Common::ConvertibleTo<u64>;
+ {t.SetAffinityMask(0)};
+
+ { t.GetAffinity(0) }
+ ->std::same_as<bool>;
+ {t.SetAffinity(0, false)};
+ {t.SetAll()};
+};
+
+template <typename T>
+concept KPriorityQueueMember = !std::is_reference_v<T> && requires(T & t) {
+ {typename T::QueueEntry()};
+ {(typename T::QueueEntry()).Initialize()};
+ {(typename T::QueueEntry()).SetPrev(std::addressof(t))};
+ {(typename T::QueueEntry()).SetNext(std::addressof(t))};
+ { (typename T::QueueEntry()).GetNext() }
+ ->std::same_as<T*>;
+ { (typename T::QueueEntry()).GetPrev() }
+ ->std::same_as<T*>;
+ { t.GetPriorityQueueEntry(0) }
+ ->std::same_as<typename T::QueueEntry&>;
+
+ {t.GetAffinityMask()};
+ { std::remove_cvref_t<decltype(t.GetAffinityMask())>() }
+ ->KPriorityQueueAffinityMask;
+
+ { t.GetActiveCore() }
+ ->Common::ConvertibleTo<s32>;
+ { t.GetPriority() }
+ ->Common::ConvertibleTo<s32>;
+};
+
+template <typename Member, size_t NumCores_, int LowestPriority, int HighestPriority>
+requires KPriorityQueueMember<Member> class KPriorityQueue {
+public:
+ using AffinityMaskType = std::remove_cv_t<
+ std::remove_reference_t<decltype(std::declval<Member>().GetAffinityMask())>>;
+
+ static_assert(LowestPriority >= 0);
+ static_assert(HighestPriority >= 0);
+ static_assert(LowestPriority >= HighestPriority);
+ static constexpr size_t NumPriority = LowestPriority - HighestPriority + 1;
+ static constexpr size_t NumCores = NumCores_;
+
+ static constexpr bool IsValidCore(s32 core) {
+ return 0 <= core && core < static_cast<s32>(NumCores);
+ }
+
+ static constexpr bool IsValidPriority(s32 priority) {
+ return HighestPriority <= priority && priority <= LowestPriority + 1;
+ }
+
+private:
+ using Entry = typename Member::QueueEntry;
+
+public:
+ class KPerCoreQueue {
+ private:
+ std::array<Entry, NumCores> root{};
+
+ public:
+ constexpr KPerCoreQueue() {
+ for (auto& per_core_root : root) {
+ per_core_root.Initialize();
+ }
+ }
+
+ constexpr bool PushBack(s32 core, Member* member) {
+ // Get the entry associated with the member.
+ Entry& member_entry = member->GetPriorityQueueEntry(core);
+
+ // Get the entry associated with the end of the queue.
+ Member* tail = this->root[core].GetPrev();
+ Entry& tail_entry =
+ (tail != nullptr) ? tail->GetPriorityQueueEntry(core) : this->root[core];
+
+ // Link the entries.
+ member_entry.SetPrev(tail);
+ member_entry.SetNext(nullptr);
+ tail_entry.SetNext(member);
+ this->root[core].SetPrev(member);
+
+ return tail == nullptr;
+ }
+
+ constexpr bool PushFront(s32 core, Member* member) {
+ // Get the entry associated with the member.
+ Entry& member_entry = member->GetPriorityQueueEntry(core);
+
+ // Get the entry associated with the front of the queue.
+ Member* head = this->root[core].GetNext();
+ Entry& head_entry =
+ (head != nullptr) ? head->GetPriorityQueueEntry(core) : this->root[core];
+
+ // Link the entries.
+ member_entry.SetPrev(nullptr);
+ member_entry.SetNext(head);
+ head_entry.SetPrev(member);
+ this->root[core].SetNext(member);
+
+ return (head == nullptr);
+ }
+
+ constexpr bool Remove(s32 core, Member* member) {
+ // Get the entry associated with the member.
+ Entry& member_entry = member->GetPriorityQueueEntry(core);
+
+ // Get the entries associated with next and prev.
+ Member* prev = member_entry.GetPrev();
+ Member* next = member_entry.GetNext();
+ Entry& prev_entry =
+ (prev != nullptr) ? prev->GetPriorityQueueEntry(core) : this->root[core];
+ Entry& next_entry =
+ (next != nullptr) ? next->GetPriorityQueueEntry(core) : this->root[core];
+
+ // Unlink.
+ prev_entry.SetNext(next);
+ next_entry.SetPrev(prev);
+
+ return (this->GetFront(core) == nullptr);
+ }
+
+ constexpr Member* GetFront(s32 core) const {
+ return this->root[core].GetNext();
+ }
+ };
+
+ class KPriorityQueueImpl {
+ public:
+ constexpr KPriorityQueueImpl() = default;
+
+ constexpr void PushBack(s32 priority, s32 core, Member* member) {
+ ASSERT(IsValidCore(core));
+ ASSERT(IsValidPriority(priority));
+
+ if (priority > LowestPriority) {
+ return;
+ }
+
+ if (this->queues[priority].PushBack(core, member)) {
+ this->available_priorities[core].SetBit(priority);
+ }
+ }
+
+ constexpr void PushFront(s32 priority, s32 core, Member* member) {
+ ASSERT(IsValidCore(core));
+ ASSERT(IsValidPriority(priority));
+
+ if (priority > LowestPriority) {
+ return;
+ }
+
+ if (this->queues[priority].PushFront(core, member)) {
+ this->available_priorities[core].SetBit(priority);
+ }
+ }
+
+ constexpr void Remove(s32 priority, s32 core, Member* member) {
+ ASSERT(IsValidCore(core));
+ ASSERT(IsValidPriority(priority));
+
+ if (priority > LowestPriority) {
+ return;
+ }
+
+ if (this->queues[priority].Remove(core, member)) {
+ this->available_priorities[core].ClearBit(priority);
+ }
+ }
+
+ constexpr Member* GetFront(s32 core) const {
+ ASSERT(IsValidCore(core));
+
+ const s32 priority =
+ static_cast<s32>(this->available_priorities[core].CountLeadingZero());
+ if (priority <= LowestPriority) {
+ return this->queues[priority].GetFront(core);
+ } else {
+ return nullptr;
+ }
+ }
+
+ constexpr Member* GetFront(s32 priority, s32 core) const {
+ ASSERT(IsValidCore(core));
+ ASSERT(IsValidPriority(priority));
+
+ if (priority <= LowestPriority) {
+ return this->queues[priority].GetFront(core);
+ } else {
+ return nullptr;
+ }
+ }
+
+ constexpr Member* GetNext(s32 core, const Member* member) const {
+ ASSERT(IsValidCore(core));
+
+ Member* next = member->GetPriorityQueueEntry(core).GetNext();
+ if (next == nullptr) {
+ const s32 priority = static_cast<s32>(
+ this->available_priorities[core].GetNextSet(member->GetPriority()));
+ if (priority <= LowestPriority) {
+ next = this->queues[priority].GetFront(core);
+ }
+ }
+ return next;
+ }
+
+ constexpr void MoveToFront(s32 priority, s32 core, Member* member) {
+ ASSERT(IsValidCore(core));
+ ASSERT(IsValidPriority(priority));
+
+ if (priority <= LowestPriority) {
+ this->queues[priority].Remove(core, member);
+ this->queues[priority].PushFront(core, member);
+ }
+ }
+
+ constexpr Member* MoveToBack(s32 priority, s32 core, Member* member) {
+ ASSERT(IsValidCore(core));
+ ASSERT(IsValidPriority(priority));
+
+ if (priority <= LowestPriority) {
+ this->queues[priority].Remove(core, member);
+ this->queues[priority].PushBack(core, member);
+ return this->queues[priority].GetFront(core);
+ } else {
+ return nullptr;
+ }
+ }
+
+ private:
+ std::array<KPerCoreQueue, NumPriority> queues{};
+ std::array<Common::BitSet64<NumPriority>, NumCores> available_priorities{};
+ };
+
+private:
+ KPriorityQueueImpl scheduled_queue;
+ KPriorityQueueImpl suggested_queue;
+
+private:
+ constexpr void ClearAffinityBit(u64& affinity, s32 core) {
+ affinity &= ~(u64(1) << core);
+ }
+
+ constexpr s32 GetNextCore(u64& affinity) {
+ const s32 core = std::countr_zero(affinity);
+ ClearAffinityBit(affinity, core);
+ return core;
+ }
+
+ constexpr void PushBack(s32 priority, Member* member) {
+ ASSERT(IsValidPriority(priority));
+
+ // Push onto the scheduled queue for its core, if we can.
+ u64 affinity = member->GetAffinityMask().GetAffinityMask();
+ if (const s32 core = member->GetActiveCore(); core >= 0) {
+ this->scheduled_queue.PushBack(priority, core, member);
+ ClearAffinityBit(affinity, core);
+ }
+
+ // And suggest the thread for all other cores.
+ while (affinity) {
+ this->suggested_queue.PushBack(priority, GetNextCore(affinity), member);
+ }
+ }
+
+ constexpr void PushFront(s32 priority, Member* member) {
+ ASSERT(IsValidPriority(priority));
+
+ // Push onto the scheduled queue for its core, if we can.
+ u64 affinity = member->GetAffinityMask().GetAffinityMask();
+ if (const s32 core = member->GetActiveCore(); core >= 0) {
+ this->scheduled_queue.PushFront(priority, core, member);
+ ClearAffinityBit(affinity, core);
+ }
+
+ // And suggest the thread for all other cores.
+ // Note: Nintendo pushes onto the back of the suggested queue, not the front.
+ while (affinity) {
+ this->suggested_queue.PushBack(priority, GetNextCore(affinity), member);
+ }
+ }
+
+ constexpr void Remove(s32 priority, Member* member) {
+ ASSERT(IsValidPriority(priority));
+
+ // Remove from the scheduled queue for its core.
+ u64 affinity = member->GetAffinityMask().GetAffinityMask();
+ if (const s32 core = member->GetActiveCore(); core >= 0) {
+ this->scheduled_queue.Remove(priority, core, member);
+ ClearAffinityBit(affinity, core);
+ }
+
+ // Remove from the suggested queue for all other cores.
+ while (affinity) {
+ this->suggested_queue.Remove(priority, GetNextCore(affinity), member);
+ }
+ }
+
+public:
+ constexpr KPriorityQueue() = default;
+
+ // Getters.
+ constexpr Member* GetScheduledFront(s32 core) const {
+ return this->scheduled_queue.GetFront(core);
+ }
+
+ constexpr Member* GetScheduledFront(s32 core, s32 priority) const {
+ return this->scheduled_queue.GetFront(priority, core);
+ }
+
+ constexpr Member* GetSuggestedFront(s32 core) const {
+ return this->suggested_queue.GetFront(core);
+ }
+
+ constexpr Member* GetSuggestedFront(s32 core, s32 priority) const {
+ return this->suggested_queue.GetFront(priority, core);
+ }
+
+ constexpr Member* GetScheduledNext(s32 core, const Member* member) const {
+ return this->scheduled_queue.GetNext(core, member);
+ }
+
+ constexpr Member* GetSuggestedNext(s32 core, const Member* member) const {
+ return this->suggested_queue.GetNext(core, member);
+ }
+
+ constexpr Member* GetSamePriorityNext(s32 core, const Member* member) const {
+ return member->GetPriorityQueueEntry(core).GetNext();
+ }
+
+ // Mutators.
+ constexpr void PushBack(Member* member) {
+ this->PushBack(member->GetPriority(), member);
+ }
+
+ constexpr void Remove(Member* member) {
+ this->Remove(member->GetPriority(), member);
+ }
+
+ constexpr void MoveToScheduledFront(Member* member) {
+ this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member);
+ }
+
+ constexpr KThread* MoveToScheduledBack(Member* member) {
+ return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(),
+ member);
+ }
+
+ // First class fancy operations.
+ constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) {
+ ASSERT(IsValidPriority(prev_priority));
+
+ // Remove the member from the queues.
+ const s32 new_priority = member->GetPriority();
+ this->Remove(prev_priority, member);
+
+ // And enqueue. If the member is running, we want to keep it running.
+ if (is_running) {
+ this->PushFront(new_priority, member);
+ } else {
+ this->PushBack(new_priority, member);
+ }
+ }
+
+ constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity,
+ Member* member) {
+ // Get the new information.
+ const s32 priority = member->GetPriority();
+ const AffinityMaskType& new_affinity = member->GetAffinityMask();
+ const s32 new_core = member->GetActiveCore();
+
+ // Remove the member from all queues it was in before.
+ for (s32 core = 0; core < static_cast<s32>(NumCores); core++) {
+ if (prev_affinity.GetAffinity(core)) {
+ if (core == prev_core) {
+ this->scheduled_queue.Remove(priority, core, member);
+ } else {
+ this->suggested_queue.Remove(priority, core, member);
+ }
+ }
+ }
+
+ // And add the member to all queues it should be in now.
+ for (s32 core = 0; core < static_cast<s32>(NumCores); core++) {
+ if (new_affinity.GetAffinity(core)) {
+ if (core == new_core) {
+ this->scheduled_queue.PushBack(priority, core, member);
+ } else {
+ this->suggested_queue.PushBack(priority, core, member);
+ }
+ }
+ }
+ }
+
+ constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) {
+ // Get the new information.
+ const s32 new_core = member->GetActiveCore();
+ const s32 priority = member->GetPriority();
+
+ // We don't need to do anything if the core is the same.
+ if (prev_core != new_core) {
+ // Remove from the scheduled queue for the previous core.
+ if (prev_core >= 0) {
+ this->scheduled_queue.Remove(priority, prev_core, member);
+ }
+
+ // Remove from the suggested queue and add to the scheduled queue for the new core.
+ if (new_core >= 0) {
+ this->suggested_queue.Remove(priority, new_core, member);
+ if (to_front) {
+ this->scheduled_queue.PushFront(priority, new_core, member);
+ } else {
+ this->scheduled_queue.PushBack(priority, new_core, member);
+ }
+ }
+
+ // Add to the suggested queue for the previous core.
+ if (prev_core >= 0) {
+ this->suggested_queue.PushBack(priority, prev_core, member);
+ }
+ }
+ }
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_readable_event.cpp b/src/core/hle/kernel/k_readable_event.cpp
new file mode 100644
index 000000000..4b4d34857
--- /dev/null
+++ b/src/core/hle/kernel/k_readable_event.cpp
@@ -0,0 +1,56 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/logging/log.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/object.h"
+#include "core/hle/kernel/svc_results.h"
+
+namespace Kernel {
+
+KReadableEvent::KReadableEvent(KernelCore& kernel, std::string&& name)
+ : KSynchronizationObject{kernel, std::move(name)} {}
+KReadableEvent::~KReadableEvent() = default;
+
+bool KReadableEvent::IsSignaled() const {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ return is_signaled;
+}
+
+ResultCode KReadableEvent::Signal() {
+ KScopedSchedulerLock lk{kernel};
+
+ if (!is_signaled) {
+ is_signaled = true;
+ NotifyAvailable();
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode KReadableEvent::Clear() {
+ Reset();
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode KReadableEvent::Reset() {
+ KScopedSchedulerLock lk{kernel};
+
+ if (!is_signaled) {
+ return ResultInvalidState;
+ }
+
+ is_signaled = false;
+ return RESULT_SUCCESS;
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_readable_event.h b/src/core/hle/kernel/k_readable_event.h
new file mode 100644
index 000000000..e6f0fd900
--- /dev/null
+++ b/src/core/hle/kernel/k_readable_event.h
@@ -0,0 +1,51 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/object.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+class KernelCore;
+class KEvent;
+
+class KReadableEvent final : public KSynchronizationObject {
+public:
+ explicit KReadableEvent(KernelCore& kernel, std::string&& name);
+ ~KReadableEvent() override;
+
+ std::string GetTypeName() const override {
+ return "KReadableEvent";
+ }
+
+ static constexpr HandleType HANDLE_TYPE = HandleType::ReadableEvent;
+ HandleType GetHandleType() const override {
+ return HANDLE_TYPE;
+ }
+
+ KEvent* GetParent() const {
+ return parent;
+ }
+
+ void Initialize(KEvent* parent_) {
+ is_signaled = false;
+ parent = parent_;
+ }
+
+ bool IsSignaled() const override;
+ void Finalize() override {}
+
+ ResultCode Signal();
+ ResultCode Clear();
+ ResultCode Reset();
+
+private:
+ bool is_signaled{};
+ KEvent* parent{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp
new file mode 100644
index 000000000..d7a4a38e6
--- /dev/null
+++ b/src/core/hle/kernel/k_resource_limit.cpp
@@ -0,0 +1,152 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#include "common/assert.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/hle/kernel/k_resource_limit.h"
+#include "core/hle/kernel/svc_results.h"
+
+namespace Kernel {
+constexpr s64 DefaultTimeout = 10000000000; // 10 seconds
+
+KResourceLimit::KResourceLimit(KernelCore& kernel, Core::System& system)
+ : Object{kernel}, lock{kernel}, cond_var{kernel}, kernel{kernel}, system(system) {}
+KResourceLimit::~KResourceLimit() = default;
+
+s64 KResourceLimit::GetLimitValue(LimitableResource which) const {
+ const auto index = static_cast<std::size_t>(which);
+ s64 value{};
+ {
+ KScopedLightLock lk{lock};
+ value = limit_values[index];
+ ASSERT(value >= 0);
+ ASSERT(current_values[index] <= limit_values[index]);
+ ASSERT(current_hints[index] <= current_values[index]);
+ }
+ return value;
+}
+
+s64 KResourceLimit::GetCurrentValue(LimitableResource which) const {
+ const auto index = static_cast<std::size_t>(which);
+ s64 value{};
+ {
+ KScopedLightLock lk{lock};
+ value = current_values[index];
+ ASSERT(value >= 0);
+ ASSERT(current_values[index] <= limit_values[index]);
+ ASSERT(current_hints[index] <= current_values[index]);
+ }
+ return value;
+}
+
+s64 KResourceLimit::GetPeakValue(LimitableResource which) const {
+ const auto index = static_cast<std::size_t>(which);
+ s64 value{};
+ {
+ KScopedLightLock lk{lock};
+ value = peak_values[index];
+ ASSERT(value >= 0);
+ ASSERT(current_values[index] <= limit_values[index]);
+ ASSERT(current_hints[index] <= current_values[index]);
+ }
+ return value;
+}
+
+s64 KResourceLimit::GetFreeValue(LimitableResource which) const {
+ const auto index = static_cast<std::size_t>(which);
+ s64 value{};
+ {
+ KScopedLightLock lk(lock);
+ ASSERT(current_values[index] >= 0);
+ ASSERT(current_values[index] <= limit_values[index]);
+ ASSERT(current_hints[index] <= current_values[index]);
+ value = limit_values[index] - current_values[index];
+ }
+
+ return value;
+}
+
+ResultCode KResourceLimit::SetLimitValue(LimitableResource which, s64 value) {
+ const auto index = static_cast<std::size_t>(which);
+ KScopedLightLock lk(lock);
+ R_UNLESS(current_values[index] <= value, ResultInvalidState);
+
+ limit_values[index] = value;
+
+ return RESULT_SUCCESS;
+}
+
+bool KResourceLimit::Reserve(LimitableResource which, s64 value) {
+ return Reserve(which, value, system.CoreTiming().GetGlobalTimeNs().count() + DefaultTimeout);
+}
+
+bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) {
+ ASSERT(value >= 0);
+ const auto index = static_cast<std::size_t>(which);
+ KScopedLightLock lk(lock);
+
+ ASSERT(current_hints[index] <= current_values[index]);
+ if (current_hints[index] >= limit_values[index]) {
+ return false;
+ }
+
+ // Loop until we reserve or run out of time.
+ while (true) {
+ ASSERT(current_values[index] <= limit_values[index]);
+ ASSERT(current_hints[index] <= current_values[index]);
+
+ // If we would overflow, don't allow to succeed.
+ if (current_values[index] + value <= current_values[index]) {
+ break;
+ }
+
+ if (current_values[index] + value <= limit_values[index]) {
+ current_values[index] += value;
+ current_hints[index] += value;
+ peak_values[index] = std::max(peak_values[index], current_values[index]);
+ return true;
+ }
+
+ if (current_hints[index] + value <= limit_values[index] &&
+ (timeout < 0 || system.CoreTiming().GetGlobalTimeNs().count() < timeout)) {
+ waiter_count++;
+ cond_var.Wait(&lock, timeout);
+ waiter_count--;
+ } else {
+ break;
+ }
+ }
+
+ return false;
+}
+
+void KResourceLimit::Release(LimitableResource which, s64 value) {
+ Release(which, value, value);
+}
+
+void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) {
+ ASSERT(value >= 0);
+ ASSERT(hint >= 0);
+
+ const auto index = static_cast<std::size_t>(which);
+ KScopedLightLock lk(lock);
+ ASSERT(current_values[index] <= limit_values[index]);
+ ASSERT(current_hints[index] <= current_values[index]);
+ ASSERT(value <= current_values[index]);
+ ASSERT(hint <= current_hints[index]);
+
+ current_values[index] -= value;
+ current_hints[index] -= hint;
+
+ if (waiter_count != 0) {
+ cond_var.Broadcast();
+ }
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h
new file mode 100644
index 000000000..58ae456f1
--- /dev/null
+++ b/src/core/hle/kernel/k_resource_limit.h
@@ -0,0 +1,81 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "core/hle/kernel/k_light_condition_variable.h"
+#include "core/hle/kernel/k_light_lock.h"
+#include "core/hle/kernel/object.h"
+
+union ResultCode;
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+class KernelCore;
+enum class LimitableResource : u32 {
+ PhysicalMemory = 0,
+ Threads = 1,
+ Events = 2,
+ TransferMemory = 3,
+ Sessions = 4,
+
+ Count,
+};
+
+constexpr bool IsValidResourceType(LimitableResource type) {
+ return type < LimitableResource::Count;
+}
+
+class KResourceLimit final : public Object {
+public:
+ explicit KResourceLimit(KernelCore& kernel, Core::System& system);
+ ~KResourceLimit();
+
+ s64 GetLimitValue(LimitableResource which) const;
+ s64 GetCurrentValue(LimitableResource which) const;
+ s64 GetPeakValue(LimitableResource which) const;
+ s64 GetFreeValue(LimitableResource which) const;
+
+ ResultCode SetLimitValue(LimitableResource which, s64 value);
+
+ bool Reserve(LimitableResource which, s64 value);
+ bool Reserve(LimitableResource which, s64 value, s64 timeout);
+ void Release(LimitableResource which, s64 value);
+ void Release(LimitableResource which, s64 value, s64 hint);
+
+ std::string GetTypeName() const override {
+ return "KResourceLimit";
+ }
+ std::string GetName() const override {
+ return GetTypeName();
+ }
+
+ static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit;
+ HandleType GetHandleType() const override {
+ return HANDLE_TYPE;
+ }
+
+ virtual void Finalize() override {}
+
+private:
+ using ResourceArray = std::array<s64, static_cast<std::size_t>(LimitableResource::Count)>;
+ ResourceArray limit_values{};
+ ResourceArray current_values{};
+ ResourceArray current_hints{};
+ ResourceArray peak_values{};
+ mutable KLightLock lock;
+ s32 waiter_count{};
+ KLightConditionVariable cond_var;
+ KernelCore& kernel;
+ Core::System& system;
+};
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
new file mode 100644
index 000000000..bb5f43b53
--- /dev/null
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -0,0 +1,814 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#include <bit>
+
+#include "common/assert.h"
+#include "common/bit_util.h"
+#include "common/fiber.h"
+#include "common/logging/log.h"
+#include "core/arm/arm_interface.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/cpu_manager.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/physical_core.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/time_manager.h"
+
+namespace Kernel {
+
+static void IncrementScheduledCount(Kernel::KThread* thread) {
+ if (auto process = thread->GetOwnerProcess(); process) {
+ process->IncrementScheduledCount();
+ }
+}
+
+void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule) {
+ auto scheduler = kernel.CurrentScheduler();
+
+ u32 current_core{0xF};
+ bool must_context_switch{};
+ if (scheduler) {
+ current_core = scheduler->core_id;
+ // TODO(bunnei): Should be set to true when we deprecate single core
+ must_context_switch = !kernel.IsPhantomModeForSingleCore();
+ }
+
+ while (cores_pending_reschedule != 0) {
+ const auto core = static_cast<u32>(std::countr_zero(cores_pending_reschedule));
+ ASSERT(core < Core::Hardware::NUM_CPU_CORES);
+ if (!must_context_switch || core != current_core) {
+ auto& phys_core = kernel.PhysicalCore(core);
+ phys_core.Interrupt();
+ } else {
+ must_context_switch = true;
+ }
+ cores_pending_reschedule &= ~(1ULL << core);
+ }
+ if (must_context_switch) {
+ auto core_scheduler = kernel.CurrentScheduler();
+ kernel.ExitSVCProfile();
+ core_scheduler->RescheduleCurrentCore();
+ kernel.EnterSVCProfile();
+ }
+}
+
+u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
+ std::scoped_lock lock{guard};
+ if (KThread* prev_highest_thread = state.highest_priority_thread;
+ prev_highest_thread != highest_thread) {
+ if (prev_highest_thread != nullptr) {
+ IncrementScheduledCount(prev_highest_thread);
+ prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks());
+ }
+ if (state.should_count_idle) {
+ if (highest_thread != nullptr) {
+ if (Process* process = highest_thread->GetOwnerProcess(); process != nullptr) {
+ process->SetRunningThread(core_id, highest_thread, state.idle_count);
+ }
+ } else {
+ state.idle_count++;
+ }
+ }
+
+ state.highest_priority_thread = highest_thread;
+ state.needs_scheduling.store(true);
+ return (1ULL << core_id);
+ } else {
+ return 0;
+ }
+}
+
+u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Clear that we need to update.
+ ClearSchedulerUpdateNeeded(kernel);
+
+ u64 cores_needing_scheduling = 0, idle_cores = 0;
+ KThread* top_threads[Core::Hardware::NUM_CPU_CORES];
+ auto& priority_queue = GetPriorityQueue(kernel);
+
+ /// We want to go over all cores, finding the highest priority thread and determining if
+ /// scheduling is needed for that core.
+ for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
+ KThread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id));
+ if (top_thread != nullptr) {
+ // If the thread has no waiters, we need to check if the process has a thread pinned.
+ if (top_thread->GetNumKernelWaiters() == 0) {
+ if (Process* parent = top_thread->GetOwnerProcess(); parent != nullptr) {
+ if (KThread* pinned = parent->GetPinnedThread(static_cast<s32>(core_id));
+ pinned != nullptr && pinned != top_thread) {
+ // We prefer our parent's pinned thread if possible. However, we also don't
+ // want to schedule un-runnable threads.
+ if (pinned->GetRawState() == ThreadState::Runnable) {
+ top_thread = pinned;
+ } else {
+ top_thread = nullptr;
+ }
+ }
+ }
+ }
+ } else {
+ idle_cores |= (1ULL << core_id);
+ }
+
+ top_threads[core_id] = top_thread;
+ cores_needing_scheduling |=
+ kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
+ }
+
+ // Idle cores are bad. We're going to try to migrate threads to each idle core in turn.
+ while (idle_cores != 0) {
+ const auto core_id = static_cast<u32>(std::countr_zero(idle_cores));
+ if (KThread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) {
+ s32 migration_candidates[Core::Hardware::NUM_CPU_CORES];
+ size_t num_candidates = 0;
+
+ // While we have a suggested thread, try to migrate it!
+ while (suggested != nullptr) {
+ // Check if the suggested thread is the top thread on its core.
+ const s32 suggested_core = suggested->GetActiveCore();
+ if (KThread* top_thread =
+ (suggested_core >= 0) ? top_threads[suggested_core] : nullptr;
+ top_thread != suggested) {
+ // Make sure we're not dealing with threads too high priority for migration.
+ if (top_thread != nullptr &&
+ top_thread->GetPriority() < HighestCoreMigrationAllowedPriority) {
+ break;
+ }
+
+ // The suggested thread isn't bound to its core, so we can migrate it!
+ suggested->SetActiveCore(core_id);
+ priority_queue.ChangeCore(suggested_core, suggested);
+
+ top_threads[core_id] = suggested;
+ cores_needing_scheduling |=
+ kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
+ break;
+ }
+
+ // Note this core as a candidate for migration.
+ ASSERT(num_candidates < Core::Hardware::NUM_CPU_CORES);
+ migration_candidates[num_candidates++] = suggested_core;
+ suggested = priority_queue.GetSuggestedNext(core_id, suggested);
+ }
+
+ // If suggested is nullptr, we failed to migrate a specific thread. So let's try all our
+ // candidate cores' top threads.
+ if (suggested == nullptr) {
+ for (size_t i = 0; i < num_candidates; i++) {
+ // Check if there's some other thread that can run on the candidate core.
+ const s32 candidate_core = migration_candidates[i];
+ suggested = top_threads[candidate_core];
+ if (KThread* next_on_candidate_core =
+ priority_queue.GetScheduledNext(candidate_core, suggested);
+ next_on_candidate_core != nullptr) {
+ // The candidate core can run some other thread! We'll migrate its current
+ // top thread to us.
+ top_threads[candidate_core] = next_on_candidate_core;
+ cores_needing_scheduling |=
+ kernel.Scheduler(candidate_core)
+ .UpdateHighestPriorityThread(top_threads[candidate_core]);
+
+ // Perform the migration.
+ suggested->SetActiveCore(core_id);
+ priority_queue.ChangeCore(candidate_core, suggested);
+
+ top_threads[core_id] = suggested;
+ cores_needing_scheduling |=
+ kernel.Scheduler(core_id).UpdateHighestPriorityThread(
+ top_threads[core_id]);
+ break;
+ }
+ }
+ }
+ }
+
+ idle_cores &= ~(1ULL << core_id);
+ }
+
+ return cores_needing_scheduling;
+}
+
+void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; ++i) {
+ // Get an atomic reference to the core scheduler's previous thread.
+ std::atomic_ref<KThread*> prev_thread(kernel.Scheduler(static_cast<s32>(i)).prev_thread);
+ static_assert(std::atomic_ref<KThread*>::is_always_lock_free);
+
+ // Atomically clear the previous thread if it's our target.
+ KThread* compare = thread;
+ prev_thread.compare_exchange_strong(compare, nullptr);
+ }
+}
+
+void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Check if the state has changed, because if it hasn't there's nothing to do.
+ const auto cur_state = thread->GetRawState();
+ if (cur_state == old_state) {
+ return;
+ }
+
+ // Update the priority queues.
+ if (old_state == ThreadState::Runnable) {
+ // If we were previously runnable, then we're not runnable now, and we should remove.
+ GetPriorityQueue(kernel).Remove(thread);
+ IncrementScheduledCount(thread);
+ SetSchedulerUpdateNeeded(kernel);
+ } else if (cur_state == ThreadState::Runnable) {
+ // If we're now runnable, then we weren't previously, and we should add.
+ GetPriorityQueue(kernel).PushBack(thread);
+ IncrementScheduledCount(thread);
+ SetSchedulerUpdateNeeded(kernel);
+ }
+}
+
+void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // If the thread is runnable, we want to change its priority in the queue.
+ if (thread->GetRawState() == ThreadState::Runnable) {
+ GetPriorityQueue(kernel).ChangePriority(
+ old_priority, thread == kernel.CurrentScheduler()->GetCurrentThread(), thread);
+ IncrementScheduledCount(thread);
+ SetSchedulerUpdateNeeded(kernel);
+ }
+}
+
+void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread,
+ const KAffinityMask& old_affinity, s32 old_core) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // If the thread is runnable, we want to change its affinity in the queue.
+ if (thread->GetRawState() == ThreadState::Runnable) {
+ GetPriorityQueue(kernel).ChangeAffinityMask(old_core, old_affinity, thread);
+ IncrementScheduledCount(thread);
+ SetSchedulerUpdateNeeded(kernel);
+ }
+}
+
+void KScheduler::RotateScheduledQueue(s32 core_id, s32 priority) {
+ ASSERT(system.GlobalSchedulerContext().IsLocked());
+
+ // Get a reference to the priority queue.
+ auto& kernel = system.Kernel();
+ auto& priority_queue = GetPriorityQueue(kernel);
+
+ // Rotate the front of the queue to the end.
+ KThread* top_thread = priority_queue.GetScheduledFront(core_id, priority);
+ KThread* next_thread = nullptr;
+ if (top_thread != nullptr) {
+ next_thread = priority_queue.MoveToScheduledBack(top_thread);
+ if (next_thread != top_thread) {
+ IncrementScheduledCount(top_thread);
+ IncrementScheduledCount(next_thread);
+ }
+ }
+
+ // While we have a suggested thread, try to migrate it!
+ {
+ KThread* suggested = priority_queue.GetSuggestedFront(core_id, priority);
+ while (suggested != nullptr) {
+ // Check if the suggested thread is the top thread on its core.
+ const s32 suggested_core = suggested->GetActiveCore();
+ if (KThread* top_on_suggested_core =
+ (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
+ : nullptr;
+ top_on_suggested_core != suggested) {
+ // If the next thread is a new thread that has been waiting longer than our
+ // suggestion, we prefer it to our suggestion.
+ if (top_thread != next_thread && next_thread != nullptr &&
+ next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick()) {
+ suggested = nullptr;
+ break;
+ }
+
+ // If we're allowed to do a migration, do one.
+ // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion
+ // to the front of the queue.
+ if (top_on_suggested_core == nullptr ||
+ top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
+ suggested->SetActiveCore(core_id);
+ priority_queue.ChangeCore(suggested_core, suggested, true);
+ IncrementScheduledCount(suggested);
+ break;
+ }
+ }
+
+ // Get the next suggestion.
+ suggested = priority_queue.GetSamePriorityNext(core_id, suggested);
+ }
+ }
+
+ // Now that we might have migrated a thread with the same priority, check if we can do better.
+
+ {
+ KThread* best_thread = priority_queue.GetScheduledFront(core_id);
+ if (best_thread == GetCurrentThread()) {
+ best_thread = priority_queue.GetScheduledNext(core_id, best_thread);
+ }
+
+ // If the best thread we can choose has a priority the same or worse than ours, try to
+ // migrate a higher priority thread.
+ if (best_thread != nullptr && best_thread->GetPriority() >= priority) {
+ KThread* suggested = priority_queue.GetSuggestedFront(core_id);
+ while (suggested != nullptr) {
+ // If the suggestion's priority is the same as ours, don't bother.
+ if (suggested->GetPriority() >= best_thread->GetPriority()) {
+ break;
+ }
+
+ // Check if the suggested thread is the top thread on its core.
+ const s32 suggested_core = suggested->GetActiveCore();
+ if (KThread* top_on_suggested_core =
+ (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
+ : nullptr;
+ top_on_suggested_core != suggested) {
+ // If we're allowed to do a migration, do one.
+ // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the
+ // suggestion to the front of the queue.
+ if (top_on_suggested_core == nullptr ||
+ top_on_suggested_core->GetPriority() >=
+ HighestCoreMigrationAllowedPriority) {
+ suggested->SetActiveCore(core_id);
+ priority_queue.ChangeCore(suggested_core, suggested, true);
+ IncrementScheduledCount(suggested);
+ break;
+ }
+ }
+
+ // Get the next suggestion.
+ suggested = priority_queue.GetSuggestedNext(core_id, suggested);
+ }
+ }
+ }
+
+ // After a rotation, we need a scheduler update.
+ SetSchedulerUpdateNeeded(kernel);
+}
+
+bool KScheduler::CanSchedule(KernelCore& kernel) {
+ return kernel.CurrentScheduler()->GetCurrentThread()->GetDisableDispatchCount() <= 1;
+}
+
+bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
+ return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire);
+}
+
+void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) {
+ kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release);
+}
+
+void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) {
+ kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release);
+}
+
+void KScheduler::DisableScheduling(KernelCore& kernel) {
+ if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
+ ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 0);
+ scheduler->GetCurrentThread()->DisableDispatch();
+ }
+}
+
+void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
+ if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
+ ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1);
+ if (scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1) {
+ scheduler->GetCurrentThread()->EnableDispatch();
+ }
+ }
+ RescheduleCores(kernel, cores_needing_scheduling);
+}
+
+u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
+ if (IsSchedulerUpdateNeeded(kernel)) {
+ return UpdateHighestPriorityThreadsImpl(kernel);
+ } else {
+ return 0;
+ }
+}
+
+KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) {
+ return kernel.GlobalSchedulerContext().priority_queue;
+}
+
+void KScheduler::YieldWithoutCoreMigration(KernelCore& kernel) {
+ // Validate preconditions.
+ ASSERT(CanSchedule(kernel));
+ ASSERT(kernel.CurrentProcess() != nullptr);
+
+ // Get the current thread and process.
+ KThread& cur_thread = Kernel::GetCurrentThread(kernel);
+ Process& cur_process = *kernel.CurrentProcess();
+
+ // If the thread's yield count matches, there's nothing for us to do.
+ if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
+ return;
+ }
+
+ // Get a reference to the priority queue.
+ auto& priority_queue = GetPriorityQueue(kernel);
+
+ // Perform the yield.
+ {
+ KScopedSchedulerLock lock(kernel);
+
+ const auto cur_state = cur_thread.GetRawState();
+ if (cur_state == ThreadState::Runnable) {
+ // Put the current thread at the back of the queue.
+ KThread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread));
+ IncrementScheduledCount(std::addressof(cur_thread));
+
+ // If the next thread is different, we have an update to perform.
+ if (next_thread != std::addressof(cur_thread)) {
+ SetSchedulerUpdateNeeded(kernel);
+ } else {
+ // Otherwise, set the thread's yield count so that we won't waste work until the
+ // process is scheduled again.
+ cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
+ }
+ }
+ }
+}
+
+void KScheduler::YieldWithCoreMigration(KernelCore& kernel) {
+ // Validate preconditions.
+ ASSERT(CanSchedule(kernel));
+ ASSERT(kernel.CurrentProcess() != nullptr);
+
+ // Get the current thread and process.
+ KThread& cur_thread = Kernel::GetCurrentThread(kernel);
+ Process& cur_process = *kernel.CurrentProcess();
+
+ // If the thread's yield count matches, there's nothing for us to do.
+ if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
+ return;
+ }
+
+ // Get a reference to the priority queue.
+ auto& priority_queue = GetPriorityQueue(kernel);
+
+ // Perform the yield.
+ {
+ KScopedSchedulerLock lock(kernel);
+
+ const auto cur_state = cur_thread.GetRawState();
+ if (cur_state == ThreadState::Runnable) {
+ // Get the current active core.
+ const s32 core_id = cur_thread.GetActiveCore();
+
+ // Put the current thread at the back of the queue.
+ KThread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread));
+ IncrementScheduledCount(std::addressof(cur_thread));
+
+ // While we have a suggested thread, try to migrate it!
+ bool recheck = false;
+ KThread* suggested = priority_queue.GetSuggestedFront(core_id);
+ while (suggested != nullptr) {
+ // Check if the suggested thread is the thread running on its core.
+ const s32 suggested_core = suggested->GetActiveCore();
+
+ if (KThread* running_on_suggested_core =
+ (suggested_core >= 0)
+ ? kernel.Scheduler(suggested_core).state.highest_priority_thread
+ : nullptr;
+ running_on_suggested_core != suggested) {
+ // If the current thread's priority is higher than our suggestion's we prefer
+ // the next thread to the suggestion. We also prefer the next thread when the
+ // current thread's priority is equal to the suggestions, but the next thread
+ // has been waiting longer.
+ if ((suggested->GetPriority() > cur_thread.GetPriority()) ||
+ (suggested->GetPriority() == cur_thread.GetPriority() &&
+ next_thread != std::addressof(cur_thread) &&
+ next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick())) {
+ suggested = nullptr;
+ break;
+ }
+
+ // If we're allowed to do a migration, do one.
+ // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the
+ // suggestion to the front of the queue.
+ if (running_on_suggested_core == nullptr ||
+ running_on_suggested_core->GetPriority() >=
+ HighestCoreMigrationAllowedPriority) {
+ suggested->SetActiveCore(core_id);
+ priority_queue.ChangeCore(suggested_core, suggested, true);
+ IncrementScheduledCount(suggested);
+ break;
+ } else {
+ // We couldn't perform a migration, but we should check again on a future
+ // yield.
+ recheck = true;
+ }
+ }
+
+ // Get the next suggestion.
+ suggested = priority_queue.GetSuggestedNext(core_id, suggested);
+ }
+
+ // If we still have a suggestion or the next thread is different, we have an update to
+ // perform.
+ if (suggested != nullptr || next_thread != std::addressof(cur_thread)) {
+ SetSchedulerUpdateNeeded(kernel);
+ } else if (!recheck) {
+ // Otherwise if we don't need to re-check, set the thread's yield count so that we
+ // won't waste work until the process is scheduled again.
+ cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
+ }
+ }
+ }
+}
+
+void KScheduler::YieldToAnyThread(KernelCore& kernel) {
+ // Validate preconditions.
+ ASSERT(CanSchedule(kernel));
+ ASSERT(kernel.CurrentProcess() != nullptr);
+
+ // Get the current thread and process.
+ KThread& cur_thread = Kernel::GetCurrentThread(kernel);
+ Process& cur_process = *kernel.CurrentProcess();
+
+ // If the thread's yield count matches, there's nothing for us to do.
+ if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
+ return;
+ }
+
+ // Get a reference to the priority queue.
+ auto& priority_queue = GetPriorityQueue(kernel);
+
+ // Perform the yield.
+ {
+ KScopedSchedulerLock lock(kernel);
+
+ const auto cur_state = cur_thread.GetRawState();
+ if (cur_state == ThreadState::Runnable) {
+ // Get the current active core.
+ const s32 core_id = cur_thread.GetActiveCore();
+
+ // Migrate the current thread to core -1.
+ cur_thread.SetActiveCore(-1);
+ priority_queue.ChangeCore(core_id, std::addressof(cur_thread));
+ IncrementScheduledCount(std::addressof(cur_thread));
+
+ // If there's nothing scheduled, we can try to perform a migration.
+ if (priority_queue.GetScheduledFront(core_id) == nullptr) {
+ // While we have a suggested thread, try to migrate it!
+ KThread* suggested = priority_queue.GetSuggestedFront(core_id);
+ while (suggested != nullptr) {
+ // Check if the suggested thread is the top thread on its core.
+ const s32 suggested_core = suggested->GetActiveCore();
+ if (KThread* top_on_suggested_core =
+ (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
+ : nullptr;
+ top_on_suggested_core != suggested) {
+ // If we're allowed to do a migration, do one.
+ if (top_on_suggested_core == nullptr ||
+ top_on_suggested_core->GetPriority() >=
+ HighestCoreMigrationAllowedPriority) {
+ suggested->SetActiveCore(core_id);
+ priority_queue.ChangeCore(suggested_core, suggested);
+ IncrementScheduledCount(suggested);
+ }
+
+ // Regardless of whether we migrated, we had a candidate, so we're done.
+ break;
+ }
+
+ // Get the next suggestion.
+ suggested = priority_queue.GetSuggestedNext(core_id, suggested);
+ }
+
+ // If the suggestion is different from the current thread, we need to perform an
+ // update.
+ if (suggested != std::addressof(cur_thread)) {
+ SetSchedulerUpdateNeeded(kernel);
+ } else {
+ // Otherwise, set the thread's yield count so that we won't waste work until the
+ // process is scheduled again.
+ cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
+ }
+ } else {
+ // Otherwise, we have an update to perform.
+ SetSchedulerUpdateNeeded(kernel);
+ }
+ }
+ }
+}
+
+KScheduler::KScheduler(Core::System& system, s32 core_id) : system(system), core_id(core_id) {
+ switch_fiber = std::make_shared<Common::Fiber>(OnSwitch, this);
+ state.needs_scheduling.store(true);
+ state.interrupt_task_thread_runnable = false;
+ state.should_count_idle = false;
+ state.idle_count = 0;
+ state.idle_thread_stack = nullptr;
+ state.highest_priority_thread = nullptr;
+}
+
+KScheduler::~KScheduler() = default;
+
+KThread* KScheduler::GetCurrentThread() const {
+ if (auto result = current_thread.load(); result) {
+ return result;
+ }
+ return idle_thread;
+}
+
+u64 KScheduler::GetLastContextSwitchTicks() const {
+ return last_context_switch_time;
+}
+
+void KScheduler::RescheduleCurrentCore() {
+ ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1);
+
+ auto& phys_core = system.Kernel().PhysicalCore(core_id);
+ if (phys_core.IsInterrupted()) {
+ phys_core.ClearInterrupt();
+ }
+ guard.lock();
+ if (state.needs_scheduling.load()) {
+ Schedule();
+ } else {
+ guard.unlock();
+ }
+}
+
+void KScheduler::OnThreadStart() {
+ SwitchContextStep2();
+}
+
+void KScheduler::Unload(KThread* thread) {
+ LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr");
+
+ if (thread) {
+ if (thread->IsCallingSvc()) {
+ system.ArmInterface(core_id).ExceptionalExit();
+ thread->ClearIsCallingSvc();
+ }
+ if (!thread->IsTerminationRequested()) {
+ prev_thread = thread;
+
+ Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
+ cpu_core.SaveContext(thread->GetContext32());
+ cpu_core.SaveContext(thread->GetContext64());
+ // Save the TPIDR_EL0 system register in case it was modified.
+ thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
+ cpu_core.ClearExclusiveState();
+ } else {
+ prev_thread = nullptr;
+ }
+ thread->context_guard.unlock();
+ }
+}
+
+void KScheduler::Reload(KThread* thread) {
+ LOG_TRACE(Kernel, "core {}, reload thread {}", core_id, thread ? thread->GetName() : "nullptr");
+
+ if (thread) {
+ ASSERT_MSG(thread->GetState() == ThreadState::Runnable, "Thread must be runnable.");
+
+ auto* const thread_owner_process = thread->GetOwnerProcess();
+ if (thread_owner_process != nullptr) {
+ system.Kernel().MakeCurrentProcess(thread_owner_process);
+ }
+
+ Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
+ cpu_core.LoadContext(thread->GetContext32());
+ cpu_core.LoadContext(thread->GetContext64());
+ cpu_core.SetTlsAddress(thread->GetTLSAddress());
+ cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
+ cpu_core.ClearExclusiveState();
+ }
+}
+
+void KScheduler::SwitchContextStep2() {
+ // Load context of new thread
+ Reload(current_thread.load());
+
+ RescheduleCurrentCore();
+}
+
+void KScheduler::ScheduleImpl() {
+ KThread* previous_thread = current_thread.load();
+ KThread* next_thread = state.highest_priority_thread;
+
+ state.needs_scheduling = false;
+
+ // We never want to schedule a null thread, so use the idle thread if we don't have a next.
+ if (next_thread == nullptr) {
+ next_thread = idle_thread;
+ }
+
+ // If we're not actually switching thread, there's nothing to do.
+ if (next_thread == current_thread.load()) {
+ guard.unlock();
+ return;
+ }
+
+ current_thread.store(next_thread);
+
+ Process* const previous_process = system.Kernel().CurrentProcess();
+
+ UpdateLastContextSwitchTime(previous_thread, previous_process);
+
+ // Save context for previous thread
+ Unload(previous_thread);
+
+ std::shared_ptr<Common::Fiber>* old_context;
+ if (previous_thread != nullptr) {
+ old_context = &previous_thread->GetHostContext();
+ } else {
+ old_context = &idle_thread->GetHostContext();
+ }
+ guard.unlock();
+
+ Common::Fiber::YieldTo(*old_context, switch_fiber);
+ /// When a thread wakes up, the scheduler may have changed to other in another core.
+ auto& next_scheduler = *system.Kernel().CurrentScheduler();
+ next_scheduler.SwitchContextStep2();
+}
+
+void KScheduler::OnSwitch(void* this_scheduler) {
+ KScheduler* sched = static_cast<KScheduler*>(this_scheduler);
+ sched->SwitchToCurrent();
+}
+
+void KScheduler::SwitchToCurrent() {
+ while (true) {
+ {
+ std::scoped_lock lock{guard};
+ current_thread.store(state.highest_priority_thread);
+ state.needs_scheduling.store(false);
+ }
+ const auto is_switch_pending = [this] {
+ std::scoped_lock lock{guard};
+ return state.needs_scheduling.load();
+ };
+ do {
+ auto next_thread = current_thread.load();
+ if (next_thread != nullptr) {
+ next_thread->context_guard.lock();
+ if (next_thread->GetRawState() != ThreadState::Runnable) {
+ next_thread->context_guard.unlock();
+ break;
+ }
+ if (next_thread->GetActiveCore() != core_id) {
+ next_thread->context_guard.unlock();
+ break;
+ }
+ }
+ std::shared_ptr<Common::Fiber>* next_context;
+ if (next_thread != nullptr) {
+ next_context = &next_thread->GetHostContext();
+ } else {
+ next_context = &idle_thread->GetHostContext();
+ }
+ Common::Fiber::YieldTo(switch_fiber, *next_context);
+ } while (!is_switch_pending());
+ }
+}
+
+void KScheduler::UpdateLastContextSwitchTime(KThread* thread, Process* process) {
+ const u64 prev_switch_ticks = last_context_switch_time;
+ const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
+ const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
+
+ if (thread != nullptr) {
+ thread->AddCpuTime(core_id, update_ticks);
+ }
+
+ if (process != nullptr) {
+ process->UpdateCPUTimeTicks(update_ticks);
+ }
+
+ last_context_switch_time = most_recent_switch_ticks;
+}
+
+void KScheduler::Initialize() {
+ std::string name = "Idle Thread Id:" + std::to_string(core_id);
+ std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc();
+ void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
+ auto thread_res = KThread::Create(system, ThreadType::Main, name, 0,
+ KThread::IdleThreadPriority, 0, static_cast<u32>(core_id), 0,
+ nullptr, std::move(init_func), init_func_parameter);
+ idle_thread = thread_res.Unwrap().get();
+}
+
+KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel)
+ : KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {}
+
+KScopedSchedulerLock::~KScopedSchedulerLock() = default;
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h
new file mode 100644
index 000000000..f595b9a5c
--- /dev/null
+++ b/src/core/hle/kernel/k_scheduler.h
@@ -0,0 +1,207 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include <atomic>
+
+#include "common/common_types.h"
+#include "common/spin_lock.h"
+#include "core/hle/kernel/global_scheduler_context.h"
+#include "core/hle/kernel/k_priority_queue.h"
+#include "core/hle/kernel/k_scheduler_lock.h"
+#include "core/hle/kernel/k_scoped_lock.h"
+
+namespace Common {
+class Fiber;
+}
+
+namespace Core {
+class System;
+}
+
+namespace Kernel {
+
+class KernelCore;
+class Process;
+class SchedulerLock;
+class KThread;
+
+class KScheduler final {
+public:
+ explicit KScheduler(Core::System& system, s32 core_id);
+ ~KScheduler();
+
+ /// Reschedules to the next available thread (call after current thread is suspended)
+ void RescheduleCurrentCore();
+
+ /// Reschedules cores pending reschedule, to be called on EnableScheduling.
+ static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule);
+
+ /// The next two are for SingleCore Only.
+ /// Unload current thread before preempting core.
+ void Unload(KThread* thread);
+
+ /// Reload current thread after core preemption.
+ void Reload(KThread* thread);
+
+ /// Gets the current running thread
+ [[nodiscard]] KThread* GetCurrentThread() const;
+
+ /// Returns true if the scheduler is idle
+ [[nodiscard]] bool IsIdle() const {
+ return GetCurrentThread() == idle_thread;
+ }
+
+ /// Gets the timestamp for the last context switch in ticks.
+ [[nodiscard]] u64 GetLastContextSwitchTicks() const;
+
+ [[nodiscard]] bool ContextSwitchPending() const {
+ return state.needs_scheduling.load(std::memory_order_relaxed);
+ }
+
+ void Initialize();
+
+ void OnThreadStart();
+
+ [[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() {
+ return switch_fiber;
+ }
+
+ [[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const {
+ return switch_fiber;
+ }
+
+ [[nodiscard]] u64 UpdateHighestPriorityThread(KThread* highest_thread);
+
+ /**
+ * Takes a thread and moves it to the back of the it's priority list.
+ *
+ * @note This operation can be redundant and no scheduling is changed if marked as so.
+ */
+ static void YieldWithoutCoreMigration(KernelCore& kernel);
+
+ /**
+ * Takes a thread and moves it to the back of the it's priority list.
+ * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
+ * a better priority than the next thread in the core.
+ *
+ * @note This operation can be redundant and no scheduling is changed if marked as so.
+ */
+ static void YieldWithCoreMigration(KernelCore& kernel);
+
+ /**
+ * Takes a thread and moves it out of the scheduling queue.
+ * and into the suggested queue. If no thread can be scheduled afterwards in that core,
+ * a suggested thread is obtained instead.
+ *
+ * @note This operation can be redundant and no scheduling is changed if marked as so.
+ */
+ static void YieldToAnyThread(KernelCore& kernel);
+
+ static void ClearPreviousThread(KernelCore& kernel, KThread* thread);
+
+ /// Notify the scheduler a thread's status has changed.
+ static void OnThreadStateChanged(KernelCore& kernel, KThread* thread, ThreadState old_state);
+
+ /// Notify the scheduler a thread's priority has changed.
+ static void OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s32 old_priority);
+
+ /// Notify the scheduler a thread's core and/or affinity mask has changed.
+ static void OnThreadAffinityMaskChanged(KernelCore& kernel, KThread* thread,
+ const KAffinityMask& old_affinity, s32 old_core);
+
+ static bool CanSchedule(KernelCore& kernel);
+ static bool IsSchedulerUpdateNeeded(const KernelCore& kernel);
+ static void SetSchedulerUpdateNeeded(KernelCore& kernel);
+ static void ClearSchedulerUpdateNeeded(KernelCore& kernel);
+ static void DisableScheduling(KernelCore& kernel);
+ static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling);
+ [[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
+
+private:
+ friend class GlobalSchedulerContext;
+
+ /**
+ * Takes care of selecting the new scheduled threads in three steps:
+ *
+ * 1. First a thread is selected from the top of the priority queue. If no thread
+ * is obtained then we move to step two, else we are done.
+ *
+ * 2. Second we try to get a suggested thread that's not assigned to any core or
+ * that is not the top thread in that core.
+ *
+ * 3. Third is no suggested thread is found, we do a second pass and pick a running
+ * thread in another core and swap it with its current thread.
+ *
+ * returns the cores needing scheduling.
+ */
+ [[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
+
+ [[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel);
+
+ void RotateScheduledQueue(s32 core_id, s32 priority);
+
+ void Schedule() {
+ ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1);
+ this->ScheduleImpl();
+ }
+
+ /// Switches the CPU's active thread context to that of the specified thread
+ void ScheduleImpl();
+
+ /// When a thread wakes up, it must run this through it's new scheduler
+ void SwitchContextStep2();
+
+ /**
+ * Called on every context switch to update the internal timestamp
+ * This also updates the running time ticks for the given thread and
+ * process using the following difference:
+ *
+ * ticks += most_recent_ticks - last_context_switch_ticks
+ *
+ * The internal tick timestamp for the scheduler is simply the
+ * most recent tick count retrieved. No special arithmetic is
+ * applied to it.
+ */
+ void UpdateLastContextSwitchTime(KThread* thread, Process* process);
+
+ static void OnSwitch(void* this_scheduler);
+ void SwitchToCurrent();
+
+ KThread* prev_thread{};
+ std::atomic<KThread*> current_thread{};
+
+ KThread* idle_thread;
+
+ std::shared_ptr<Common::Fiber> switch_fiber{};
+
+ struct SchedulingState {
+ std::atomic<bool> needs_scheduling;
+ bool interrupt_task_thread_runnable{};
+ bool should_count_idle{};
+ u64 idle_count{};
+ KThread* highest_priority_thread{};
+ void* idle_thread_stack{};
+ };
+
+ SchedulingState state;
+
+ Core::System& system;
+ u64 last_context_switch_time{};
+ const s32 core_id;
+
+ Common::SpinLock guard{};
+};
+
+class KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> {
+public:
+ explicit KScopedSchedulerLock(KernelCore& kernel);
+ ~KScopedSchedulerLock();
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h
new file mode 100644
index 000000000..169455d18
--- /dev/null
+++ b/src/core/hle/kernel/k_scheduler_lock.h
@@ -0,0 +1,75 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/spin_lock.h"
+#include "core/hardware_properties.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+
+namespace Kernel {
+
+class KernelCore;
+
+template <typename SchedulerType>
+class KAbstractSchedulerLock {
+public:
+ explicit KAbstractSchedulerLock(KernelCore& kernel_) : kernel{kernel_} {}
+
+ bool IsLockedByCurrentThread() const {
+ return owner_thread == GetCurrentThreadPointer(kernel);
+ }
+
+ void Lock() {
+ if (IsLockedByCurrentThread()) {
+ // If we already own the lock, we can just increment the count.
+ ASSERT(lock_count > 0);
+ lock_count++;
+ } else {
+ // Otherwise, we want to disable scheduling and acquire the spinlock.
+ SchedulerType::DisableScheduling(kernel);
+ spin_lock.lock();
+
+ // For debug, ensure that our state is valid.
+ ASSERT(lock_count == 0);
+ ASSERT(owner_thread == nullptr);
+
+ // Increment count, take ownership.
+ lock_count = 1;
+ owner_thread = GetCurrentThreadPointer(kernel);
+ }
+ }
+
+ void Unlock() {
+ ASSERT(IsLockedByCurrentThread());
+ ASSERT(lock_count > 0);
+
+ // Release an instance of the lock.
+ if ((--lock_count) == 0) {
+ // We're no longer going to hold the lock. Take note of what cores need scheduling.
+ const u64 cores_needing_scheduling =
+ SchedulerType::UpdateHighestPriorityThreads(kernel);
+
+ // Note that we no longer hold the lock, and unlock the spinlock.
+ owner_thread = nullptr;
+ spin_lock.unlock();
+
+ // Enable scheduling, and perform a rescheduling operation.
+ SchedulerType::EnableScheduling(kernel, cores_needing_scheduling);
+ }
+ }
+
+private:
+ KernelCore& kernel;
+ Common::SpinLock spin_lock{};
+ s32 lock_count{};
+ KThread* owner_thread{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scoped_lock.h b/src/core/hle/kernel/k_scoped_lock.h
new file mode 100644
index 000000000..d7cc557b2
--- /dev/null
+++ b/src/core/hle/kernel/k_scoped_lock.h
@@ -0,0 +1,41 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Kernel {
+
+template <typename T>
+concept KLockable = !std::is_reference_v<T> && requires(T & t) {
+ { t.Lock() }
+ ->std::same_as<void>;
+ { t.Unlock() }
+ ->std::same_as<void>;
+};
+
+template <typename T>
+requires KLockable<T> class KScopedLock {
+public:
+ explicit KScopedLock(T* l) : lock_ptr(l) {
+ this->lock_ptr->Lock();
+ }
+ explicit KScopedLock(T& l) : KScopedLock(std::addressof(l)) { /* ... */
+ }
+ ~KScopedLock() {
+ this->lock_ptr->Unlock();
+ }
+
+ KScopedLock(const KScopedLock&) = delete;
+ KScopedLock(KScopedLock&&) = delete;
+
+private:
+ T* lock_ptr;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scoped_resource_reservation.h b/src/core/hle/kernel/k_scoped_resource_reservation.h
new file mode 100644
index 000000000..c5deca00b
--- /dev/null
+++ b/src/core/hle/kernel/k_scoped_resource_reservation.h
@@ -0,0 +1,67 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_resource_limit.h"
+#include "core/hle/kernel/process.h"
+
+namespace Kernel {
+
+class KScopedResourceReservation {
+public:
+ explicit KScopedResourceReservation(std::shared_ptr<KResourceLimit> l, LimitableResource r,
+ s64 v, s64 timeout)
+ : resource_limit(std::move(l)), value(v), resource(r) {
+ if (resource_limit && value) {
+ success = resource_limit->Reserve(resource, value, timeout);
+ } else {
+ success = true;
+ }
+ }
+
+ explicit KScopedResourceReservation(std::shared_ptr<KResourceLimit> l, LimitableResource r,
+ s64 v = 1)
+ : resource_limit(std::move(l)), value(v), resource(r) {
+ if (resource_limit && value) {
+ success = resource_limit->Reserve(resource, value);
+ } else {
+ success = true;
+ }
+ }
+
+ explicit KScopedResourceReservation(const Process* p, LimitableResource r, s64 v, s64 t)
+ : KScopedResourceReservation(p->GetResourceLimit(), r, v, t) {}
+
+ explicit KScopedResourceReservation(const Process* p, LimitableResource r, s64 v = 1)
+ : KScopedResourceReservation(p->GetResourceLimit(), r, v) {}
+
+ ~KScopedResourceReservation() noexcept {
+ if (resource_limit && value && success) {
+ // resource was not committed, release the reservation.
+ resource_limit->Release(resource, value);
+ }
+ }
+
+ /// Commit the resource reservation, destruction of this object does not release the resource
+ void Commit() {
+ resource_limit = nullptr;
+ }
+
+ [[nodiscard]] bool Succeeded() const {
+ return success;
+ }
+
+private:
+ std::shared_ptr<KResourceLimit> resource_limit;
+ s64 value;
+ LimitableResource resource;
+ bool success;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
new file mode 100644
index 000000000..f8189e107
--- /dev/null
+++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
@@ -0,0 +1,46 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This file references various implementation details from Atmosphere, an open-source firmware for
+// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/time_manager.h"
+
+namespace Kernel {
+
+class KScopedSchedulerLockAndSleep {
+public:
+ explicit KScopedSchedulerLockAndSleep(KernelCore& kernel, KThread* t, s64 timeout)
+ : kernel(kernel), thread(t), timeout_tick(timeout) {
+ // Lock the scheduler.
+ kernel.GlobalSchedulerContext().scheduler_lock.Lock();
+ }
+
+ ~KScopedSchedulerLockAndSleep() {
+ // Register the sleep.
+ if (timeout_tick > 0) {
+ kernel.TimeManager().ScheduleTimeEvent(thread, timeout_tick);
+ }
+
+ // Unlock the scheduler.
+ kernel.GlobalSchedulerContext().scheduler_lock.Unlock();
+ }
+
+ void CancelSleep() {
+ timeout_tick = 0;
+ }
+
+private:
+ KernelCore& kernel;
+ KThread* thread{};
+ s64 timeout_tick{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_synchronization_object.cpp b/src/core/hle/kernel/k_synchronization_object.cpp
new file mode 100644
index 000000000..82f72a0fe
--- /dev/null
+++ b/src/core/hle/kernel/k_synchronization_object.cpp
@@ -0,0 +1,171 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/svc_results.h"
+
+namespace Kernel {
+
+ResultCode KSynchronizationObject::Wait(KernelCore& kernel, s32* out_index,
+ KSynchronizationObject** objects, const s32 num_objects,
+ s64 timeout) {
+ // Allocate space on stack for thread nodes.
+ std::vector<ThreadListNode> thread_nodes(num_objects);
+
+ // Prepare for wait.
+ KThread* thread = kernel.CurrentScheduler()->GetCurrentThread();
+
+ {
+ // Setup the scheduling lock and sleep.
+ KScopedSchedulerLockAndSleep slp{kernel, thread, timeout};
+
+ // Check if any of the objects are already signaled.
+ for (auto i = 0; i < num_objects; ++i) {
+ ASSERT(objects[i] != nullptr);
+
+ if (objects[i]->IsSignaled()) {
+ *out_index = i;
+ slp.CancelSleep();
+ return RESULT_SUCCESS;
+ }
+ }
+
+ // Check if the timeout is zero.
+ if (timeout == 0) {
+ slp.CancelSleep();
+ return ResultTimedOut;
+ }
+
+ // Check if the thread should terminate.
+ if (thread->IsTerminationRequested()) {
+ slp.CancelSleep();
+ return ResultTerminationRequested;
+ }
+
+ // Check if waiting was canceled.
+ if (thread->IsWaitCancelled()) {
+ slp.CancelSleep();
+ thread->ClearWaitCancelled();
+ return ResultCancelled;
+ }
+
+ // Add the waiters.
+ for (auto i = 0; i < num_objects; ++i) {
+ thread_nodes[i].thread = thread;
+ thread_nodes[i].next = nullptr;
+
+ if (objects[i]->thread_list_tail == nullptr) {
+ objects[i]->thread_list_head = std::addressof(thread_nodes[i]);
+ } else {
+ objects[i]->thread_list_tail->next = std::addressof(thread_nodes[i]);
+ }
+
+ objects[i]->thread_list_tail = std::addressof(thread_nodes[i]);
+ }
+
+ // For debugging only
+ thread->SetWaitObjectsForDebugging({objects, static_cast<std::size_t>(num_objects)});
+
+ // Mark the thread as waiting.
+ thread->SetCancellable();
+ thread->SetSyncedObject(nullptr, ResultTimedOut);
+ thread->SetState(ThreadState::Waiting);
+ thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Synchronization);
+ }
+
+ // The lock/sleep is done, so we should be able to get our result.
+
+ // Thread is no longer cancellable.
+ thread->ClearCancellable();
+
+ // For debugging only
+ thread->SetWaitObjectsForDebugging({});
+
+ // Cancel the timer as needed.
+ kernel.TimeManager().UnscheduleTimeEvent(thread);
+
+ // Get the wait result.
+ ResultCode wait_result{RESULT_SUCCESS};
+ s32 sync_index = -1;
+ {
+ KScopedSchedulerLock lock(kernel);
+ KSynchronizationObject* synced_obj;
+ wait_result = thread->GetWaitResult(std::addressof(synced_obj));
+
+ for (auto i = 0; i < num_objects; ++i) {
+ // Unlink the object from the list.
+ ThreadListNode* prev_ptr =
+ reinterpret_cast<ThreadListNode*>(std::addressof(objects[i]->thread_list_head));
+ ThreadListNode* prev_val = nullptr;
+ ThreadListNode *prev, *tail_prev;
+
+ do {
+ prev = prev_ptr;
+ prev_ptr = prev_ptr->next;
+ tail_prev = prev_val;
+ prev_val = prev_ptr;
+ } while (prev_ptr != std::addressof(thread_nodes[i]));
+
+ if (objects[i]->thread_list_tail == std::addressof(thread_nodes[i])) {
+ objects[i]->thread_list_tail = tail_prev;
+ }
+
+ prev->next = thread_nodes[i].next;
+
+ if (objects[i] == synced_obj) {
+ sync_index = i;
+ }
+ }
+ }
+
+ // Set output.
+ *out_index = sync_index;
+ return wait_result;
+}
+
+KSynchronizationObject::KSynchronizationObject(KernelCore& kernel) : Object{kernel} {}
+
+KSynchronizationObject::KSynchronizationObject(KernelCore& kernel, std::string&& name)
+ : Object{kernel, std::move(name)} {}
+
+KSynchronizationObject::~KSynchronizationObject() = default;
+
+void KSynchronizationObject::NotifyAvailable(ResultCode result) {
+ KScopedSchedulerLock lock(kernel);
+
+ // If we're not signaled, we've nothing to notify.
+ if (!this->IsSignaled()) {
+ return;
+ }
+
+ // Iterate over each thread.
+ for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) {
+ KThread* thread = cur_node->thread;
+ if (thread->GetState() == ThreadState::Waiting) {
+ thread->SetSyncedObject(this, result);
+ thread->SetState(ThreadState::Runnable);
+ }
+ }
+}
+
+std::vector<KThread*> KSynchronizationObject::GetWaitingThreadsForDebugging() const {
+ std::vector<KThread*> threads;
+
+ // If debugging, dump the list of waiters.
+ {
+ KScopedSchedulerLock lock(kernel);
+ for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) {
+ threads.emplace_back(cur_node->thread);
+ }
+ }
+
+ return threads;
+}
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_synchronization_object.h b/src/core/hle/kernel/k_synchronization_object.h
new file mode 100644
index 000000000..5803718fd
--- /dev/null
+++ b/src/core/hle/kernel/k_synchronization_object.h
@@ -0,0 +1,59 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "core/hle/kernel/object.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+class KernelCore;
+class Synchronization;
+class KThread;
+
+/// Class that represents a Kernel object that a thread can be waiting on
+class KSynchronizationObject : public Object {
+public:
+ struct ThreadListNode {
+ ThreadListNode* next{};
+ KThread* thread{};
+ };
+
+ [[nodiscard]] static ResultCode Wait(KernelCore& kernel, s32* out_index,
+ KSynchronizationObject** objects, const s32 num_objects,
+ s64 timeout);
+
+ [[nodiscard]] virtual bool IsSignaled() const = 0;
+
+ [[nodiscard]] std::vector<KThread*> GetWaitingThreadsForDebugging() const;
+
+protected:
+ explicit KSynchronizationObject(KernelCore& kernel);
+ explicit KSynchronizationObject(KernelCore& kernel, std::string&& name);
+ virtual ~KSynchronizationObject();
+
+ void NotifyAvailable(ResultCode result);
+ void NotifyAvailable() {
+ return this->NotifyAvailable(RESULT_SUCCESS);
+ }
+
+private:
+ ThreadListNode* thread_list_head{};
+ ThreadListNode* thread_list_tail{};
+};
+
+// Specialization of DynamicObjectCast for KSynchronizationObjects
+template <>
+inline std::shared_ptr<KSynchronizationObject> DynamicObjectCast<KSynchronizationObject>(
+ std::shared_ptr<Object> object) {
+ if (object != nullptr && object->IsWaitable()) {
+ return std::static_pointer_cast<KSynchronizationObject>(object);
+ }
+ return nullptr;
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
new file mode 100644
index 000000000..e5620da5a
--- /dev/null
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -0,0 +1,1048 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cinttypes>
+#include <optional>
+#include <vector>
+
+#include "common/assert.h"
+#include "common/bit_util.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/fiber.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "common/thread_queue_list.h"
+#include "core/core.h"
+#include "core/cpu_manager.h"
+#include "core/hardware_properties.h"
+#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/k_condition_variable.h"
+#include "core/hle/kernel/k_resource_limit.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_thread_queue.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory/memory_layout.h"
+#include "core/hle/kernel/object.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/svc_results.h"
+#include "core/hle/kernel/time_manager.h"
+#include "core/hle/result.h"
+#include "core/memory.h"
+
+#ifdef ARCHITECTURE_x86_64
+#include "core/arm/dynarmic/arm_dynarmic_32.h"
+#include "core/arm/dynarmic/arm_dynarmic_64.h"
+#endif
+
+namespace {
+static void ResetThreadContext32(Core::ARM_Interface::ThreadContext32& context, u32 stack_top,
+ u32 entry_point, u32 arg) {
+ context = {};
+ context.cpu_registers[0] = arg;
+ context.cpu_registers[15] = entry_point;
+ context.cpu_registers[13] = stack_top;
+}
+
+static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context, VAddr stack_top,
+ VAddr entry_point, u64 arg) {
+ context = {};
+ context.cpu_registers[0] = arg;
+ context.pc = entry_point;
+ context.sp = stack_top;
+ // TODO(merry): Perform a hardware test to determine the below value.
+ context.fpcr = 0;
+}
+} // namespace
+
+namespace Kernel {
+
+KThread::KThread(KernelCore& kernel)
+ : KSynchronizationObject{kernel}, activity_pause_lock{kernel} {}
+KThread::~KThread() = default;
+
+ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack_top, s32 prio,
+ s32 virt_core, Process* owner, ThreadType type) {
+ // Assert parameters are valid.
+ ASSERT((type == ThreadType::Main) ||
+ (Svc::HighestThreadPriority <= prio && prio <= Svc::LowestThreadPriority));
+ ASSERT((owner != nullptr) || (type != ThreadType::User));
+ ASSERT(0 <= virt_core && virt_core < static_cast<s32>(Common::BitSize<u64>()));
+
+ // Convert the virtual core to a physical core.
+ const s32 phys_core = Core::Hardware::VirtualToPhysicalCoreMap[virt_core];
+ ASSERT(0 <= phys_core && phys_core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+
+ // First, clear the TLS address.
+ tls_address = {};
+
+ // Next, assert things based on the type.
+ switch (type) {
+ case ThreadType::Main:
+ ASSERT(arg == 0);
+ [[fallthrough]];
+ case ThreadType::HighPriority:
+ [[fallthrough]];
+ case ThreadType::User:
+ ASSERT(((owner == nullptr) ||
+ (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
+ ASSERT(((owner == nullptr) ||
+ (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask()));
+ break;
+ case ThreadType::Kernel:
+ UNIMPLEMENTED();
+ break;
+ default:
+ UNREACHABLE_MSG("KThread::Initialize: Unknown ThreadType {}", static_cast<u32>(type));
+ break;
+ }
+ thread_type_for_debugging = type;
+
+ // Set the ideal core ID and affinity mask.
+ virtual_ideal_core_id = virt_core;
+ physical_ideal_core_id = phys_core;
+ virtual_affinity_mask = 1ULL << virt_core;
+ physical_affinity_mask.SetAffinity(phys_core, true);
+
+ // Set the thread state.
+ thread_state = (type == ThreadType::Main) ? ThreadState::Runnable : ThreadState::Initialized;
+
+ // Set TLS address.
+ tls_address = 0;
+
+ // Set parent and condvar tree.
+ parent = nullptr;
+ condvar_tree = nullptr;
+
+ // Set sync booleans.
+ signaled = false;
+ termination_requested = false;
+ wait_cancelled = false;
+ cancellable = false;
+
+ // Set core ID and wait result.
+ core_id = phys_core;
+ wait_result = ResultNoSynchronizationObject;
+
+ // Set priorities.
+ priority = prio;
+ base_priority = prio;
+
+ // Set sync object and waiting lock to null.
+ synced_object = nullptr;
+
+ // Initialize sleeping queue.
+ sleeping_queue = nullptr;
+
+ // Set suspend flags.
+ suspend_request_flags = 0;
+ suspend_allowed_flags = static_cast<u32>(ThreadState::SuspendFlagMask);
+
+ // We're neither debug attached, nor are we nesting our priority inheritance.
+ debug_attached = false;
+ priority_inheritance_count = 0;
+
+ // We haven't been scheduled, and we have done no light IPC.
+ schedule_count = -1;
+ last_scheduled_tick = 0;
+ light_ipc_data = nullptr;
+
+ // We're not waiting for a lock, and we haven't disabled migration.
+ lock_owner = nullptr;
+ num_core_migration_disables = 0;
+
+ // We have no waiters, but we do have an entrypoint.
+ num_kernel_waiters = 0;
+
+ // Set our current core id.
+ current_core_id = phys_core;
+
+ // We haven't released our resource limit hint, and we've spent no time on the cpu.
+ resource_limit_release_hint = false;
+ cpu_time = 0;
+
+ // Clear our stack parameters.
+ std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0,
+ sizeof(StackParameters));
+
+ // Setup the TLS, if needed.
+ if (type == ThreadType::User) {
+ tls_address = owner->CreateTLSRegion();
+ }
+
+ // Set parent, if relevant.
+ if (owner != nullptr) {
+ parent = owner;
+ parent->IncrementThreadCount();
+ }
+
+ // Initialize thread context.
+ ResetThreadContext64(thread_context_64, user_stack_top, func, arg);
+ ResetThreadContext32(thread_context_32, static_cast<u32>(user_stack_top),
+ static_cast<u32>(func), static_cast<u32>(arg));
+
+ // Setup the stack parameters.
+ StackParameters& sp = GetStackParameters();
+ sp.cur_thread = this;
+ sp.disable_count = 1;
+ SetInExceptionHandler();
+
+ // Set thread ID.
+ thread_id = kernel.CreateNewThreadID();
+
+ // We initialized!
+ initialized = true;
+
+ // Register ourselves with our parent process.
+ if (parent != nullptr) {
+ parent->RegisterThread(this);
+ if (parent->IsSuspended()) {
+ RequestSuspend(SuspendType::Process);
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg,
+ VAddr user_stack_top, s32 prio, s32 core, Process* owner,
+ ThreadType type) {
+ // Initialize the thread.
+ R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type));
+
+ return RESULT_SUCCESS;
+}
+
+void KThread::Finalize() {
+ // If the thread has an owner process, unregister it.
+ if (parent != nullptr) {
+ parent->UnregisterThread(this);
+ }
+
+ // If the thread has a local region, delete it.
+ if (tls_address != 0) {
+ parent->FreeTLSRegion(tls_address);
+ }
+
+ // Release any waiters.
+ {
+ ASSERT(lock_owner == nullptr);
+ KScopedSchedulerLock sl{kernel};
+
+ auto it = waiter_list.begin();
+ while (it != waiter_list.end()) {
+ // The thread shouldn't be a kernel waiter.
+ it->SetLockOwner(nullptr);
+ it->SetSyncedObject(nullptr, ResultInvalidState);
+ it->Wakeup();
+ it = waiter_list.erase(it);
+ }
+ }
+
+ // Decrement the parent process's thread count.
+ if (parent != nullptr) {
+ parent->DecrementThreadCount();
+ parent->GetResourceLimit()->Release(LimitableResource::Threads, 1);
+ }
+}
+
+bool KThread::IsSignaled() const {
+ return signaled;
+}
+
+void KThread::Wakeup() {
+ KScopedSchedulerLock sl{kernel};
+
+ if (GetState() == ThreadState::Waiting) {
+ if (sleeping_queue != nullptr) {
+ sleeping_queue->WakeupThread(this);
+ } else {
+ SetState(ThreadState::Runnable);
+ }
+ }
+}
+
+void KThread::StartTermination() {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Release user exception and unpin, if relevant.
+ if (parent != nullptr) {
+ parent->ReleaseUserException(this);
+ if (parent->GetPinnedThread(GetCurrentCoreId(kernel)) == this) {
+ parent->UnpinCurrentThread();
+ }
+ }
+
+ // Set state to terminated.
+ SetState(ThreadState::Terminated);
+
+ // Clear the thread's status as running in parent.
+ if (parent != nullptr) {
+ parent->ClearRunningThread(this);
+ }
+
+ // Signal.
+ signaled = true;
+ NotifyAvailable();
+
+ // Clear previous thread in KScheduler.
+ KScheduler::ClearPreviousThread(kernel, this);
+
+ // Register terminated dpc flag.
+ RegisterDpc(DpcFlag::Terminated);
+}
+
+void KThread::Pin() {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Set ourselves as pinned.
+ GetStackParameters().is_pinned = true;
+
+ // Disable core migration.
+ ASSERT(num_core_migration_disables == 0);
+ {
+ ++num_core_migration_disables;
+
+ // Save our ideal state to restore when we're unpinned.
+ original_physical_ideal_core_id = physical_ideal_core_id;
+ original_physical_affinity_mask = physical_affinity_mask;
+
+ // Bind ourselves to this core.
+ const s32 active_core = GetActiveCore();
+ const s32 current_core = GetCurrentCoreId(kernel);
+
+ SetActiveCore(current_core);
+ physical_ideal_core_id = current_core;
+ physical_affinity_mask.SetAffinityMask(1ULL << current_core);
+
+ if (active_core != current_core || physical_affinity_mask.GetAffinityMask() !=
+ original_physical_affinity_mask.GetAffinityMask()) {
+ KScheduler::OnThreadAffinityMaskChanged(kernel, this, original_physical_affinity_mask,
+ active_core);
+ }
+ }
+
+ // Disallow performing thread suspension.
+ {
+ // Update our allow flags.
+ suspend_allowed_flags &= ~(1 << (static_cast<u32>(SuspendType::Thread) +
+ static_cast<u32>(ThreadState::SuspendShift)));
+
+ // Update our state.
+ const ThreadState old_state = thread_state;
+ thread_state = static_cast<ThreadState>(GetSuspendFlags() |
+ static_cast<u32>(old_state & ThreadState::Mask));
+ if (thread_state != old_state) {
+ KScheduler::OnThreadStateChanged(kernel, this, old_state);
+ }
+ }
+
+ // TODO(bunnei): Update our SVC access permissions.
+ ASSERT(parent != nullptr);
+}
+
+void KThread::Unpin() {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Set ourselves as unpinned.
+ GetStackParameters().is_pinned = false;
+
+ // Enable core migration.
+ ASSERT(num_core_migration_disables == 1);
+ {
+ num_core_migration_disables--;
+
+ // Restore our original state.
+ const KAffinityMask old_mask = physical_affinity_mask;
+
+ physical_ideal_core_id = original_physical_ideal_core_id;
+ physical_affinity_mask = original_physical_affinity_mask;
+
+ if (physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) {
+ const s32 active_core = GetActiveCore();
+
+ if (!physical_affinity_mask.GetAffinity(active_core)) {
+ if (physical_ideal_core_id >= 0) {
+ SetActiveCore(physical_ideal_core_id);
+ } else {
+ SetActiveCore(static_cast<s32>(
+ Common::BitSize<u64>() - 1 -
+ std::countl_zero(physical_affinity_mask.GetAffinityMask())));
+ }
+ }
+ KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_mask, active_core);
+ }
+ }
+
+ // Allow performing thread suspension (if termination hasn't been requested).
+ {
+ // Update our allow flags.
+ if (!IsTerminationRequested()) {
+ suspend_allowed_flags |= (1 << (static_cast<u32>(SuspendType::Thread) +
+ static_cast<u32>(ThreadState::SuspendShift)));
+ }
+
+ // Update our state.
+ const ThreadState old_state = thread_state;
+ thread_state = static_cast<ThreadState>(GetSuspendFlags() |
+ static_cast<u32>(old_state & ThreadState::Mask));
+ if (thread_state != old_state) {
+ KScheduler::OnThreadStateChanged(kernel, this, old_state);
+ }
+ }
+
+ // TODO(bunnei): Update our SVC access permissions.
+ ASSERT(parent != nullptr);
+
+ // Resume any threads that began waiting on us while we were pinned.
+ for (auto it = pinned_waiter_list.begin(); it != pinned_waiter_list.end(); ++it) {
+ if (it->GetState() == ThreadState::Waiting) {
+ it->SetState(ThreadState::Runnable);
+ }
+ }
+}
+
+ResultCode KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) {
+ KScopedSchedulerLock sl{kernel};
+
+ // Get the virtual mask.
+ *out_ideal_core = virtual_ideal_core_id;
+ *out_affinity_mask = virtual_affinity_mask;
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) {
+ KScopedSchedulerLock sl{kernel};
+ ASSERT(num_core_migration_disables >= 0);
+
+ // Select between core mask and original core mask.
+ if (num_core_migration_disables == 0) {
+ *out_ideal_core = physical_ideal_core_id;
+ *out_affinity_mask = physical_affinity_mask.GetAffinityMask();
+ } else {
+ *out_ideal_core = original_physical_ideal_core_id;
+ *out_affinity_mask = original_physical_affinity_mask.GetAffinityMask();
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode KThread::SetCoreMask(s32 core_id, u64 v_affinity_mask) {
+ ASSERT(parent != nullptr);
+ ASSERT(v_affinity_mask != 0);
+ KScopedLightLock lk{activity_pause_lock};
+
+ // Set the core mask.
+ u64 p_affinity_mask = 0;
+ {
+ KScopedSchedulerLock sl{kernel};
+ ASSERT(num_core_migration_disables >= 0);
+
+ // If the core id is no-update magic, preserve the ideal core id.
+ if (core_id == Svc::IdealCoreNoUpdate) {
+ core_id = virtual_ideal_core_id;
+ R_UNLESS(((1ULL << core_id) & v_affinity_mask) != 0, ResultInvalidCombination);
+ }
+
+ // Set the virtual core/affinity mask.
+ virtual_ideal_core_id = core_id;
+ virtual_affinity_mask = v_affinity_mask;
+
+ // Translate the virtual core to a physical core.
+ if (core_id >= 0) {
+ core_id = Core::Hardware::VirtualToPhysicalCoreMap[core_id];
+ }
+
+ // Translate the virtual affinity mask to a physical one.
+ while (v_affinity_mask != 0) {
+ const u64 next = std::countr_zero(v_affinity_mask);
+ v_affinity_mask &= ~(1ULL << next);
+ p_affinity_mask |= (1ULL << Core::Hardware::VirtualToPhysicalCoreMap[next]);
+ }
+
+ // If we haven't disabled migration, perform an affinity change.
+ if (num_core_migration_disables == 0) {
+ const KAffinityMask old_mask = physical_affinity_mask;
+
+ // Set our new ideals.
+ physical_ideal_core_id = core_id;
+ physical_affinity_mask.SetAffinityMask(p_affinity_mask);
+
+ if (physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) {
+ const s32 active_core = GetActiveCore();
+
+ if (active_core >= 0 && !physical_affinity_mask.GetAffinity(active_core)) {
+ const s32 new_core = static_cast<s32>(
+ physical_ideal_core_id >= 0
+ ? physical_ideal_core_id
+ : Common::BitSize<u64>() - 1 -
+ std::countl_zero(physical_affinity_mask.GetAffinityMask()));
+ SetActiveCore(new_core);
+ }
+ KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_mask, active_core);
+ }
+ } else {
+ // Otherwise, we edit the original affinity for restoration later.
+ original_physical_ideal_core_id = core_id;
+ original_physical_affinity_mask.SetAffinityMask(p_affinity_mask);
+ }
+ }
+
+ // Update the pinned waiter list.
+ {
+ bool retry_update{};
+ bool thread_is_pinned{};
+ do {
+ // Lock the scheduler.
+ KScopedSchedulerLock sl{kernel};
+
+ // Don't do any further management if our termination has been requested.
+ R_SUCCEED_IF(IsTerminationRequested());
+
+ // By default, we won't need to retry.
+ retry_update = false;
+
+ // Check if the thread is currently running.
+ bool thread_is_current{};
+ s32 thread_core;
+ for (thread_core = 0; thread_core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES);
+ ++thread_core) {
+ if (kernel.Scheduler(thread_core).GetCurrentThread() == this) {
+ thread_is_current = true;
+ break;
+ }
+ }
+
+ // If the thread is currently running, check whether it's no longer allowed under the
+ // new mask.
+ if (thread_is_current && ((1ULL << thread_core) & p_affinity_mask) == 0) {
+ // If the thread is pinned, we want to wait until it's not pinned.
+ if (GetStackParameters().is_pinned) {
+ // Verify that the current thread isn't terminating.
+ R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(),
+ ResultTerminationRequested);
+
+ // Note that the thread was pinned.
+ thread_is_pinned = true;
+
+ // Wait until the thread isn't pinned any more.
+ pinned_waiter_list.push_back(GetCurrentThread(kernel));
+ GetCurrentThread(kernel).SetState(ThreadState::Waiting);
+ } else {
+ // If the thread isn't pinned, release the scheduler lock and retry until it's
+ // not current.
+ retry_update = true;
+ }
+ }
+ } while (retry_update);
+
+ // If the thread was pinned, it no longer is, and we should remove the current thread from
+ // our waiter list.
+ if (thread_is_pinned) {
+ // Lock the scheduler.
+ KScopedSchedulerLock sl{kernel};
+
+ // Remove from the list.
+ pinned_waiter_list.erase(pinned_waiter_list.iterator_to(GetCurrentThread(kernel)));
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
+void KThread::SetBasePriority(s32 value) {
+ ASSERT(Svc::HighestThreadPriority <= value && value <= Svc::LowestThreadPriority);
+
+ KScopedSchedulerLock sl{kernel};
+
+ // Change our base priority.
+ base_priority = value;
+
+ // Perform a priority restoration.
+ RestorePriority(kernel, this);
+}
+
+void KThread::RequestSuspend(SuspendType type) {
+ KScopedSchedulerLock sl{kernel};
+
+ // Note the request in our flags.
+ suspend_request_flags |=
+ (1u << (static_cast<u32>(ThreadState::SuspendShift) + static_cast<u32>(type)));
+
+ // Try to perform the suspend.
+ TrySuspend();
+}
+
+void KThread::Resume(SuspendType type) {
+ KScopedSchedulerLock sl{kernel};
+
+ // Clear the request in our flags.
+ suspend_request_flags &=
+ ~(1u << (static_cast<u32>(ThreadState::SuspendShift) + static_cast<u32>(type)));
+
+ // Update our state.
+ const ThreadState old_state = thread_state;
+ thread_state = static_cast<ThreadState>(GetSuspendFlags() |
+ static_cast<u32>(old_state & ThreadState::Mask));
+ if (thread_state != old_state) {
+ KScheduler::OnThreadStateChanged(kernel, this, old_state);
+ }
+}
+
+void KThread::WaitCancel() {
+ KScopedSchedulerLock sl{kernel};
+
+ // Check if we're waiting and cancellable.
+ if (GetState() == ThreadState::Waiting && cancellable) {
+ if (sleeping_queue != nullptr) {
+ sleeping_queue->WakeupThread(this);
+ wait_cancelled = true;
+ } else {
+ SetSyncedObject(nullptr, ResultCancelled);
+ SetState(ThreadState::Runnable);
+ wait_cancelled = false;
+ }
+ } else {
+ // Otherwise, note that we cancelled a wait.
+ wait_cancelled = true;
+ }
+}
+
+void KThread::TrySuspend() {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ ASSERT(IsSuspendRequested());
+
+ // Ensure that we have no waiters.
+ if (GetNumKernelWaiters() > 0) {
+ return;
+ }
+ ASSERT(GetNumKernelWaiters() == 0);
+
+ // Perform the suspend.
+ Suspend();
+}
+
+void KThread::Suspend() {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ ASSERT(IsSuspendRequested());
+
+ // Set our suspend flags in state.
+ const auto old_state = thread_state;
+ thread_state = static_cast<ThreadState>(GetSuspendFlags()) | (old_state & ThreadState::Mask);
+
+ // Note the state change in scheduler.
+ KScheduler::OnThreadStateChanged(kernel, this, old_state);
+}
+
+void KThread::Continue() {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Clear our suspend flags in state.
+ const auto old_state = thread_state;
+ thread_state = old_state & ThreadState::Mask;
+
+ // Note the state change in scheduler.
+ KScheduler::OnThreadStateChanged(kernel, this, old_state);
+}
+
+ResultCode KThread::SetActivity(Svc::ThreadActivity activity) {
+ // Lock ourselves.
+ KScopedLightLock lk(activity_pause_lock);
+
+ // Set the activity.
+ {
+ // Lock the scheduler.
+ KScopedSchedulerLock sl{kernel};
+
+ // Verify our state.
+ const auto cur_state = GetState();
+ R_UNLESS((cur_state == ThreadState::Waiting || cur_state == ThreadState::Runnable),
+ ResultInvalidState);
+
+ // Either pause or resume.
+ if (activity == Svc::ThreadActivity::Paused) {
+ // Verify that we're not suspended.
+ R_UNLESS(!IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
+
+ // Suspend.
+ RequestSuspend(SuspendType::Thread);
+ } else {
+ ASSERT(activity == Svc::ThreadActivity::Runnable);
+
+ // Verify that we're suspended.
+ R_UNLESS(IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
+
+ // Resume.
+ Resume(SuspendType::Thread);
+ }
+ }
+
+ // If the thread is now paused, update the pinned waiter list.
+ if (activity == Svc::ThreadActivity::Paused) {
+ bool thread_is_pinned{};
+ bool thread_is_current{};
+ do {
+ // Lock the scheduler.
+ KScopedSchedulerLock sl{kernel};
+
+ // Don't do any further management if our termination has been requested.
+ R_SUCCEED_IF(IsTerminationRequested());
+
+ // Check whether the thread is pinned.
+ if (GetStackParameters().is_pinned) {
+ // Verify that the current thread isn't terminating.
+ R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(),
+ ResultTerminationRequested);
+
+ // Note that the thread was pinned and not current.
+ thread_is_pinned = true;
+ thread_is_current = false;
+
+ // Wait until the thread isn't pinned any more.
+ pinned_waiter_list.push_back(GetCurrentThread(kernel));
+ GetCurrentThread(kernel).SetState(ThreadState::Waiting);
+ } else {
+ // Check if the thread is currently running.
+ // If it is, we'll need to retry.
+ thread_is_current = false;
+
+ for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
+ if (kernel.Scheduler(i).GetCurrentThread() == this) {
+ thread_is_current = true;
+ break;
+ }
+ }
+ }
+ } while (thread_is_current);
+
+ // If the thread was pinned, it no longer is, and we should remove the current thread from
+ // our waiter list.
+ if (thread_is_pinned) {
+ // Lock the scheduler.
+ KScopedSchedulerLock sl{kernel};
+
+ // Remove from the list.
+ pinned_waiter_list.erase(pinned_waiter_list.iterator_to(GetCurrentThread(kernel)));
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode KThread::GetThreadContext3(std::vector<u8>& out) {
+ // Lock ourselves.
+ KScopedLightLock lk{activity_pause_lock};
+
+ // Get the context.
+ {
+ // Lock the scheduler.
+ KScopedSchedulerLock sl{kernel};
+
+ // Verify that we're suspended.
+ R_UNLESS(IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
+
+ // If we're not terminating, get the thread's user context.
+ if (!IsTerminationRequested()) {
+ if (parent->Is64BitProcess()) {
+ // Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
+ auto context = GetContext64();
+ context.pstate &= 0xFF0FFE20;
+
+ out.resize(sizeof(context));
+ std::memcpy(out.data(), &context, sizeof(context));
+ } else {
+ // Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
+ auto context = GetContext32();
+ context.cpsr &= 0xFF0FFE20;
+
+ out.resize(sizeof(context));
+ std::memcpy(out.data(), &context, sizeof(context));
+ }
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
+void KThread::AddWaiterImpl(KThread* thread) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Find the right spot to insert the waiter.
+ auto it = waiter_list.begin();
+ while (it != waiter_list.end()) {
+ if (it->GetPriority() > thread->GetPriority()) {
+ break;
+ }
+ it++;
+ }
+
+ // Keep track of how many kernel waiters we have.
+ if (Memory::IsKernelAddressKey(thread->GetAddressKey())) {
+ ASSERT((num_kernel_waiters++) >= 0);
+ }
+
+ // Insert the waiter.
+ waiter_list.insert(it, *thread);
+ thread->SetLockOwner(this);
+}
+
+void KThread::RemoveWaiterImpl(KThread* thread) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Keep track of how many kernel waiters we have.
+ if (Memory::IsKernelAddressKey(thread->GetAddressKey())) {
+ ASSERT((num_kernel_waiters--) > 0);
+ }
+
+ // Remove the waiter.
+ waiter_list.erase(waiter_list.iterator_to(*thread));
+ thread->SetLockOwner(nullptr);
+}
+
+void KThread::RestorePriority(KernelCore& kernel, KThread* thread) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ while (true) {
+ // We want to inherit priority where possible.
+ s32 new_priority = thread->GetBasePriority();
+ if (thread->HasWaiters()) {
+ new_priority = std::min(new_priority, thread->waiter_list.front().GetPriority());
+ }
+
+ // If the priority we would inherit is not different from ours, don't do anything.
+ if (new_priority == thread->GetPriority()) {
+ return;
+ }
+
+ // Ensure we don't violate condition variable red black tree invariants.
+ if (auto* cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) {
+ BeforeUpdatePriority(kernel, cv_tree, thread);
+ }
+
+ // Change the priority.
+ const s32 old_priority = thread->GetPriority();
+ thread->SetPriority(new_priority);
+
+ // Restore the condition variable, if relevant.
+ if (auto* cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) {
+ AfterUpdatePriority(kernel, cv_tree, thread);
+ }
+
+ // Update the scheduler.
+ KScheduler::OnThreadPriorityChanged(kernel, thread, old_priority);
+
+ // Keep the lock owner up to date.
+ KThread* lock_owner = thread->GetLockOwner();
+ if (lock_owner == nullptr) {
+ return;
+ }
+
+ // Update the thread in the lock owner's sorted list, and continue inheriting.
+ lock_owner->RemoveWaiterImpl(thread);
+ lock_owner->AddWaiterImpl(thread);
+ thread = lock_owner;
+ }
+}
+
+void KThread::AddWaiter(KThread* thread) {
+ AddWaiterImpl(thread);
+ RestorePriority(kernel, this);
+}
+
+void KThread::RemoveWaiter(KThread* thread) {
+ RemoveWaiterImpl(thread);
+ RestorePriority(kernel, this);
+}
+
+KThread* KThread::RemoveWaiterByKey(s32* out_num_waiters, VAddr key) {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ s32 num_waiters{};
+ KThread* next_lock_owner{};
+ auto it = waiter_list.begin();
+ while (it != waiter_list.end()) {
+ if (it->GetAddressKey() == key) {
+ KThread* thread = std::addressof(*it);
+
+ // Keep track of how many kernel waiters we have.
+ if (Memory::IsKernelAddressKey(thread->GetAddressKey())) {
+ ASSERT((num_kernel_waiters--) > 0);
+ }
+ it = waiter_list.erase(it);
+
+ // Update the next lock owner.
+ if (next_lock_owner == nullptr) {
+ next_lock_owner = thread;
+ next_lock_owner->SetLockOwner(nullptr);
+ } else {
+ next_lock_owner->AddWaiterImpl(thread);
+ }
+ num_waiters++;
+ } else {
+ it++;
+ }
+ }
+
+ // Do priority updates, if we have a next owner.
+ if (next_lock_owner) {
+ RestorePriority(kernel, this);
+ RestorePriority(kernel, next_lock_owner);
+ }
+
+ // Return output.
+ *out_num_waiters = num_waiters;
+ return next_lock_owner;
+}
+
+ResultCode KThread::Run() {
+ while (true) {
+ KScopedSchedulerLock lk{kernel};
+
+ // If either this thread or the current thread are requesting termination, note it.
+ R_UNLESS(!IsTerminationRequested(), ResultTerminationRequested);
+ R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested);
+
+ // Ensure our thread state is correct.
+ R_UNLESS(GetState() == ThreadState::Initialized, ResultInvalidState);
+
+ // If the current thread has been asked to suspend, suspend it and retry.
+ if (GetCurrentThread(kernel).IsSuspended()) {
+ GetCurrentThread(kernel).Suspend();
+ continue;
+ }
+
+ // If we're not a kernel thread and we've been asked to suspend, suspend ourselves.
+ if (IsUserThread() && IsSuspended()) {
+ Suspend();
+ }
+
+ // Set our state and finish.
+ SetState(ThreadState::Runnable);
+ return RESULT_SUCCESS;
+ }
+}
+
+void KThread::Exit() {
+ ASSERT(this == GetCurrentThreadPointer(kernel));
+
+ // Release the thread resource hint from parent.
+ if (parent != nullptr) {
+ // TODO(bunnei): Hint that the resource is about to be released.
+ resource_limit_release_hint = true;
+ }
+
+ // Perform termination.
+ {
+ KScopedSchedulerLock sl{kernel};
+
+ // Disallow all suspension.
+ suspend_allowed_flags = 0;
+
+ // Start termination.
+ StartTermination();
+ }
+}
+
+ResultCode KThread::Sleep(s64 timeout) {
+ ASSERT(!kernel.GlobalSchedulerContext().IsLocked());
+ ASSERT(this == GetCurrentThreadPointer(kernel));
+ ASSERT(timeout > 0);
+
+ {
+ // Setup the scheduling lock and sleep.
+ KScopedSchedulerLockAndSleep slp{kernel, this, timeout};
+
+ // Check if the thread should terminate.
+ if (IsTerminationRequested()) {
+ slp.CancelSleep();
+ return ResultTerminationRequested;
+ }
+
+ // Mark the thread as waiting.
+ SetState(ThreadState::Waiting);
+ SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep);
+ }
+
+ // The lock/sleep is done.
+
+ // Cancel the timer.
+ kernel.TimeManager().UnscheduleTimeEvent(this);
+
+ return RESULT_SUCCESS;
+}
+
+void KThread::SetState(ThreadState state) {
+ KScopedSchedulerLock sl{kernel};
+
+ // Clear debugging state
+ SetMutexWaitAddressForDebugging({});
+ SetWaitReasonForDebugging({});
+
+ const ThreadState old_state = thread_state;
+ thread_state =
+ static_cast<ThreadState>((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask));
+ if (thread_state != old_state) {
+ KScheduler::OnThreadStateChanged(kernel, this, old_state);
+ }
+}
+
+std::shared_ptr<Common::Fiber>& KThread::GetHostContext() {
+ return host_context;
+}
+
+ResultVal<std::shared_ptr<KThread>> KThread::Create(Core::System& system, ThreadType type_flags,
+ std::string name, VAddr entry_point,
+ u32 priority, u64 arg, s32 processor_id,
+ VAddr stack_top, Process* owner_process) {
+ std::function<void(void*)> init_func = Core::CpuManager::GetGuestThreadStartFunc();
+ void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
+ return Create(system, type_flags, name, entry_point, priority, arg, processor_id, stack_top,
+ owner_process, std::move(init_func), init_func_parameter);
+}
+
+ResultVal<std::shared_ptr<KThread>> KThread::Create(Core::System& system, ThreadType type_flags,
+ std::string name, VAddr entry_point,
+ u32 priority, u64 arg, s32 processor_id,
+ VAddr stack_top, Process* owner_process,
+ std::function<void(void*)>&& thread_start_func,
+ void* thread_start_parameter) {
+ auto& kernel = system.Kernel();
+
+ std::shared_ptr<KThread> thread = std::make_shared<KThread>(kernel);
+
+ if (const auto result =
+ thread->InitializeThread(thread.get(), entry_point, arg, stack_top, priority,
+ processor_id, owner_process, type_flags);
+ result.IsError()) {
+ return result;
+ }
+
+ thread->name = name;
+
+ auto& scheduler = kernel.GlobalSchedulerContext();
+ scheduler.AddThread(thread);
+
+ thread->host_context =
+ std::make_shared<Common::Fiber>(std::move(thread_start_func), thread_start_parameter);
+
+ return MakeResult<std::shared_ptr<KThread>>(std::move(thread));
+}
+
+KThread* GetCurrentThreadPointer(KernelCore& kernel) {
+ return kernel.GetCurrentEmuThread();
+}
+
+KThread& GetCurrentThread(KernelCore& kernel) {
+ return *GetCurrentThreadPointer(kernel);
+}
+
+s32 GetCurrentCoreId(KernelCore& kernel) {
+ return GetCurrentThread(kernel).GetCurrentCore();
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
new file mode 100644
index 000000000..c8ac656a4
--- /dev/null
+++ b/src/core/hle/kernel/k_thread.h
@@ -0,0 +1,768 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <span>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/intrusive/list.hpp>
+
+#include "common/common_types.h"
+#include "common/intrusive_red_black_tree.h"
+#include "common/spin_lock.h"
+#include "core/arm/arm_interface.h"
+#include "core/hle/kernel/k_affinity_mask.h"
+#include "core/hle/kernel/k_light_lock.h"
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/object.h"
+#include "core/hle/kernel/svc_common.h"
+#include "core/hle/kernel/svc_types.h"
+#include "core/hle/result.h"
+
+namespace Common {
+class Fiber;
+}
+
+namespace Core {
+class ARM_Interface;
+class System;
+} // namespace Core
+
+namespace Kernel {
+
+class GlobalSchedulerContext;
+class KernelCore;
+class Process;
+class KScheduler;
+class KThreadQueue;
+
+using KThreadFunction = VAddr;
+
+enum class ThreadType : u32 {
+ Main = 0,
+ Kernel = 1,
+ HighPriority = 2,
+ User = 3,
+};
+DECLARE_ENUM_FLAG_OPERATORS(ThreadType);
+
+enum class SuspendType : u32 {
+ Process = 0,
+ Thread = 1,
+ Debug = 2,
+ Backtrace = 3,
+ Init = 4,
+
+ Count,
+};
+
+enum class ThreadState : u16 {
+ Initialized = 0,
+ Waiting = 1,
+ Runnable = 2,
+ Terminated = 3,
+
+ SuspendShift = 4,
+ Mask = (1 << SuspendShift) - 1,
+
+ ProcessSuspended = (1 << (0 + SuspendShift)),
+ ThreadSuspended = (1 << (1 + SuspendShift)),
+ DebugSuspended = (1 << (2 + SuspendShift)),
+ BacktraceSuspended = (1 << (3 + SuspendShift)),
+ InitSuspended = (1 << (4 + SuspendShift)),
+
+ SuspendFlagMask = ((1 << 5) - 1) << SuspendShift,
+};
+DECLARE_ENUM_FLAG_OPERATORS(ThreadState);
+
+enum class DpcFlag : u32 {
+ Terminating = (1 << 0),
+ Terminated = (1 << 1),
+};
+
+enum class ThreadWaitReasonForDebugging : u32 {
+ None, ///< Thread is not waiting
+ Sleep, ///< Thread is waiting due to a SleepThread SVC
+ IPC, ///< Thread is waiting for the reply from an IPC request
+ Synchronization, ///< Thread is waiting due to a WaitSynchronization SVC
+ ConditionVar, ///< Thread is waiting due to a WaitProcessWideKey SVC
+ Arbitration, ///< Thread is waiting due to a SignalToAddress/WaitForAddress SVC
+ Suspended, ///< Thread is waiting due to process suspension
+};
+
+[[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel);
+[[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel);
+[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel);
+
+class KThread final : public KSynchronizationObject, public boost::intrusive::list_base_hook<> {
+ friend class KScheduler;
+ friend class Process;
+
+public:
+ static constexpr s32 DefaultThreadPriority = 44;
+ static constexpr s32 IdleThreadPriority = Svc::LowestThreadPriority + 1;
+
+ explicit KThread(KernelCore& kernel);
+ ~KThread() override;
+
+public:
+ using ThreadContext32 = Core::ARM_Interface::ThreadContext32;
+ using ThreadContext64 = Core::ARM_Interface::ThreadContext64;
+ using WaiterList = boost::intrusive::list<KThread>;
+
+ /**
+ * Creates and returns a new thread. The new thread is immediately scheduled
+ * @param system The instance of the whole system
+ * @param name The friendly name desired for the thread
+ * @param entry_point The address at which the thread should start execution
+ * @param priority The thread's priority
+ * @param arg User data to pass to the thread
+ * @param processor_id The ID(s) of the processors on which the thread is desired to be run
+ * @param stack_top The address of the thread's stack top
+ * @param owner_process The parent process for the thread, if null, it's a kernel thread
+ * @return A shared pointer to the newly created thread
+ */
+ [[nodiscard]] static ResultVal<std::shared_ptr<KThread>> Create(
+ Core::System& system, ThreadType type_flags, std::string name, VAddr entry_point,
+ u32 priority, u64 arg, s32 processor_id, VAddr stack_top, Process* owner_process);
+
+ /**
+ * Creates and returns a new thread. The new thread is immediately scheduled
+ * @param system The instance of the whole system
+ * @param name The friendly name desired for the thread
+ * @param entry_point The address at which the thread should start execution
+ * @param priority The thread's priority
+ * @param arg User data to pass to the thread
+ * @param processor_id The ID(s) of the processors on which the thread is desired to be run
+ * @param stack_top The address of the thread's stack top
+ * @param owner_process The parent process for the thread, if null, it's a kernel thread
+ * @param thread_start_func The function where the host context will start.
+ * @param thread_start_parameter The parameter which will passed to host context on init
+ * @return A shared pointer to the newly created thread
+ */
+ [[nodiscard]] static ResultVal<std::shared_ptr<KThread>> Create(
+ Core::System& system, ThreadType type_flags, std::string name, VAddr entry_point,
+ u32 priority, u64 arg, s32 processor_id, VAddr stack_top, Process* owner_process,
+ std::function<void(void*)>&& thread_start_func, void* thread_start_parameter);
+
+ [[nodiscard]] std::string GetName() const override {
+ return name;
+ }
+
+ void SetName(std::string new_name) {
+ name = std::move(new_name);
+ }
+
+ [[nodiscard]] std::string GetTypeName() const override {
+ return "Thread";
+ }
+
+ static constexpr HandleType HANDLE_TYPE = HandleType::Thread;
+ [[nodiscard]] HandleType GetHandleType() const override {
+ return HANDLE_TYPE;
+ }
+
+ /**
+ * Gets the thread's current priority
+ * @return The current thread's priority
+ */
+ [[nodiscard]] s32 GetPriority() const {
+ return priority;
+ }
+
+ /**
+ * Sets the thread's current priority.
+ * @param priority The new priority.
+ */
+ void SetPriority(s32 value) {
+ priority = value;
+ }
+
+ /**
+ * Gets the thread's nominal priority.
+ * @return The current thread's nominal priority.
+ */
+ [[nodiscard]] s32 GetBasePriority() const {
+ return base_priority;
+ }
+
+ /**
+ * Gets the thread's thread ID
+ * @return The thread's ID
+ */
+ [[nodiscard]] u64 GetThreadID() const {
+ return thread_id;
+ }
+
+ void ContinueIfHasKernelWaiters() {
+ if (GetNumKernelWaiters() > 0) {
+ Continue();
+ }
+ }
+
+ void Wakeup();
+
+ void SetBasePriority(s32 value);
+
+ [[nodiscard]] ResultCode Run();
+
+ void Exit();
+
+ [[nodiscard]] u32 GetSuspendFlags() const {
+ return suspend_allowed_flags & suspend_request_flags;
+ }
+
+ [[nodiscard]] bool IsSuspended() const {
+ return GetSuspendFlags() != 0;
+ }
+
+ [[nodiscard]] bool IsSuspendRequested(SuspendType type) const {
+ return (suspend_request_flags &
+ (1u << (static_cast<u32>(ThreadState::SuspendShift) + static_cast<u32>(type)))) !=
+ 0;
+ }
+
+ [[nodiscard]] bool IsSuspendRequested() const {
+ return suspend_request_flags != 0;
+ }
+
+ void RequestSuspend(SuspendType type);
+
+ void Resume(SuspendType type);
+
+ void TrySuspend();
+
+ void Continue();
+
+ void Suspend();
+
+ void Finalize() override;
+
+ bool IsSignaled() const override;
+
+ void SetSyncedObject(KSynchronizationObject* obj, ResultCode wait_res) {
+ synced_object = obj;
+ wait_result = wait_res;
+ }
+
+ [[nodiscard]] ResultCode GetWaitResult(KSynchronizationObject** out) const {
+ *out = synced_object;
+ return wait_result;
+ }
+
+ /*
+ * Returns the Thread Local Storage address of the current thread
+ * @returns VAddr of the thread's TLS
+ */
+ [[nodiscard]] VAddr GetTLSAddress() const {
+ return tls_address;
+ }
+
+ /*
+ * Returns the value of the TPIDR_EL0 Read/Write system register for this thread.
+ * @returns The value of the TPIDR_EL0 register.
+ */
+ [[nodiscard]] u64 GetTPIDR_EL0() const {
+ return thread_context_64.tpidr;
+ }
+
+ /// Sets the value of the TPIDR_EL0 Read/Write system register for this thread.
+ void SetTPIDR_EL0(u64 value) {
+ thread_context_64.tpidr = value;
+ thread_context_32.tpidr = static_cast<u32>(value);
+ }
+
+ [[nodiscard]] ThreadContext32& GetContext32() {
+ return thread_context_32;
+ }
+
+ [[nodiscard]] const ThreadContext32& GetContext32() const {
+ return thread_context_32;
+ }
+
+ [[nodiscard]] ThreadContext64& GetContext64() {
+ return thread_context_64;
+ }
+
+ [[nodiscard]] const ThreadContext64& GetContext64() const {
+ return thread_context_64;
+ }
+
+ [[nodiscard]] std::shared_ptr<Common::Fiber>& GetHostContext();
+
+ [[nodiscard]] ThreadState GetState() const {
+ return thread_state & ThreadState::Mask;
+ }
+
+ [[nodiscard]] ThreadState GetRawState() const {
+ return thread_state;
+ }
+
+ void SetState(ThreadState state);
+
+ [[nodiscard]] s64 GetLastScheduledTick() const {
+ return last_scheduled_tick;
+ }
+
+ void SetLastScheduledTick(s64 tick) {
+ last_scheduled_tick = tick;
+ }
+
+ void AddCpuTime([[maybe_unused]] s32 core_id_, s64 amount) {
+ cpu_time += amount;
+ // TODO(bunnei): Debug kernels track per-core tick counts. Should we?
+ }
+
+ [[nodiscard]] s64 GetCpuTime() const {
+ return cpu_time;
+ }
+
+ [[nodiscard]] s32 GetActiveCore() const {
+ return core_id;
+ }
+
+ void SetActiveCore(s32 core) {
+ core_id = core;
+ }
+
+ [[nodiscard]] s32 GetCurrentCore() const {
+ return current_core_id;
+ }
+
+ void SetCurrentCore(s32 core) {
+ current_core_id = core;
+ }
+
+ [[nodiscard]] Process* GetOwnerProcess() {
+ return parent;
+ }
+
+ [[nodiscard]] const Process* GetOwnerProcess() const {
+ return parent;
+ }
+
+ [[nodiscard]] bool IsUserThread() const {
+ return parent != nullptr;
+ }
+
+ [[nodiscard]] KThread* GetLockOwner() const {
+ return lock_owner;
+ }
+
+ void SetLockOwner(KThread* owner) {
+ lock_owner = owner;
+ }
+
+ [[nodiscard]] const KAffinityMask& GetAffinityMask() const {
+ return physical_affinity_mask;
+ }
+
+ [[nodiscard]] ResultCode GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask);
+
+ [[nodiscard]] ResultCode GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask);
+
+ [[nodiscard]] ResultCode SetCoreMask(s32 core_id, u64 v_affinity_mask);
+
+ [[nodiscard]] ResultCode SetActivity(Svc::ThreadActivity activity);
+
+ [[nodiscard]] ResultCode Sleep(s64 timeout);
+
+ [[nodiscard]] s64 GetYieldScheduleCount() const {
+ return schedule_count;
+ }
+
+ void SetYieldScheduleCount(s64 count) {
+ schedule_count = count;
+ }
+
+ void WaitCancel();
+
+ [[nodiscard]] bool IsWaitCancelled() const {
+ return wait_cancelled;
+ }
+
+ [[nodiscard]] void ClearWaitCancelled() {
+ wait_cancelled = false;
+ }
+
+ [[nodiscard]] bool IsCancellable() const {
+ return cancellable;
+ }
+
+ void SetCancellable() {
+ cancellable = true;
+ }
+
+ void ClearCancellable() {
+ cancellable = false;
+ }
+
+ [[nodiscard]] bool IsTerminationRequested() const {
+ return termination_requested || GetRawState() == ThreadState::Terminated;
+ }
+
+ struct StackParameters {
+ u8 svc_permission[0x10];
+ std::atomic<u8> dpc_flags;
+ u8 current_svc_id;
+ bool is_calling_svc;
+ bool is_in_exception_handler;
+ bool is_pinned;
+ s32 disable_count;
+ KThread* cur_thread;
+ };
+
+ [[nodiscard]] StackParameters& GetStackParameters() {
+ return stack_parameters;
+ }
+
+ [[nodiscard]] const StackParameters& GetStackParameters() const {
+ return stack_parameters;
+ }
+
+ class QueueEntry {
+ public:
+ constexpr QueueEntry() = default;
+
+ constexpr void Initialize() {
+ prev = nullptr;
+ next = nullptr;
+ }
+
+ constexpr KThread* GetPrev() const {
+ return prev;
+ }
+ constexpr KThread* GetNext() const {
+ return next;
+ }
+ constexpr void SetPrev(KThread* thread) {
+ prev = thread;
+ }
+ constexpr void SetNext(KThread* thread) {
+ next = thread;
+ }
+
+ private:
+ KThread* prev{};
+ KThread* next{};
+ };
+
+ [[nodiscard]] QueueEntry& GetPriorityQueueEntry(s32 core) {
+ return per_core_priority_queue_entry[core];
+ }
+
+ [[nodiscard]] const QueueEntry& GetPriorityQueueEntry(s32 core) const {
+ return per_core_priority_queue_entry[core];
+ }
+
+ void SetSleepingQueue(KThreadQueue* q) {
+ sleeping_queue = q;
+ }
+
+ [[nodiscard]] s32 GetDisableDispatchCount() const {
+ return this->GetStackParameters().disable_count;
+ }
+
+ void DisableDispatch() {
+ ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
+ this->GetStackParameters().disable_count++;
+ }
+
+ void EnableDispatch() {
+ ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0);
+ this->GetStackParameters().disable_count--;
+ }
+
+ void Pin();
+
+ void Unpin();
+
+ void SetInExceptionHandler() {
+ this->GetStackParameters().is_in_exception_handler = true;
+ }
+
+ void ClearInExceptionHandler() {
+ this->GetStackParameters().is_in_exception_handler = false;
+ }
+
+ [[nodiscard]] bool IsInExceptionHandler() const {
+ return this->GetStackParameters().is_in_exception_handler;
+ }
+
+ void SetIsCallingSvc() {
+ this->GetStackParameters().is_calling_svc = true;
+ }
+
+ void ClearIsCallingSvc() {
+ this->GetStackParameters().is_calling_svc = false;
+ }
+
+ [[nodiscard]] bool IsCallingSvc() const {
+ return this->GetStackParameters().is_calling_svc;
+ }
+
+ [[nodiscard]] u8 GetSvcId() const {
+ return this->GetStackParameters().current_svc_id;
+ }
+
+ void RegisterDpc(DpcFlag flag) {
+ this->GetStackParameters().dpc_flags |= static_cast<u8>(flag);
+ }
+
+ void ClearDpc(DpcFlag flag) {
+ this->GetStackParameters().dpc_flags &= ~static_cast<u8>(flag);
+ }
+
+ [[nodiscard]] u8 GetDpc() const {
+ return this->GetStackParameters().dpc_flags;
+ }
+
+ [[nodiscard]] bool HasDpc() const {
+ return this->GetDpc() != 0;
+ }
+
+ void SetWaitReasonForDebugging(ThreadWaitReasonForDebugging reason) {
+ wait_reason_for_debugging = reason;
+ }
+
+ [[nodiscard]] ThreadWaitReasonForDebugging GetWaitReasonForDebugging() const {
+ return wait_reason_for_debugging;
+ }
+
+ [[nodiscard]] ThreadType GetThreadTypeForDebugging() const {
+ return thread_type_for_debugging;
+ }
+
+ void SetWaitObjectsForDebugging(const std::span<KSynchronizationObject*>& objects) {
+ wait_objects_for_debugging.clear();
+ wait_objects_for_debugging.reserve(objects.size());
+ for (const auto& object : objects) {
+ wait_objects_for_debugging.emplace_back(object);
+ }
+ }
+
+ [[nodiscard]] const std::vector<KSynchronizationObject*>& GetWaitObjectsForDebugging() const {
+ return wait_objects_for_debugging;
+ }
+
+ void SetMutexWaitAddressForDebugging(VAddr address) {
+ mutex_wait_address_for_debugging = address;
+ }
+
+ [[nodiscard]] VAddr GetMutexWaitAddressForDebugging() const {
+ return mutex_wait_address_for_debugging;
+ }
+
+ [[nodiscard]] s32 GetIdealCoreForDebugging() const {
+ return virtual_ideal_core_id;
+ }
+
+ void AddWaiter(KThread* thread);
+
+ void RemoveWaiter(KThread* thread);
+
+ [[nodiscard]] ResultCode GetThreadContext3(std::vector<u8>& out);
+
+ [[nodiscard]] KThread* RemoveWaiterByKey(s32* out_num_waiters, VAddr key);
+
+ [[nodiscard]] VAddr GetAddressKey() const {
+ return address_key;
+ }
+
+ [[nodiscard]] u32 GetAddressKeyValue() const {
+ return address_key_value;
+ }
+
+ void SetAddressKey(VAddr key) {
+ address_key = key;
+ }
+
+ void SetAddressKey(VAddr key, u32 val) {
+ address_key = key;
+ address_key_value = val;
+ }
+
+ [[nodiscard]] bool HasWaiters() const {
+ return !waiter_list.empty();
+ }
+
+ [[nodiscard]] s32 GetNumKernelWaiters() const {
+ return num_kernel_waiters;
+ }
+
+ [[nodiscard]] u64 GetConditionVariableKey() const {
+ return condvar_key;
+ }
+
+ [[nodiscard]] u64 GetAddressArbiterKey() const {
+ return condvar_key;
+ }
+
+private:
+ static constexpr size_t PriorityInheritanceCountMax = 10;
+ union SyncObjectBuffer {
+ std::array<KSynchronizationObject*, Svc::ArgumentHandleCountMax> sync_objects{};
+ std::array<Handle,
+ Svc::ArgumentHandleCountMax*(sizeof(KSynchronizationObject*) / sizeof(Handle))>
+ handles;
+ constexpr SyncObjectBuffer() {}
+ };
+ static_assert(sizeof(SyncObjectBuffer::sync_objects) == sizeof(SyncObjectBuffer::handles));
+
+ struct ConditionVariableComparator {
+ struct LightCompareType {
+ u64 cv_key{};
+ s32 priority{};
+
+ [[nodiscard]] constexpr u64 GetConditionVariableKey() const {
+ return cv_key;
+ }
+
+ [[nodiscard]] constexpr s32 GetPriority() const {
+ return priority;
+ }
+ };
+
+ template <typename T>
+ requires(
+ std::same_as<T, KThread> ||
+ std::same_as<T, LightCompareType>) static constexpr int Compare(const T& lhs,
+ const KThread& rhs) {
+ const u64 l_key = lhs.GetConditionVariableKey();
+ const u64 r_key = rhs.GetConditionVariableKey();
+
+ if (l_key < r_key) {
+ // Sort first by key
+ return -1;
+ } else if (l_key == r_key && lhs.GetPriority() < rhs.GetPriority()) {
+ // And then by priority.
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ };
+
+ void AddWaiterImpl(KThread* thread);
+
+ void RemoveWaiterImpl(KThread* thread);
+
+ void StartTermination();
+
+ [[nodiscard]] ResultCode Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack_top,
+ s32 prio, s32 virt_core, Process* owner, ThreadType type);
+
+ [[nodiscard]] static ResultCode InitializeThread(KThread* thread, KThreadFunction func,
+ uintptr_t arg, VAddr user_stack_top, s32 prio,
+ s32 core, Process* owner, ThreadType type);
+
+ static void RestorePriority(KernelCore& kernel, KThread* thread);
+
+ // For core KThread implementation
+ ThreadContext32 thread_context_32{};
+ ThreadContext64 thread_context_64{};
+ Common::IntrusiveRedBlackTreeNode condvar_arbiter_tree_node{};
+ s32 priority{};
+ using ConditionVariableThreadTreeTraits =
+ Common::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<
+ &KThread::condvar_arbiter_tree_node>;
+ using ConditionVariableThreadTree =
+ ConditionVariableThreadTreeTraits::TreeType<ConditionVariableComparator>;
+ ConditionVariableThreadTree* condvar_tree{};
+ u64 condvar_key{};
+ u64 virtual_affinity_mask{};
+ KAffinityMask physical_affinity_mask{};
+ u64 thread_id{};
+ std::atomic<s64> cpu_time{};
+ KSynchronizationObject* synced_object{};
+ VAddr address_key{};
+ Process* parent{};
+ VAddr kernel_stack_top{};
+ u32* light_ipc_data{};
+ VAddr tls_address{};
+ KLightLock activity_pause_lock;
+ s64 schedule_count{};
+ s64 last_scheduled_tick{};
+ std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> per_core_priority_queue_entry{};
+ KThreadQueue* sleeping_queue{};
+ WaiterList waiter_list{};
+ WaiterList pinned_waiter_list{};
+ KThread* lock_owner{};
+ u32 address_key_value{};
+ u32 suspend_request_flags{};
+ u32 suspend_allowed_flags{};
+ ResultCode wait_result{RESULT_SUCCESS};
+ s32 base_priority{};
+ s32 physical_ideal_core_id{};
+ s32 virtual_ideal_core_id{};
+ s32 num_kernel_waiters{};
+ s32 current_core_id{};
+ s32 core_id{};
+ KAffinityMask original_physical_affinity_mask{};
+ s32 original_physical_ideal_core_id{};
+ s32 num_core_migration_disables{};
+ ThreadState thread_state{};
+ std::atomic<bool> termination_requested{};
+ bool wait_cancelled{};
+ bool cancellable{};
+ bool signaled{};
+ bool initialized{};
+ bool debug_attached{};
+ s8 priority_inheritance_count{};
+ bool resource_limit_release_hint{};
+ StackParameters stack_parameters{};
+ Common::SpinLock context_guard{};
+
+ // For emulation
+ std::shared_ptr<Common::Fiber> host_context{};
+
+ // For debugging
+ std::vector<KSynchronizationObject*> wait_objects_for_debugging;
+ VAddr mutex_wait_address_for_debugging{};
+ ThreadWaitReasonForDebugging wait_reason_for_debugging{};
+ ThreadType thread_type_for_debugging{};
+ std::string name;
+
+public:
+ using ConditionVariableThreadTreeType = ConditionVariableThreadTree;
+
+ void SetConditionVariable(ConditionVariableThreadTree* tree, VAddr address, u64 cv_key,
+ u32 value) {
+ condvar_tree = tree;
+ condvar_key = cv_key;
+ address_key = address;
+ address_key_value = value;
+ }
+
+ void ClearConditionVariable() {
+ condvar_tree = nullptr;
+ }
+
+ [[nodiscard]] bool IsWaitingForConditionVariable() const {
+ return condvar_tree != nullptr;
+ }
+
+ void SetAddressArbiter(ConditionVariableThreadTree* tree, u64 address) {
+ condvar_tree = tree;
+ condvar_key = address;
+ }
+
+ void ClearAddressArbiter() {
+ condvar_tree = nullptr;
+ }
+
+ [[nodiscard]] bool IsWaitingForAddressArbiter() const {
+ return condvar_tree != nullptr;
+ }
+
+ [[nodiscard]] ConditionVariableThreadTree* GetConditionVariableTree() const {
+ return condvar_tree;
+ }
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread_queue.h b/src/core/hle/kernel/k_thread_queue.h
new file mode 100644
index 000000000..c52eba249
--- /dev/null
+++ b/src/core/hle/kernel/k_thread_queue.h
@@ -0,0 +1,81 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/k_thread.h"
+
+namespace Kernel {
+
+class KThreadQueue {
+public:
+ explicit KThreadQueue(KernelCore& kernel) : kernel{kernel} {}
+
+ bool IsEmpty() const {
+ return wait_list.empty();
+ }
+
+ KThread::WaiterList::iterator begin() {
+ return wait_list.begin();
+ }
+ KThread::WaiterList::iterator end() {
+ return wait_list.end();
+ }
+
+ bool SleepThread(KThread* t) {
+ KScopedSchedulerLock sl{kernel};
+
+ // If the thread needs terminating, don't enqueue it.
+ if (t->IsTerminationRequested()) {
+ return false;
+ }
+
+ // Set the thread's queue and mark it as waiting.
+ t->SetSleepingQueue(this);
+ t->SetState(ThreadState::Waiting);
+
+ // Add the thread to the queue.
+ wait_list.push_back(*t);
+
+ return true;
+ }
+
+ void WakeupThread(KThread* t) {
+ KScopedSchedulerLock sl{kernel};
+
+ // Remove the thread from the queue.
+ wait_list.erase(wait_list.iterator_to(*t));
+
+ // Mark the thread as no longer sleeping.
+ t->SetState(ThreadState::Runnable);
+ t->SetSleepingQueue(nullptr);
+ }
+
+ KThread* WakeupFrontThread() {
+ KScopedSchedulerLock sl{kernel};
+
+ if (wait_list.empty()) {
+ return nullptr;
+ } else {
+ // Remove the thread from the queue.
+ auto it = wait_list.begin();
+ KThread* thread = std::addressof(*it);
+ wait_list.erase(it);
+
+ ASSERT(thread->GetState() == ThreadState::Waiting);
+
+ // Mark the thread as no longer sleeping.
+ thread->SetState(ThreadState::Runnable);
+ thread->SetSleepingQueue(nullptr);
+
+ return thread;
+ }
+ }
+
+private:
+ KernelCore& kernel;
+ KThread::WaiterList wait_list{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_writable_event.cpp b/src/core/hle/kernel/k_writable_event.cpp
new file mode 100644
index 000000000..25c52edb2
--- /dev/null
+++ b/src/core/hle/kernel/k_writable_event.cpp
@@ -0,0 +1,27 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
+
+namespace Kernel {
+
+KWritableEvent::KWritableEvent(KernelCore& kernel, std::string&& name)
+ : Object{kernel, std::move(name)} {}
+KWritableEvent::~KWritableEvent() = default;
+
+void KWritableEvent::Initialize(KEvent* parent_) {
+ parent = parent_;
+}
+
+ResultCode KWritableEvent::Signal() {
+ return parent->GetReadableEvent()->Signal();
+}
+
+ResultCode KWritableEvent::Clear() {
+ return parent->GetReadableEvent()->Clear();
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_writable_event.h b/src/core/hle/kernel/k_writable_event.h
new file mode 100644
index 000000000..518f5448d
--- /dev/null
+++ b/src/core/hle/kernel/k_writable_event.h
@@ -0,0 +1,44 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/object.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+class KernelCore;
+class KEvent;
+
+class KWritableEvent final : public Object {
+public:
+ explicit KWritableEvent(KernelCore& kernel, std::string&& name);
+ ~KWritableEvent() override;
+
+ std::string GetTypeName() const override {
+ return "KWritableEvent";
+ }
+
+ static constexpr HandleType HANDLE_TYPE = HandleType::WritableEvent;
+ HandleType GetHandleType() const override {
+ return HANDLE_TYPE;
+ }
+
+ void Initialize(KEvent* parent_);
+
+ void Finalize() override {}
+
+ ResultCode Signal();
+ ResultCode Clear();
+
+ KEvent* GetParent() const {
+ return parent;
+ }
+
+private:
+ KEvent* parent{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index cabe8d418..b6e6f115e 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -7,15 +7,15 @@
#include <bitset>
#include <functional>
#include <memory>
-#include <mutex>
#include <thread>
-#include <unordered_map>
+#include <unordered_set>
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/thread.h"
+#include "common/thread_worker.h"
#include "core/arm/arm_interface.h"
#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/exclusive_monitor.h"
@@ -26,19 +26,19 @@
#include "core/device_memory.h"
#include "core/hardware_properties.h"
#include "core/hle/kernel/client_port.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/k_resource_limit.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/memory_layout.h"
#include "core/hle/kernel/memory/memory_manager.h"
#include "core/hle/kernel/memory/slab_heap.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/resource_limit.h"
-#include "core/hle/kernel/scheduler.h"
+#include "core/hle/kernel/service_thread.h"
#include "core/hle/kernel/shared_memory.h"
-#include "core/hle/kernel/synchronization.h"
-#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/lock.h"
#include "core/hle/result.h"
@@ -50,26 +50,42 @@ namespace Kernel {
struct KernelCore::Impl {
explicit Impl(Core::System& system, KernelCore& kernel)
- : global_scheduler{kernel}, synchronization{system}, time_manager{system},
- global_handle_table{kernel}, system{system} {}
+ : time_manager{system}, global_handle_table{kernel}, system{system} {}
void SetMulticore(bool is_multicore) {
this->is_multicore = is_multicore;
}
void Initialize(KernelCore& kernel) {
- Shutdown();
+ global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
+
RegisterHostThread();
+ service_thread_manager =
+ std::make_unique<Common::ThreadWorker>(1, "yuzu:ServiceThreadManager");
+ is_phantom_mode_for_singlecore = false;
+
InitializePhysicalCores();
- InitializeSystemResourceLimit(kernel);
+ InitializeSystemResourceLimit(kernel, system);
InitializeMemoryLayout();
InitializePreemption(kernel);
InitializeSchedulers();
InitializeSuspendThreads();
}
+ void InitializeCores() {
+ for (auto& core : cores) {
+ core.Initialize(current_process->Is64BitProcess());
+ }
+ }
+
void Shutdown() {
+ process_list.clear();
+
+ // Ensures all service threads gracefully shutdown
+ service_thread_manager.reset();
+ service_threads.clear();
+
next_object_id = 0;
next_kernel_process_id = Process::InitialKIPIDMin;
next_user_process_id = Process::ProcessIDMin;
@@ -81,74 +97,69 @@ struct KernelCore::Impl {
}
}
- for (std::size_t i = 0; i < cores.size(); i++) {
- cores[i].Shutdown();
- schedulers[i].reset();
- }
cores.clear();
- registered_core_threads.reset();
-
- process_list.clear();
current_process = nullptr;
system_resource_limit = nullptr;
global_handle_table.Clear();
- preemption_event = nullptr;
- global_scheduler.Shutdown();
+ preemption_event = nullptr;
named_ports.clear();
- for (auto& core : cores) {
- core.Shutdown();
- }
- cores.clear();
-
exclusive_monitor.reset();
- host_thread_ids.clear();
+
+ // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
+ next_host_thread_id = Core::Hardware::NUM_CPU_CORES;
}
void InitializePhysicalCores() {
exclusive_monitor =
Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
- for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
- schedulers[i] = std::make_unique<Kernel::Scheduler>(system, i);
- cores.emplace_back(system, i, *schedulers[i], interrupts[i]);
+ for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i);
+ cores.emplace_back(i, system, *schedulers[i], interrupts);
}
}
void InitializeSchedulers() {
- for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
+ for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
cores[i].Scheduler().Initialize();
}
}
// Creates the default system resource limit
- void InitializeSystemResourceLimit(KernelCore& kernel) {
- system_resource_limit = ResourceLimit::Create(kernel);
+ void InitializeSystemResourceLimit(KernelCore& kernel, Core::System& system) {
+ system_resource_limit = std::make_shared<KResourceLimit>(kernel, system);
// If setting the default system values fails, then something seriously wrong has occurred.
- ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x100000000)
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, 0x100000000)
.IsSuccess());
- ASSERT(system_resource_limit->SetLimitValue(ResourceType::Threads, 800).IsSuccess());
- ASSERT(system_resource_limit->SetLimitValue(ResourceType::Events, 700).IsSuccess());
- ASSERT(system_resource_limit->SetLimitValue(ResourceType::TransferMemory, 200).IsSuccess());
- ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess());
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Threads, 800).IsSuccess());
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Events, 700).IsSuccess());
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemory, 200)
+ .IsSuccess());
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Sessions, 933).IsSuccess());
- if (!system_resource_limit->Reserve(ResourceType::PhysicalMemory, 0) ||
- !system_resource_limit->Reserve(ResourceType::PhysicalMemory, 0x60000)) {
+ // Derived from recent software updates. The kernel reserves 27MB
+ constexpr u64 kernel_size{0x1b00000};
+ if (!system_resource_limit->Reserve(LimitableResource::PhysicalMemory, kernel_size)) {
UNREACHABLE();
}
+ // Reserve secure applet memory, introduced in firmware 5.0.0
+ constexpr u64 secure_applet_memory_size{0x400000};
+ ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory,
+ secure_applet_memory_size));
}
void InitializePreemption(KernelCore& kernel) {
preemption_event = Core::Timing::CreateEvent(
"PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) {
{
- SchedulerLock lock(kernel);
- global_scheduler.PreemptThreads();
+ KScopedSchedulerLock lock(kernel);
+ global_scheduler_context->PreemptThreads();
}
const auto time_interval = std::chrono::nanoseconds{
Core::Timing::msToCycles(std::chrono::milliseconds(10))};
@@ -165,11 +176,9 @@ struct KernelCore::Impl {
std::string name = "Suspend Thread Id:" + std::to_string(i);
std::function<void(void*)> init_func = Core::CpuManager::GetSuspendThreadStartFunc();
void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
- const auto type =
- static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_SUSPEND);
- auto thread_res =
- Thread::Create(system, type, std::move(name), 0, 0, 0, static_cast<u32>(i), 0,
- nullptr, std::move(init_func), init_func_parameter);
+ auto thread_res = KThread::Create(system, ThreadType::HighPriority, std::move(name), 0,
+ 0, 0, static_cast<u32>(i), 0, nullptr,
+ std::move(init_func), init_func_parameter);
suspend_threads[i] = std::move(thread_res).Unwrap();
}
@@ -177,69 +186,82 @@ struct KernelCore::Impl {
void MakeCurrentProcess(Process* process) {
current_process = process;
-
if (process == nullptr) {
return;
}
- u32 core_id = GetCurrentHostThreadID();
+ const u32 core_id = GetCurrentHostThreadID();
if (core_id < Core::Hardware::NUM_CPU_CORES) {
system.Memory().SetCurrentPageTable(*process, core_id);
}
}
+ /// Creates a new host thread ID, should only be called by GetHostThreadId
+ u32 AllocateHostThreadId(std::optional<std::size_t> core_id) {
+ if (core_id) {
+ // The first for slots are reserved for CPU core threads
+ ASSERT(*core_id < Core::Hardware::NUM_CPU_CORES);
+ return static_cast<u32>(*core_id);
+ } else {
+ return next_host_thread_id++;
+ }
+ }
+
+ /// Gets the host thread ID for the caller, allocating a new one if this is the first time
+ u32 GetHostThreadId(std::optional<std::size_t> core_id = std::nullopt) {
+ const thread_local auto host_thread_id{AllocateHostThreadId(core_id)};
+ return host_thread_id;
+ }
+
+ // Gets the dummy KThread for the caller, allocating a new one if this is the first time
+ KThread* GetHostDummyThread() {
+ const thread_local auto thread =
+ KThread::Create(
+ system, ThreadType::Main, fmt::format("DummyThread:{}", GetHostThreadId()), 0,
+ KThread::DefaultThreadPriority, 0, static_cast<u32>(3), 0, nullptr,
+ []([[maybe_unused]] void* arg) { UNREACHABLE(); }, nullptr)
+ .Unwrap();
+ return thread.get();
+ }
+
+ /// Registers a CPU core thread by allocating a host thread ID for it
void RegisterCoreThread(std::size_t core_id) {
- std::unique_lock lock{register_thread_mutex};
+ ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
+ const auto this_id = GetHostThreadId(core_id);
if (!is_multicore) {
- single_core_thread_id = std::this_thread::get_id();
+ single_core_thread_id = this_id;
}
- const std::thread::id this_id = std::this_thread::get_id();
- const auto it = host_thread_ids.find(this_id);
- ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
- ASSERT(it == host_thread_ids.end());
- ASSERT(!registered_core_threads[core_id]);
- host_thread_ids[this_id] = static_cast<u32>(core_id);
- registered_core_threads.set(core_id);
}
+ /// Registers a new host thread by allocating a host thread ID for it
void RegisterHostThread() {
- std::unique_lock lock{register_thread_mutex};
- const std::thread::id this_id = std::this_thread::get_id();
- const auto it = host_thread_ids.find(this_id);
- if (it != host_thread_ids.end()) {
- return;
- }
- host_thread_ids[this_id] = registered_thread_ids++;
+ [[maybe_unused]] const auto this_id = GetHostThreadId();
+ [[maybe_unused]] const auto dummy_thread = GetHostDummyThread();
}
- u32 GetCurrentHostThreadID() const {
- const std::thread::id this_id = std::this_thread::get_id();
- if (!is_multicore) {
- if (single_core_thread_id == this_id) {
- return static_cast<u32>(system.GetCpuManager().CurrentCore());
- }
- }
- const auto it = host_thread_ids.find(this_id);
- if (it == host_thread_ids.end()) {
- return Core::INVALID_HOST_THREAD_ID;
+ [[nodiscard]] u32 GetCurrentHostThreadID() {
+ const auto this_id = GetHostThreadId();
+ if (!is_multicore && single_core_thread_id == this_id) {
+ return static_cast<u32>(system.GetCpuManager().CurrentCore());
}
- return it->second;
+ return this_id;
}
- Core::EmuThreadHandle GetCurrentEmuThreadID() const {
- Core::EmuThreadHandle result = Core::EmuThreadHandle::InvalidHandle();
- result.host_handle = GetCurrentHostThreadID();
- if (result.host_handle >= Core::Hardware::NUM_CPU_CORES) {
- return result;
- }
- const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler();
- const Kernel::Thread* current = sched.GetCurrentThread();
- if (current != nullptr && !current->IsPhantomMode()) {
- result.guest_handle = current->GetGlobalHandle();
- } else {
- result.guest_handle = InvalidHandle;
+ bool IsPhantomModeForSingleCore() const {
+ return is_phantom_mode_for_singlecore;
+ }
+
+ void SetIsPhantomModeForSingleCore(bool value) {
+ ASSERT(!is_multicore);
+ is_phantom_mode_for_singlecore = value;
+ }
+
+ KThread* GetCurrentEmuThread() {
+ const auto thread_id = GetCurrentHostThreadID();
+ if (thread_id >= Core::Hardware::NUM_CPU_CORES) {
+ return GetHostDummyThread();
}
- return result;
+ return schedulers[thread_id]->GetCurrentThread();
}
void InitializeMemoryLayout() {
@@ -286,8 +308,11 @@ struct KernelCore::Impl {
// Allocate slab heaps
user_slab_heap_pages = std::make_unique<Memory::SlabHeap<Memory::Page>>();
+ constexpr u64 user_slab_heap_size{0x1ef000};
+ // Reserve slab heaps
+ ASSERT(
+ system_resource_limit->Reserve(LimitableResource::PhysicalMemory, user_slab_heap_size));
// Initialize slab heaps
- constexpr u64 user_slab_heap_size{0x3de000};
user_slab_heap_pages->Initialize(
system.DeviceMemory().GetPointer(Core::DramMemoryMap::SlabHeapBase),
user_slab_heap_size);
@@ -301,11 +326,10 @@ struct KernelCore::Impl {
// Lists all processes that exist in the current session.
std::vector<std::shared_ptr<Process>> process_list;
Process* current_process = nullptr;
- Kernel::GlobalScheduler global_scheduler;
- Kernel::Synchronization synchronization;
+ std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
Kernel::TimeManager time_manager;
- std::shared_ptr<ResourceLimit> system_resource_limit;
+ std::shared_ptr<KResourceLimit> system_resource_limit;
std::shared_ptr<Core::Timing::EventType> preemption_event;
@@ -320,11 +344,8 @@ struct KernelCore::Impl {
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
std::vector<Kernel::PhysicalCore> cores;
- // 0-3 IDs represent core threads, >3 represent others
- std::unordered_map<std::thread::id, u32> host_thread_ids;
- u32 registered_thread_ids{Core::Hardware::NUM_CPU_CORES};
- std::bitset<Core::Hardware::NUM_CPU_CORES> registered_core_threads;
- std::mutex register_thread_mutex;
+ // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
+ std::atomic<u32> next_host_thread_id{Core::Hardware::NUM_CPU_CORES};
// Kernel memory management
std::unique_ptr<Memory::MemoryManager> memory_manager;
@@ -336,12 +357,20 @@ struct KernelCore::Impl {
std::shared_ptr<Kernel::SharedMemory> irs_shared_mem;
std::shared_ptr<Kernel::SharedMemory> time_shared_mem;
- std::array<std::shared_ptr<Thread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{};
+ // Threads used for services
+ std::unordered_set<std::shared_ptr<Kernel::ServiceThread>> service_threads;
+
+ // Service threads are managed by a worker thread, so that a calling service thread can queue up
+ // the release of itself
+ std::unique_ptr<Common::ThreadWorker> service_thread_manager;
+
+ std::array<std::shared_ptr<KThread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{};
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
- std::array<std::unique_ptr<Kernel::Scheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
+ std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
bool is_multicore{};
- std::thread::id single_core_thread_id{};
+ bool is_phantom_mode_for_singlecore{};
+ u32 single_core_thread_id{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> svc_ticks{};
@@ -362,16 +391,20 @@ void KernelCore::Initialize() {
impl->Initialize(*this);
}
+void KernelCore::InitializeCores() {
+ impl->InitializeCores();
+}
+
void KernelCore::Shutdown() {
impl->Shutdown();
}
-std::shared_ptr<ResourceLimit> KernelCore::GetSystemResourceLimit() const {
+std::shared_ptr<KResourceLimit> KernelCore::GetSystemResourceLimit() const {
return impl->system_resource_limit;
}
-std::shared_ptr<Thread> KernelCore::RetrieveThreadFromGlobalHandleTable(Handle handle) const {
- return impl->global_handle_table.Get<Thread>(handle);
+std::shared_ptr<KThread> KernelCore::RetrieveThreadFromGlobalHandleTable(Handle handle) const {
+ return impl->global_handle_table.Get<KThread>(handle);
}
void KernelCore::AppendNewProcess(std::shared_ptr<Process> process) {
@@ -394,19 +427,19 @@ const std::vector<std::shared_ptr<Process>>& KernelCore::GetProcessList() const
return impl->process_list;
}
-Kernel::GlobalScheduler& KernelCore::GlobalScheduler() {
- return impl->global_scheduler;
+Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() {
+ return *impl->global_scheduler_context;
}
-const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
- return impl->global_scheduler;
+const Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() const {
+ return *impl->global_scheduler_context;
}
-Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) {
+Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) {
return *impl->schedulers[id];
}
-const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const {
+const Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) const {
return *impl->schedulers[id];
}
@@ -430,16 +463,13 @@ const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
return impl->cores[core_id];
}
-Kernel::Scheduler& KernelCore::CurrentScheduler() {
+Kernel::KScheduler* KernelCore::CurrentScheduler() {
u32 core_id = impl->GetCurrentHostThreadID();
- ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
- return *impl->schedulers[core_id];
-}
-
-const Kernel::Scheduler& KernelCore::CurrentScheduler() const {
- u32 core_id = impl->GetCurrentHostThreadID();
- ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
- return *impl->schedulers[core_id];
+ if (core_id >= Core::Hardware::NUM_CPU_CORES) {
+ // This is expected when called from not a guest thread
+ return {};
+ }
+ return impl->schedulers[core_id].get();
}
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts() {
@@ -451,14 +481,6 @@ const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Kern
return impl->interrupts;
}
-Kernel::Synchronization& KernelCore::Synchronization() {
- return impl->synchronization;
-}
-
-const Kernel::Synchronization& KernelCore::Synchronization() const {
- return impl->synchronization;
-}
-
Kernel::TimeManager& KernelCore::TimeManager() {
return impl->time_manager;
}
@@ -476,12 +498,17 @@ const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const {
}
void KernelCore::InvalidateAllInstructionCaches() {
- auto& threads = GlobalScheduler().GetThreadList();
- for (auto& thread : threads) {
- if (!thread->IsHLEThread()) {
- auto& arm_interface = thread->ArmInterface();
- arm_interface.ClearInstructionCache();
+ for (auto& physical_core : impl->cores) {
+ physical_core.ArmInterface().ClearInstructionCache();
+ }
+}
+
+void KernelCore::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) {
+ for (auto& physical_core : impl->cores) {
+ if (!physical_core.IsInitialized()) {
+ continue;
}
+ physical_core.ArmInterface().InvalidateCacheRange(addr, size);
}
}
@@ -542,8 +569,8 @@ u32 KernelCore::GetCurrentHostThreadID() const {
return impl->GetCurrentHostThreadID();
}
-Core::EmuThreadHandle KernelCore::GetCurrentEmuThreadID() const {
- return impl->GetCurrentEmuThreadID();
+KThread* KernelCore::GetCurrentEmuThread() const {
+ return impl->GetCurrentEmuThread();
}
Memory::MemoryManager& KernelCore::MemoryManager() {
@@ -597,10 +624,12 @@ const Kernel::SharedMemory& KernelCore::GetTimeSharedMem() const {
void KernelCore::Suspend(bool in_suspention) {
const bool should_suspend = exception_exited || in_suspention;
{
- SchedulerLock lock(*this);
- ThreadStatus status = should_suspend ? ThreadStatus::Ready : ThreadStatus::WaitSleep;
+ KScopedSchedulerLock lock(*this);
+ const auto state = should_suspend ? ThreadState::Runnable : ThreadState::Waiting;
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
- impl->suspend_threads[i]->SetStatus(status);
+ impl->suspend_threads[i]->SetState(state);
+ impl->suspend_threads[i]->SetWaitReasonForDebugging(
+ ThreadWaitReasonForDebugging::Suspended);
}
}
}
@@ -624,4 +653,27 @@ void KernelCore::ExitSVCProfile() {
MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]);
}
+std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) {
+ auto service_thread = std::make_shared<Kernel::ServiceThread>(*this, 1, name);
+ impl->service_thread_manager->QueueWork(
+ [this, service_thread] { impl->service_threads.emplace(service_thread); });
+ return service_thread;
+}
+
+void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
+ impl->service_thread_manager->QueueWork([this, service_thread] {
+ if (auto strong_ptr = service_thread.lock()) {
+ impl->service_threads.erase(strong_ptr);
+ }
+ });
+}
+
+bool KernelCore::IsPhantomModeForSingleCore() const {
+ return impl->IsPhantomModeForSingleCore();
+}
+
+void KernelCore::SetIsPhantomModeForSingleCore(bool value) {
+ impl->SetIsPhantomModeForSingleCore(value);
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 16285c3f0..806a0d986 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -33,19 +33,23 @@ template <typename T>
class SlabHeap;
} // namespace Memory
-class AddressArbiter;
class ClientPort;
-class GlobalScheduler;
+class GlobalSchedulerContext;
class HandleTable;
class PhysicalCore;
class Process;
-class ResourceLimit;
-class Scheduler;
+class KResourceLimit;
+class KScheduler;
class SharedMemory;
+class ServiceThread;
class Synchronization;
-class Thread;
+class KThread;
class TimeManager;
+using EmuThreadHandle = uintptr_t;
+constexpr EmuThreadHandle EmuThreadHandleInvalid{};
+constexpr EmuThreadHandle EmuThreadHandleReserved{1ULL << 63};
+
/// Represents a single instance of the kernel.
class KernelCore {
private:
@@ -74,14 +78,17 @@ public:
/// Resets the kernel to a clean slate for use.
void Initialize();
+ /// Initializes the CPU cores.
+ void InitializeCores();
+
/// Clears all resources in use by the kernel instance.
void Shutdown();
/// Retrieves a shared pointer to the system resource limit instance.
- std::shared_ptr<ResourceLimit> GetSystemResourceLimit() const;
+ std::shared_ptr<KResourceLimit> GetSystemResourceLimit() const;
/// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table.
- std::shared_ptr<Thread> RetrieveThreadFromGlobalHandleTable(Handle handle) const;
+ std::shared_ptr<KThread> RetrieveThreadFromGlobalHandleTable(Handle handle) const;
/// Adds the given shared pointer to an internal list of active processes.
void AppendNewProcess(std::shared_ptr<Process> process);
@@ -99,16 +106,16 @@ public:
const std::vector<std::shared_ptr<Process>>& GetProcessList() const;
/// Gets the sole instance of the global scheduler
- Kernel::GlobalScheduler& GlobalScheduler();
+ Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
/// Gets the sole instance of the global scheduler
- const Kernel::GlobalScheduler& GlobalScheduler() const;
+ const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const;
/// Gets the sole instance of the Scheduler assoviated with cpu core 'id'
- Kernel::Scheduler& Scheduler(std::size_t id);
+ Kernel::KScheduler& Scheduler(std::size_t id);
/// Gets the sole instance of the Scheduler assoviated with cpu core 'id'
- const Kernel::Scheduler& Scheduler(std::size_t id) const;
+ const Kernel::KScheduler& Scheduler(std::size_t id) const;
/// Gets the an instance of the respective physical CPU core.
Kernel::PhysicalCore& PhysicalCore(std::size_t id);
@@ -117,10 +124,7 @@ public:
const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
/// Gets the sole instance of the Scheduler at the current running core.
- Kernel::Scheduler& CurrentScheduler();
-
- /// Gets the sole instance of the Scheduler at the current running core.
- const Kernel::Scheduler& CurrentScheduler() const;
+ Kernel::KScheduler* CurrentScheduler();
/// Gets the an instance of the current physical CPU core.
Kernel::PhysicalCore& CurrentPhysicalCore();
@@ -128,12 +132,6 @@ public:
/// Gets the an instance of the current physical CPU core.
const Kernel::PhysicalCore& CurrentPhysicalCore() const;
- /// Gets the an instance of the Synchronization Interface.
- Kernel::Synchronization& Synchronization();
-
- /// Gets the an instance of the Synchronization Interface.
- const Kernel::Synchronization& Synchronization() const;
-
/// Gets the an instance of the TimeManager Interface.
Kernel::TimeManager& TimeManager();
@@ -153,6 +151,8 @@ public:
void InvalidateAllInstructionCaches();
+ void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size);
+
/// Adds a port to the named port table
void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port);
@@ -165,8 +165,8 @@ public:
/// Determines whether or not the given port is a valid named port.
bool IsValidNamedPort(NamedPortTable::const_iterator port) const;
- /// Gets the current host_thread/guest_thread handle.
- Core::EmuThreadHandle GetCurrentEmuThreadID() const;
+ /// Gets the current host_thread/guest_thread pointer.
+ KThread* GetCurrentEmuThread() const;
/// Gets the current host_thread handle.
u32 GetCurrentHostThreadID() const;
@@ -225,10 +225,30 @@ public:
void ExitSVCProfile();
+ /**
+ * Creates an HLE service thread, which are used to execute service routines asynchronously.
+ * While these are allocated per ServerSession, these need to be owned and managed outside of
+ * ServerSession to avoid a circular dependency.
+ * @param name String name for the ServerSession creating this thread, used for debug purposes.
+ * @returns The a weak pointer newly created service thread.
+ */
+ std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(const std::string& name);
+
+ /**
+ * Releases a HLE service thread, instructing KernelCore to free it. This should be called when
+ * the ServerSession associated with the thread is destroyed.
+ * @param service_thread Service thread to release.
+ */
+ void ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread);
+
+ /// Workaround for single-core mode when preempting threads while idle.
+ bool IsPhantomModeForSingleCore() const;
+ void SetIsPhantomModeForSingleCore(bool value);
+
private:
friend class Object;
friend class Process;
- friend class Thread;
+ friend class KThread;
/// Creates a new object ID, incrementing the internal object ID counter.
u32 CreateNewObjectID();
diff --git a/src/core/hle/kernel/memory/address_space_info.cpp b/src/core/hle/kernel/memory/address_space_info.cpp
index e4288cab4..6cf43ba24 100644
--- a/src/core/hle/kernel/memory/address_space_info.cpp
+++ b/src/core/hle/kernel/memory/address_space_info.cpp
@@ -96,6 +96,7 @@ u64 AddressSpaceInfo::GetAddressSpaceStart(std::size_t width, Type type) {
return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].address;
}
UNREACHABLE();
+ return 0;
}
std::size_t AddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type) {
@@ -112,6 +113,7 @@ std::size_t AddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type)
return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].size;
}
UNREACHABLE();
+ return 0;
}
} // namespace Kernel::Memory
diff --git a/src/core/hle/kernel/memory/memory_block.h b/src/core/hle/kernel/memory/memory_block.h
index 9d7839d08..83acece1e 100644
--- a/src/core/hle/kernel/memory/memory_block.h
+++ b/src/core/hle/kernel/memory/memory_block.h
@@ -73,12 +73,12 @@ enum class MemoryState : u32 {
ThreadLocal =
static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagReferenceCounted,
- Transfered = static_cast<u32>(Svc::MemoryState::Transfered) | FlagsMisc |
- FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc |
- FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+ Transferred = static_cast<u32>(Svc::MemoryState::Transferred) | FlagsMisc |
+ FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc |
+ FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
- SharedTransfered = static_cast<u32>(Svc::MemoryState::SharedTransfered) | FlagsMisc |
- FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+ SharedTransferred = static_cast<u32>(Svc::MemoryState::SharedTransferred) | FlagsMisc |
+ FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
SharedCode = static_cast<u32>(Svc::MemoryState::SharedCode) | FlagMapped |
FlagReferenceCounted | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
@@ -111,8 +111,8 @@ static_assert(static_cast<u32>(MemoryState::AliasCodeData) == 0x03FFBD09);
static_assert(static_cast<u32>(MemoryState::Ipc) == 0x005C3C0A);
static_assert(static_cast<u32>(MemoryState::Stack) == 0x005C3C0B);
static_assert(static_cast<u32>(MemoryState::ThreadLocal) == 0x0040200C);
-static_assert(static_cast<u32>(MemoryState::Transfered) == 0x015C3C0D);
-static_assert(static_cast<u32>(MemoryState::SharedTransfered) == 0x005C380E);
+static_assert(static_cast<u32>(MemoryState::Transferred) == 0x015C3C0D);
+static_assert(static_cast<u32>(MemoryState::SharedTransferred) == 0x005C380E);
static_assert(static_cast<u32>(MemoryState::SharedCode) == 0x0040380F);
static_assert(static_cast<u32>(MemoryState::Inaccessible) == 0x00000010);
static_assert(static_cast<u32>(MemoryState::NonSecureIpc) == 0x005C3811);
@@ -222,9 +222,9 @@ public:
public:
constexpr MemoryBlock() = default;
- constexpr MemoryBlock(VAddr addr, std::size_t num_pages, MemoryState state,
- MemoryPermission perm, MemoryAttribute attribute)
- : addr{addr}, num_pages(num_pages), state{state}, perm{perm}, attribute{attribute} {}
+ constexpr MemoryBlock(VAddr addr_, std::size_t num_pages_, MemoryState state_,
+ MemoryPermission perm_, MemoryAttribute attribute_)
+ : addr{addr_}, num_pages(num_pages_), state{state_}, perm{perm_}, attribute{attribute_} {}
constexpr VAddr GetAddress() const {
return addr;
diff --git a/src/core/hle/kernel/memory/memory_block_manager.h b/src/core/hle/kernel/memory/memory_block_manager.h
index 6e1d41075..f57d1bbcc 100644
--- a/src/core/hle/kernel/memory/memory_block_manager.h
+++ b/src/core/hle/kernel/memory/memory_block_manager.h
@@ -57,8 +57,8 @@ public:
private:
void MergeAdjacent(iterator it, iterator& next_it);
- const VAddr start_addr;
- const VAddr end_addr;
+ [[maybe_unused]] const VAddr start_addr;
+ [[maybe_unused]] const VAddr end_addr;
MemoryBlockTree memory_block_tree;
};
diff --git a/src/core/hle/kernel/memory/memory_layout.h b/src/core/hle/kernel/memory/memory_layout.h
index 9b3d6267a..c7c0b2f49 100644
--- a/src/core/hle/kernel/memory/memory_layout.h
+++ b/src/core/hle/kernel/memory/memory_layout.h
@@ -5,9 +5,28 @@
#pragma once
#include "common/common_types.h"
+#include "core/device_memory.h"
namespace Kernel::Memory {
+constexpr std::size_t KernelAslrAlignment = 2 * 1024 * 1024;
+constexpr std::size_t KernelVirtualAddressSpaceWidth = 1ULL << 39;
+constexpr std::size_t KernelPhysicalAddressSpaceWidth = 1ULL << 48;
+constexpr std::size_t KernelVirtualAddressSpaceBase = 0ULL - KernelVirtualAddressSpaceWidth;
+constexpr std::size_t KernelVirtualAddressSpaceEnd =
+ KernelVirtualAddressSpaceBase + (KernelVirtualAddressSpaceWidth - KernelAslrAlignment);
+constexpr std::size_t KernelVirtualAddressSpaceLast = KernelVirtualAddressSpaceEnd - 1;
+constexpr std::size_t KernelVirtualAddressSpaceSize =
+ KernelVirtualAddressSpaceEnd - KernelVirtualAddressSpaceBase;
+
+constexpr bool IsKernelAddressKey(VAddr key) {
+ return KernelVirtualAddressSpaceBase <= key && key <= KernelVirtualAddressSpaceLast;
+}
+
+constexpr bool IsKernelAddress(VAddr address) {
+ return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd;
+}
+
class MemoryRegion final {
friend class MemoryLayout;
diff --git a/src/core/hle/kernel/memory/memory_manager.cpp b/src/core/hle/kernel/memory/memory_manager.cpp
index acf13585c..77f135cdc 100644
--- a/src/core/hle/kernel/memory/memory_manager.cpp
+++ b/src/core/hle/kernel/memory/memory_manager.cpp
@@ -8,9 +8,9 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/scope_exit.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory/memory_manager.h"
#include "core/hle/kernel/memory/page_linked_list.h"
+#include "core/hle/kernel/svc_results.h"
namespace Kernel::Memory {
@@ -95,7 +95,7 @@ ResultCode MemoryManager::Allocate(PageLinkedList& page_list, std::size_t num_pa
// Choose a heap based on our page size request
const s32 heap_index{PageHeap::GetBlockIndex(num_pages)};
if (heap_index < 0) {
- return ERR_OUT_OF_MEMORY;
+ return ResultOutOfMemory;
}
// TODO (bunnei): Support multiple managers
@@ -140,7 +140,7 @@ ResultCode MemoryManager::Allocate(PageLinkedList& page_list, std::size_t num_pa
// Only succeed if we allocated as many pages as we wanted
if (num_pages) {
- return ERR_OUT_OF_MEMORY;
+ return ResultOutOfMemory;
}
// We succeeded!
diff --git a/src/core/hle/kernel/memory/page_heap.h b/src/core/hle/kernel/memory/page_heap.h
index 22b0de860..131093284 100644
--- a/src/core/hle/kernel/memory/page_heap.h
+++ b/src/core/hle/kernel/memory/page_heap.h
@@ -8,11 +8,11 @@
#pragma once
#include <array>
+#include <bit>
#include <vector>
#include "common/alignment.h"
#include "common/assert.h"
-#include "common/bit_util.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/kernel/memory/memory_types.h"
@@ -105,7 +105,7 @@ private:
ASSERT(depth == 0);
return -1;
}
- offset = offset * 64 + Common::CountTrailingZeroes64(v);
+ offset = offset * 64 + static_cast<u32>(std::countr_zero(v));
++depth;
} while (depth < static_cast<s32>(used_depths));
diff --git a/src/core/hle/kernel/memory/page_table.cpp b/src/core/hle/kernel/memory/page_table.cpp
index a3fadb533..00ed9b881 100644
--- a/src/core/hle/kernel/memory/page_table.cpp
+++ b/src/core/hle/kernel/memory/page_table.cpp
@@ -6,7 +6,7 @@
#include "common/assert.h"
#include "common/scope_exit.h"
#include "core/core.h"
-#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/address_space_info.h"
#include "core/hle/kernel/memory/memory_block.h"
@@ -15,7 +15,7 @@
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/memory/system_control.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/resource_limit.h"
+#include "core/hle/kernel/svc_results.h"
#include "core/memory.h"
namespace Kernel::Memory {
@@ -141,7 +141,7 @@ ResultCode PageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_t
(alias_region_size + heap_region_size + stack_region_size + kernel_map_region_size)};
if (alloc_size < needed_size) {
UNREACHABLE();
- return ERR_OUT_OF_MEMORY;
+ return ResultOutOfMemory;
}
const std::size_t remaining_size{alloc_size - needed_size};
@@ -265,7 +265,7 @@ ResultCode PageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_t
physical_memory_usage = 0;
memory_pool = pool;
- page_table_impl.Resize(address_space_width, PageBits, true);
+ page_table_impl.Resize(address_space_width, PageBits);
return InitializeMemoryLayout(start, end);
}
@@ -277,11 +277,11 @@ ResultCode PageTable::MapProcessCode(VAddr addr, std::size_t num_pages, MemorySt
const u64 size{num_pages * PageSize};
if (!CanContain(addr, size, state)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (IsRegionMapped(addr, size)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
PageLinkedList page_linked_list;
@@ -307,7 +307,7 @@ ResultCode PageTable::MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::
MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped));
if (IsRegionMapped(dst_addr, size)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
PageLinkedList page_linked_list;
@@ -409,27 +409,25 @@ ResultCode PageTable::MapPhysicalMemory(VAddr addr, std::size_t size) {
return RESULT_SUCCESS;
}
- auto process{system.Kernel().CurrentProcess()};
const std::size_t remaining_size{size - mapped_size};
const std::size_t remaining_pages{remaining_size / PageSize};
- if (process->GetResourceLimit() &&
- !process->GetResourceLimit()->Reserve(ResourceType::PhysicalMemory, remaining_size)) {
- return ERR_RESOURCE_LIMIT_EXCEEDED;
+ // Reserve the memory from the process resource limit.
+ KScopedResourceReservation memory_reservation(
+ system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory,
+ remaining_size);
+ if (!memory_reservation.Succeeded()) {
+ LOG_ERROR(Kernel, "Could not reserve remaining {:X} bytes", remaining_size);
+ return ResultResourceLimitedExceeded;
}
PageLinkedList page_linked_list;
- {
- auto block_guard = detail::ScopeExit([&] {
- system.Kernel().MemoryManager().Free(page_linked_list, remaining_pages, memory_pool);
- process->GetResourceLimit()->Release(ResourceType::PhysicalMemory, remaining_size);
- });
- CASCADE_CODE(system.Kernel().MemoryManager().Allocate(page_linked_list, remaining_pages,
- memory_pool));
+ CASCADE_CODE(
+ system.Kernel().MemoryManager().Allocate(page_linked_list, remaining_pages, memory_pool));
- block_guard.Cancel();
- }
+ // We succeeded, so commit the memory reservation.
+ memory_reservation.Commit();
MapPhysicalMemory(page_linked_list, addr, end_addr);
@@ -454,12 +452,12 @@ ResultCode PageTable::UnmapPhysicalMemory(VAddr addr, std::size_t size) {
block_manager->IterateForRange(addr, end_addr, [&](const MemoryInfo& info) {
if (info.state == MemoryState::Normal) {
if (info.attribute != MemoryAttribute::None) {
- result = ERR_INVALID_ADDRESS_STATE;
+ result = ResultInvalidCurrentMemory;
return;
}
mapped_size += GetSizeInRange(info, addr, end_addr);
} else if (info.state != MemoryState::Free) {
- result = ERR_INVALID_ADDRESS_STATE;
+ result = ResultInvalidCurrentMemory;
}
});
@@ -474,7 +472,7 @@ ResultCode PageTable::UnmapPhysicalMemory(VAddr addr, std::size_t size) {
CASCADE_CODE(UnmapMemory(addr, size));
auto process{system.Kernel().CurrentProcess()};
- process->GetResourceLimit()->Release(ResourceType::PhysicalMemory, mapped_size);
+ process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size);
physical_memory_usage -= mapped_size;
return RESULT_SUCCESS;
@@ -526,7 +524,7 @@ ResultCode PageTable::Map(VAddr dst_addr, VAddr src_addr, std::size_t size) {
MemoryAttribute::Mask, MemoryAttribute::None, MemoryAttribute::IpcAndDeviceMapped));
if (IsRegionMapped(dst_addr, size)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
PageLinkedList page_linked_list;
@@ -577,7 +575,7 @@ ResultCode PageTable::Unmap(VAddr dst_addr, VAddr src_addr, std::size_t size) {
AddRegionToPages(dst_addr, num_pages, dst_pages);
if (!dst_pages.IsEqual(src_pages)) {
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
{
@@ -626,11 +624,11 @@ ResultCode PageTable::MapPages(VAddr addr, PageLinkedList& page_linked_list, Mem
const std::size_t size{num_pages * PageSize};
if (!CanContain(addr, size, state)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (IsRegionMapped(addr, num_pages * PageSize)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
CASCADE_CODE(MapPages(addr, page_linked_list, perm));
@@ -670,6 +668,11 @@ ResultCode PageTable::SetCodeMemoryPermission(VAddr addr, std::size_t size, Memo
return RESULT_SUCCESS;
}
+ if ((prev_perm & MemoryPermission::Execute) != (perm & MemoryPermission::Execute)) {
+ // Memory execution state is changing, invalidate CPU cache range
+ system.InvalidateCpuInstructionCacheRange(addr, size);
+ }
+
const std::size_t num_pages{size / PageSize};
const OperationType operation{(perm & MemoryPermission::Execute) != MemoryPermission::None
? OperationType::ChangePermissionsAndRefresh
@@ -763,7 +766,7 @@ ResultCode PageTable::SetHeapCapacity(std::size_t new_heap_capacity) {
ResultVal<VAddr> PageTable::SetHeapSize(std::size_t size) {
if (size > heap_region_end - heap_region_start) {
- return ERR_OUT_OF_MEMORY;
+ return ResultOutOfMemory;
}
const u64 previous_heap_size{GetHeapSize()};
@@ -776,10 +779,14 @@ ResultVal<VAddr> PageTable::SetHeapSize(std::size_t size) {
const u64 delta{size - previous_heap_size};
- auto process{system.Kernel().CurrentProcess()};
- if (process->GetResourceLimit() && delta != 0 &&
- !process->GetResourceLimit()->Reserve(ResourceType::PhysicalMemory, delta)) {
- return ERR_RESOURCE_LIMIT_EXCEEDED;
+ // Reserve memory for the heap extension.
+ KScopedResourceReservation memory_reservation(
+ system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory,
+ delta);
+
+ if (!memory_reservation.Succeeded()) {
+ LOG_ERROR(Kernel, "Could not reserve heap extension of size {:X} bytes", delta);
+ return ResultResourceLimitedExceeded;
}
PageLinkedList page_linked_list;
@@ -789,12 +796,15 @@ ResultVal<VAddr> PageTable::SetHeapSize(std::size_t size) {
system.Kernel().MemoryManager().Allocate(page_linked_list, num_pages, memory_pool));
if (IsRegionMapped(current_heap_addr, delta)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
CASCADE_CODE(
Operate(current_heap_addr, num_pages, page_linked_list, OperationType::MapGroup));
+ // Succeeded in allocation, commit the resource reservation
+ memory_reservation.Commit();
+
block_manager->Update(current_heap_addr, num_pages, MemoryState::Normal,
MemoryPermission::ReadAndWrite);
@@ -811,17 +821,17 @@ ResultVal<VAddr> PageTable::AllocateAndMapMemory(std::size_t needed_num_pages, s
std::lock_guard lock{page_table_lock};
if (!CanContain(region_start, region_num_pages * PageSize, state)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (region_num_pages <= needed_num_pages) {
- return ERR_OUT_OF_MEMORY;
+ return ResultOutOfMemory;
}
const VAddr addr{
AllocateVirtualMemory(region_start, region_num_pages, needed_num_pages, align)};
if (!addr) {
- return ERR_OUT_OF_MEMORY;
+ return ResultOutOfMemory;
}
if (is_map_only) {
@@ -1002,8 +1012,8 @@ constexpr VAddr PageTable::GetRegionAddress(MemoryState state) const {
case MemoryState::Shared:
case MemoryState::AliasCode:
case MemoryState::AliasCodeData:
- case MemoryState::Transfered:
- case MemoryState::SharedTransfered:
+ case MemoryState::Transferred:
+ case MemoryState::SharedTransferred:
case MemoryState::SharedCode:
case MemoryState::GeneratedCode:
case MemoryState::CodeOut:
@@ -1037,8 +1047,8 @@ constexpr std::size_t PageTable::GetRegionSize(MemoryState state) const {
case MemoryState::Shared:
case MemoryState::AliasCode:
case MemoryState::AliasCodeData:
- case MemoryState::Transfered:
- case MemoryState::SharedTransfered:
+ case MemoryState::Transferred:
+ case MemoryState::SharedTransferred:
case MemoryState::SharedCode:
case MemoryState::GeneratedCode:
case MemoryState::CodeOut:
@@ -1075,8 +1085,8 @@ constexpr bool PageTable::CanContain(VAddr addr, std::size_t size, MemoryState s
case MemoryState::AliasCodeData:
case MemoryState::Stack:
case MemoryState::ThreadLocal:
- case MemoryState::Transfered:
- case MemoryState::SharedTransfered:
+ case MemoryState::Transferred:
+ case MemoryState::SharedTransferred:
case MemoryState::SharedCode:
case MemoryState::GeneratedCode:
case MemoryState::CodeOut:
@@ -1100,13 +1110,13 @@ constexpr ResultCode PageTable::CheckMemoryState(const MemoryInfo& info, MemoryS
MemoryAttribute attr) const {
// Validate the states match expectation
if ((info.state & state_mask) != state) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if ((info.perm & perm_mask) != perm) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if ((info.attribute & attr_mask) != attr) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
return RESULT_SUCCESS;
@@ -1133,14 +1143,14 @@ ResultCode PageTable::CheckMemoryState(MemoryState* out_state, MemoryPermission*
while (true) {
// Validate the current block
if (!(info.state == first_state)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (!(info.perm == first_perm)) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (!((info.attribute | static_cast<MemoryAttribute>(ignore_attr)) ==
(first_attr | static_cast<MemoryAttribute>(ignore_attr)))) {
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
// Validate against the provided masks
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
deleted file mode 100644
index 8f6c944d1..000000000
--- a/src/core/hle/kernel/mutex.cpp
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/mutex.h"
-#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/result.h"
-#include "core/memory.h"
-
-namespace Kernel {
-
-/// Returns the number of threads that are waiting for a mutex, and the highest priority one among
-/// those.
-static std::pair<std::shared_ptr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
- const std::shared_ptr<Thread>& current_thread, VAddr mutex_addr) {
-
- std::shared_ptr<Thread> highest_priority_thread;
- u32 num_waiters = 0;
-
- for (const auto& thread : current_thread->GetMutexWaitingThreads()) {
- if (thread->GetMutexWaitAddress() != mutex_addr)
- continue;
-
- ++num_waiters;
- if (highest_priority_thread == nullptr ||
- thread->GetPriority() < highest_priority_thread->GetPriority()) {
- highest_priority_thread = thread;
- }
- }
-
- return {highest_priority_thread, num_waiters};
-}
-
-/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
-static void TransferMutexOwnership(VAddr mutex_addr, std::shared_ptr<Thread> current_thread,
- std::shared_ptr<Thread> new_owner) {
- current_thread->RemoveMutexWaiter(new_owner);
- const auto threads = current_thread->GetMutexWaitingThreads();
- for (const auto& thread : threads) {
- if (thread->GetMutexWaitAddress() != mutex_addr)
- continue;
-
- ASSERT(thread->GetLockOwner() == current_thread.get());
- current_thread->RemoveMutexWaiter(thread);
- if (new_owner != thread)
- new_owner->AddMutexWaiter(thread);
- }
-}
-
-Mutex::Mutex(Core::System& system) : system{system} {}
-Mutex::~Mutex() = default;
-
-ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
- Handle requesting_thread_handle) {
- // The mutex address must be 4-byte aligned
- if ((address % sizeof(u32)) != 0) {
- LOG_ERROR(Kernel, "Address is not 4-byte aligned! address={:016X}", address);
- return ERR_INVALID_ADDRESS;
- }
-
- auto& kernel = system.Kernel();
- std::shared_ptr<Thread> current_thread =
- SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
- {
- SchedulerLock lock(kernel);
- // The mutex address must be 4-byte aligned
- if ((address % sizeof(u32)) != 0) {
- return ERR_INVALID_ADDRESS;
- }
-
- const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
- std::shared_ptr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
- std::shared_ptr<Thread> requesting_thread =
- handle_table.Get<Thread>(requesting_thread_handle);
-
- // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of
- // another thread.
- ASSERT(requesting_thread == current_thread);
-
- current_thread->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
-
- const u32 addr_value = system.Memory().Read32(address);
-
- // If the mutex isn't being held, just return success.
- if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
- return RESULT_SUCCESS;
- }
-
- if (holding_thread == nullptr) {
- return ERR_INVALID_HANDLE;
- }
-
- // Wait until the mutex is released
- current_thread->SetMutexWaitAddress(address);
- current_thread->SetWaitHandle(requesting_thread_handle);
-
- current_thread->SetStatus(ThreadStatus::WaitMutex);
-
- // Update the lock holder thread's priority to prevent priority inversion.
- holding_thread->AddMutexWaiter(current_thread);
- }
-
- {
- SchedulerLock lock(kernel);
- auto* owner = current_thread->GetLockOwner();
- if (owner != nullptr) {
- owner->RemoveMutexWaiter(current_thread);
- }
- }
- return current_thread->GetSignalingResult();
-}
-
-std::pair<ResultCode, std::shared_ptr<Thread>> Mutex::Unlock(std::shared_ptr<Thread> owner,
- VAddr address) {
- // The mutex address must be 4-byte aligned
- if ((address % sizeof(u32)) != 0) {
- LOG_ERROR(Kernel, "Address is not 4-byte aligned! address={:016X}", address);
- return {ERR_INVALID_ADDRESS, nullptr};
- }
-
- auto [new_owner, num_waiters] = GetHighestPriorityMutexWaitingThread(owner, address);
- if (new_owner == nullptr) {
- system.Memory().Write32(address, 0);
- return {RESULT_SUCCESS, nullptr};
- }
- // Transfer the ownership of the mutex from the previous owner to the new one.
- TransferMutexOwnership(address, owner, new_owner);
- u32 mutex_value = new_owner->GetWaitHandle();
- if (num_waiters >= 2) {
- // Notify the guest that there are still some threads waiting for the mutex
- mutex_value |= Mutex::MutexHasWaitersFlag;
- }
- new_owner->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
- new_owner->SetLockOwner(nullptr);
- new_owner->ResumeFromWait();
-
- system.Memory().Write32(address, mutex_value);
- return {RESULT_SUCCESS, new_owner};
-}
-
-ResultCode Mutex::Release(VAddr address) {
- auto& kernel = system.Kernel();
- SchedulerLock lock(kernel);
-
- std::shared_ptr<Thread> current_thread =
- SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
-
- auto [result, new_owner] = Unlock(current_thread, address);
-
- if (result != RESULT_SUCCESS && new_owner != nullptr) {
- new_owner->SetSynchronizationResults(nullptr, result);
- }
-
- return result;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h
deleted file mode 100644
index 3b81dc3df..000000000
--- a/src/core/hle/kernel/mutex.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_types.h"
-
-union ResultCode;
-
-namespace Core {
-class System;
-}
-
-namespace Kernel {
-
-class Mutex final {
-public:
- explicit Mutex(Core::System& system);
- ~Mutex();
-
- /// Flag that indicates that a mutex still has threads waiting for it.
- static constexpr u32 MutexHasWaitersFlag = 0x40000000;
- /// Mask of the bits in a mutex address value that contain the mutex owner.
- static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
-
- /// Attempts to acquire a mutex at the specified address.
- ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
- Handle requesting_thread_handle);
-
- /// Unlocks a mutex for owner at address
- std::pair<ResultCode, std::shared_ptr<Thread>> Unlock(std::shared_ptr<Thread> owner,
- VAddr address);
-
- /// Releases the mutex at the specified address.
- ResultCode Release(VAddr address);
-
-private:
- Core::System& system;
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp
index 2c571792b..d7f40c403 100644
--- a/src/core/hle/kernel/object.cpp
+++ b/src/core/hle/kernel/object.cpp
@@ -8,7 +8,10 @@
namespace Kernel {
-Object::Object(KernelCore& kernel) : kernel{kernel}, object_id{kernel.CreateNewObjectID()} {}
+Object::Object(KernelCore& kernel_)
+ : kernel{kernel_}, object_id{kernel_.CreateNewObjectID()}, name{"[UNKNOWN KERNEL OBJECT]"} {}
+Object::Object(KernelCore& kernel_, std::string&& name_)
+ : kernel{kernel_}, object_id{kernel_.CreateNewObjectID()}, name{std::move(name_)} {}
Object::~Object() = default;
bool Object::IsWaitable() const {
@@ -21,6 +24,7 @@ bool Object::IsWaitable() const {
return true;
case HandleType::Unknown:
+ case HandleType::Event:
case HandleType::WritableEvent:
case HandleType::SharedMemory:
case HandleType::TransferMemory:
diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h
index e3391e2af..501e58b33 100644
--- a/src/core/hle/kernel/object.h
+++ b/src/core/hle/kernel/object.h
@@ -18,6 +18,7 @@ using Handle = u32;
enum class HandleType : u32 {
Unknown,
+ Event,
WritableEvent,
ReadableEvent,
SharedMemory,
@@ -34,7 +35,8 @@ enum class HandleType : u32 {
class Object : NonCopyable, public std::enable_shared_from_this<Object> {
public:
- explicit Object(KernelCore& kernel);
+ explicit Object(KernelCore& kernel_);
+ explicit Object(KernelCore& kernel_, std::string&& name_);
virtual ~Object();
/// Returns a unique identifier for the object. For debugging purposes only.
@@ -46,22 +48,30 @@ public:
return "[BAD KERNEL OBJECT TYPE]";
}
virtual std::string GetName() const {
- return "[UNKNOWN KERNEL OBJECT]";
+ return name;
}
virtual HandleType GetHandleType() const = 0;
+ void Close() {
+ // TODO(bunnei): This is a placeholder to decrement the reference count, which we will use
+ // when we implement KAutoObject instead of using shared_ptr.
+ }
+
/**
* Check if a thread can wait on the object
* @return True if a thread can wait on the object, otherwise false
*/
bool IsWaitable() const;
+ virtual void Finalize() = 0;
+
protected:
/// The kernel instance this object was created under.
KernelCore& kernel;
private:
std::atomic<u32> object_id{0};
+ std::string name;
};
template <typename T>
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index c6bbdb080..7fea45f96 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -2,54 +2,60 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/assert.h"
-#include "common/logging/log.h"
#include "common/spin_lock.h"
-#include "core/arm/arm_interface.h"
-#ifdef ARCHITECTURE_x86_64
+#include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
-#endif
-#include "core/arm/cpu_interrupt_handler.h"
-#include "core/arm/exclusive_monitor.h"
-#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
namespace Kernel {
-PhysicalCore::PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler,
- Core::CPUInterruptHandler& interrupt_handler)
- : interrupt_handler{interrupt_handler}, core_index{id}, scheduler{scheduler} {
-
- guard = std::make_unique<Common::SpinLock>();
-}
+PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system,
+ Kernel::KScheduler& scheduler, Core::CPUInterrupts& interrupts)
+ : core_index{core_index}, system{system}, scheduler{scheduler},
+ interrupts{interrupts}, guard{std::make_unique<Common::SpinLock>()} {}
PhysicalCore::~PhysicalCore() = default;
-void PhysicalCore::Idle() {
- interrupt_handler.AwaitInterrupt();
+void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) {
+#ifdef ARCHITECTURE_x86_64
+ auto& kernel = system.Kernel();
+ if (is_64_bit) {
+ arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
+ system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
+ } else {
+ arm_interface = std::make_unique<Core::ARM_Dynarmic_32>(
+ system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
+ }
+#else
+#error Platform not supported yet.
+#endif
}
-void PhysicalCore::Shutdown() {
- scheduler.Shutdown();
+void PhysicalCore::Run() {
+ arm_interface->Run();
+}
+
+void PhysicalCore::Idle() {
+ interrupts[core_index].AwaitInterrupt();
}
bool PhysicalCore::IsInterrupted() const {
- return interrupt_handler.IsInterrupted();
+ return interrupts[core_index].IsInterrupted();
}
void PhysicalCore::Interrupt() {
guard->lock();
- interrupt_handler.SetInterrupt(true);
+ interrupts[core_index].SetInterrupt(true);
guard->unlock();
}
void PhysicalCore::ClearInterrupt() {
guard->lock();
- interrupt_handler.SetInterrupt(false);
+ interrupts[core_index].SetInterrupt(false);
guard->unlock();
}
diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h
index d7a7a951c..f2b0911aa 100644
--- a/src/core/hle/kernel/physical_core.h
+++ b/src/core/hle/kernel/physical_core.h
@@ -4,19 +4,21 @@
#pragma once
+#include <array>
#include <cstddef>
#include <memory>
+#include "core/arm/arm_interface.h"
+
namespace Common {
class SpinLock;
}
namespace Kernel {
-class Scheduler;
+class KScheduler;
} // namespace Kernel
namespace Core {
-class ARM_Interface;
class CPUInterruptHandler;
class ExclusiveMonitor;
class System;
@@ -26,17 +28,24 @@ namespace Kernel {
class PhysicalCore {
public:
- PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler,
- Core::CPUInterruptHandler& interrupt_handler);
+ PhysicalCore(std::size_t core_index, Core::System& system, Kernel::KScheduler& scheduler,
+ Core::CPUInterrupts& interrupts);
~PhysicalCore();
PhysicalCore(const PhysicalCore&) = delete;
PhysicalCore& operator=(const PhysicalCore&) = delete;
PhysicalCore(PhysicalCore&&) = default;
- PhysicalCore& operator=(PhysicalCore&&) = default;
+ PhysicalCore& operator=(PhysicalCore&&) = delete;
+
+ /// Initialize the core for the specified parameters.
+ void Initialize(bool is_64_bit);
+
+ /// Execute current jit state
+ void Run();
void Idle();
+
/// Interrupt this physical core.
void Interrupt();
@@ -46,8 +55,17 @@ public:
/// Check if this core is interrupted
bool IsInterrupted() const;
- // Shutdown this physical core.
- void Shutdown();
+ bool IsInitialized() const {
+ return arm_interface != nullptr;
+ }
+
+ Core::ARM_Interface& ArmInterface() {
+ return *arm_interface;
+ }
+
+ const Core::ARM_Interface& ArmInterface() const {
+ return *arm_interface;
+ }
bool IsMainCore() const {
return core_index == 0;
@@ -61,19 +79,21 @@ public:
return core_index;
}
- Kernel::Scheduler& Scheduler() {
+ Kernel::KScheduler& Scheduler() {
return scheduler;
}
- const Kernel::Scheduler& Scheduler() const {
+ const Kernel::KScheduler& Scheduler() const {
return scheduler;
}
private:
- Core::CPUInterruptHandler& interrupt_handler;
- std::size_t core_index;
- Kernel::Scheduler& scheduler;
+ const std::size_t core_index;
+ Core::System& system;
+ Kernel::KScheduler& scheduler;
+ Core::CPUInterrupts& interrupts;
std::unique_ptr<Common::SpinLock> guard;
+ std::unique_ptr<Core::ARM_Interface> arm_interface;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index ff9d9248b..47b3ac57b 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -4,6 +4,7 @@
#include <algorithm>
#include <bitset>
+#include <ctime>
#include <memory>
#include <random>
#include "common/alignment.h"
@@ -13,15 +14,16 @@
#include "core/device_memory.h"
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/code_set.h"
-#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/k_resource_limit.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/memory_block_manager.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/memory/slab_heap.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/resource_limit.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/svc_results.h"
#include "core/hle/lock.h"
#include "core/memory.h"
#include "core/settings.h"
@@ -37,11 +39,11 @@ namespace {
*/
void SetupMainThread(Core::System& system, Process& owner_process, u32 priority, VAddr stack_top) {
const VAddr entry_point = owner_process.PageTable().GetCodeRegionStart();
- ThreadType type = THREADTYPE_USER;
- auto thread_res = Thread::Create(system, type, "main", entry_point, priority, 0,
- owner_process.GetIdealCore(), stack_top, &owner_process);
+ ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::Threads, 1));
+ auto thread_res = KThread::Create(system, ThreadType::User, "main", entry_point, priority, 0,
+ owner_process.GetIdealCoreId(), stack_top, &owner_process);
- std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap();
+ std::shared_ptr<KThread> thread = std::move(thread_res).Unwrap();
// Register 1 must be a handle to the main thread
const Handle thread_handle = owner_process.GetHandleTable().Create(thread).Unwrap();
@@ -53,8 +55,8 @@ void SetupMainThread(Core::System& system, Process& owner_process, u32 priority,
auto& kernel = system.Kernel();
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
{
- SchedulerLock lock{kernel};
- thread->SetStatus(ThreadStatus::Ready);
+ KScopedSchedulerLock lock{kernel};
+ thread->SetState(ThreadState::Runnable);
}
}
} // Anonymous namespace
@@ -116,14 +118,17 @@ std::shared_ptr<Process> Process::Create(Core::System& system, std::string name,
std::shared_ptr<Process> process = std::make_shared<Process>(system);
process->name = std::move(name);
- process->resource_limit = ResourceLimit::Create(kernel);
+
+ // TODO: This is inaccurate
+ // The process should hold a reference to the kernel-wide resource limit.
+ process->resource_limit = std::make_shared<KResourceLimit>(kernel, system);
process->status = ProcessStatus::Created;
process->program_id = 0;
process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID()
: kernel.CreateNewUserProcessID();
process->capabilities.InitializeForMetadatalessProcess();
- std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(0));
+ std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr)));
std::uniform_int_distribution<u64> distribution;
std::generate(process->random_entropy.begin(), process->random_entropy.end(),
[&] { return distribution(rng); });
@@ -132,12 +137,32 @@ std::shared_ptr<Process> Process::Create(Core::System& system, std::string name,
return process;
}
-std::shared_ptr<ResourceLimit> Process::GetResourceLimit() const {
+std::shared_ptr<KResourceLimit> Process::GetResourceLimit() const {
return resource_limit;
}
+void Process::IncrementThreadCount() {
+ ASSERT(num_threads >= 0);
+ num_created_threads++;
+
+ if (const auto count = ++num_threads; count > peak_num_threads) {
+ peak_num_threads = count;
+ }
+}
+
+void Process::DecrementThreadCount() {
+ ASSERT(num_threads > 0);
+
+ if (const auto count = --num_threads; count == 0) {
+ UNIMPLEMENTED_MSG("Process termination is not implemented!");
+ }
+}
+
u64 Process::GetTotalPhysicalMemoryAvailable() const {
- const u64 capacity{resource_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory) +
+ // TODO: This is expected to always return the application memory pool size after accurately
+ // reserving kernel resources. The current workaround uses a process-local resource limit of
+ // application memory pool size, which is inaccurate.
+ const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemory) +
page_table->GetTotalHeapSize() + GetSystemResourceSize() + image_size +
main_thread_stack_size};
@@ -161,68 +186,79 @@ u64 Process::GetTotalPhysicalMemoryUsedWithoutSystemResource() const {
return GetTotalPhysicalMemoryUsed() - GetSystemResourceUsage();
}
-void Process::InsertConditionVariableThread(std::shared_ptr<Thread> thread) {
- VAddr cond_var_addr = thread->GetCondVarWaitAddress();
- std::list<std::shared_ptr<Thread>>& thread_list = cond_var_threads[cond_var_addr];
- auto it = thread_list.begin();
- while (it != thread_list.end()) {
- const std::shared_ptr<Thread> current_thread = *it;
- if (current_thread->GetPriority() > thread->GetPriority()) {
- thread_list.insert(it, thread);
- return;
+bool Process::ReleaseUserException(KThread* thread) {
+ KScopedSchedulerLock sl{kernel};
+
+ if (exception_thread == thread) {
+ exception_thread = nullptr;
+
+ // Remove waiter thread.
+ s32 num_waiters{};
+ KThread* next = thread->RemoveWaiterByKey(
+ std::addressof(num_waiters),
+ reinterpret_cast<uintptr_t>(std::addressof(exception_thread)));
+ if (next != nullptr) {
+ if (next->GetState() == ThreadState::Waiting) {
+ next->SetState(ThreadState::Runnable);
+ } else {
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
+ }
}
- ++it;
+
+ return true;
+ } else {
+ return false;
}
- thread_list.push_back(thread);
}
-void Process::RemoveConditionVariableThread(std::shared_ptr<Thread> thread) {
- VAddr cond_var_addr = thread->GetCondVarWaitAddress();
- std::list<std::shared_ptr<Thread>>& thread_list = cond_var_threads[cond_var_addr];
- auto it = thread_list.begin();
- while (it != thread_list.end()) {
- const std::shared_ptr<Thread> current_thread = *it;
- if (current_thread.get() == thread.get()) {
- thread_list.erase(it);
- return;
- }
- ++it;
- }
+void Process::PinCurrentThread() {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Get the current thread.
+ const s32 core_id = GetCurrentCoreId(kernel);
+ KThread* cur_thread = GetCurrentThreadPointer(kernel);
+
+ // Pin it.
+ PinThread(core_id, cur_thread);
+ cur_thread->Pin();
+
+ // An update is needed.
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
}
-std::vector<std::shared_ptr<Thread>> Process::GetConditionVariableThreads(
- const VAddr cond_var_addr) {
- std::vector<std::shared_ptr<Thread>> result{};
- std::list<std::shared_ptr<Thread>>& thread_list = cond_var_threads[cond_var_addr];
- auto it = thread_list.begin();
- while (it != thread_list.end()) {
- std::shared_ptr<Thread> current_thread = *it;
- result.push_back(current_thread);
- ++it;
- }
- return result;
+void Process::UnpinCurrentThread() {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+ // Get the current thread.
+ const s32 core_id = GetCurrentCoreId(kernel);
+ KThread* cur_thread = GetCurrentThreadPointer(kernel);
+
+ // Unpin it.
+ cur_thread->Unpin();
+ UnpinThread(core_id, cur_thread);
+
+ // An update is needed.
+ KScheduler::SetSchedulerUpdateNeeded(kernel);
}
-void Process::RegisterThread(const Thread* thread) {
+void Process::RegisterThread(const KThread* thread) {
thread_list.push_back(thread);
}
-void Process::UnregisterThread(const Thread* thread) {
+void Process::UnregisterThread(const KThread* thread) {
thread_list.remove(thread);
}
-ResultCode Process::ClearSignalState() {
- SchedulerLock lock(system.Kernel());
- if (status == ProcessStatus::Exited) {
- LOG_ERROR(Kernel, "called on a terminated process instance.");
- return ERR_INVALID_STATE;
- }
+ResultCode Process::Reset() {
+ // Lock the process and the scheduler.
+ KScopedLightLock lk(state_lock);
+ KScopedSchedulerLock sl{kernel};
- if (!is_signaled) {
- LOG_ERROR(Kernel, "called on a process instance that isn't signaled.");
- return ERR_INVALID_STATE;
- }
+ // Validate that we're in a state that we can reset.
+ R_UNLESS(status != ProcessStatus::Exited, ResultInvalidState);
+ R_UNLESS(is_signaled, ResultInvalidState);
+ // Clear signaled.
is_signaled = false;
return RESULT_SUCCESS;
}
@@ -235,6 +271,17 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
system_resource_size = metadata.GetSystemResourceSize();
image_size = code_size;
+ // Set initial resource limits
+ resource_limit->SetLimitValue(
+ LimitableResource::PhysicalMemory,
+ kernel.MemoryManager().GetSize(Memory::MemoryManager::Pool::Application));
+ KScopedResourceReservation memory_reservation(resource_limit, LimitableResource::PhysicalMemory,
+ code_size + system_resource_size);
+ if (!memory_reservation.Succeeded()) {
+ LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes",
+ code_size + system_resource_size);
+ return ResultResourceLimitedExceeded;
+ }
// Initialize proces address space
if (const ResultCode result{
page_table->InitializeForProcess(metadata.GetAddressSpaceType(), false, 0x8000000,
@@ -276,24 +323,22 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
UNREACHABLE();
}
- // Set initial resource limits
- resource_limit->SetLimitValue(
- ResourceType::PhysicalMemory,
- kernel.MemoryManager().GetSize(Memory::MemoryManager::Pool::Application));
- resource_limit->SetLimitValue(ResourceType::Threads, 608);
- resource_limit->SetLimitValue(ResourceType::Events, 700);
- resource_limit->SetLimitValue(ResourceType::TransferMemory, 128);
- resource_limit->SetLimitValue(ResourceType::Sessions, 894);
- ASSERT(resource_limit->Reserve(ResourceType::PhysicalMemory, code_size));
+ resource_limit->SetLimitValue(LimitableResource::Threads, 608);
+ resource_limit->SetLimitValue(LimitableResource::Events, 700);
+ resource_limit->SetLimitValue(LimitableResource::TransferMemory, 128);
+ resource_limit->SetLimitValue(LimitableResource::Sessions, 894);
// Create TLS region
tls_region_address = CreateTLSRegion();
+ memory_reservation.Commit();
return handle_table.SetSize(capabilities.GetHandleTableSize());
}
void Process::Run(s32 main_thread_priority, u64 stack_size) {
AllocateMainThreadStack(stack_size);
+ resource_limit->Reserve(LimitableResource::Threads, 1);
+ resource_limit->Reserve(LimitableResource::PhysicalMemory, main_thread_stack_size);
const std::size_t heap_capacity{memory_usage_capacity - main_thread_stack_size - image_size};
ASSERT(!page_table->SetHeapCapacity(heap_capacity).IsError());
@@ -301,34 +346,37 @@ void Process::Run(s32 main_thread_priority, u64 stack_size) {
ChangeStatus(ProcessStatus::Running);
SetupMainThread(system, *this, main_thread_priority, main_thread_stack_top);
- resource_limit->Reserve(ResourceType::Threads, 1);
- resource_limit->Reserve(ResourceType::PhysicalMemory, main_thread_stack_size);
}
void Process::PrepareForTermination() {
ChangeStatus(ProcessStatus::Exiting);
- const auto stop_threads = [this](const std::vector<std::shared_ptr<Thread>>& thread_list) {
+ const auto stop_threads = [this](const std::vector<std::shared_ptr<KThread>>& thread_list) {
for (auto& thread : thread_list) {
if (thread->GetOwnerProcess() != this)
continue;
- if (thread.get() == system.CurrentScheduler().GetCurrentThread())
+ if (thread.get() == kernel.CurrentScheduler()->GetCurrentThread())
continue;
// TODO(Subv): When are the other running/ready threads terminated?
- ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynch,
+ ASSERT_MSG(thread->GetState() == ThreadState::Waiting,
"Exiting processes with non-waiting threads is currently unimplemented");
- thread->Stop();
+ thread->Exit();
}
};
- stop_threads(system.GlobalScheduler().GetThreadList());
+ stop_threads(system.GlobalSchedulerContext().GetThreadList());
FreeTLSRegion(tls_region_address);
tls_region_address = 0;
+ if (resource_limit) {
+ resource_limit->Release(LimitableResource::PhysicalMemory,
+ main_thread_stack_size + image_size);
+ }
+
ChangeStatus(ProcessStatus::Exited);
}
@@ -346,7 +394,7 @@ static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) {
}
VAddr Process::CreateTLSRegion() {
- SchedulerLock lock(system.Kernel());
+ KScopedSchedulerLock lock(system.Kernel());
if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)};
tls_page_iter != tls_pages.cend()) {
return *tls_page_iter->ReserveSlot();
@@ -377,7 +425,7 @@ VAddr Process::CreateTLSRegion() {
}
void Process::FreeTLSRegion(VAddr tls_address) {
- SchedulerLock lock(system.Kernel());
+ KScopedSchedulerLock lock(system.Kernel());
const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE);
auto iter =
std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) {
@@ -405,21 +453,18 @@ void Process::LoadModule(CodeSet code_set, VAddr base_addr) {
ReprotectSegment(code_set.DataSegment(), Memory::MemoryPermission::ReadAndWrite);
}
+bool Process::IsSignaled() const {
+ ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+ return is_signaled;
+}
+
Process::Process(Core::System& system)
- : SynchronizationObject{system.Kernel()}, page_table{std::make_unique<Memory::PageTable>(
- system)},
- handle_table{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {}
+ : KSynchronizationObject{system.Kernel()},
+ page_table{std::make_unique<Memory::PageTable>(system)}, handle_table{system.Kernel()},
+ address_arbiter{system}, condition_var{system}, state_lock{system.Kernel()}, system{system} {}
Process::~Process() = default;
-void Process::Acquire(Thread* thread) {
- ASSERT_MSG(!ShouldWait(thread), "Object unavailable!");
-}
-
-bool Process::ShouldWait(const Thread* thread) const {
- return !is_signaled;
-}
-
void Process::ChangeStatus(ProcessStatus new_status) {
if (status == new_status) {
return;
@@ -427,7 +472,7 @@ void Process::ChangeStatus(ProcessStatus new_status) {
status = new_status;
is_signaled = true;
- Signal();
+ NotifyAvailable();
}
ResultCode Process::AllocateMainThreadStack(std::size_t stack_size) {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index f45cb5674..320b0f347 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -11,11 +11,11 @@
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
-#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/mutex.h"
+#include "core/hle/kernel/k_address_arbiter.h"
+#include "core/hle/kernel/k_condition_variable.h"
+#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/process_capability.h"
-#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/result.h"
namespace Core {
@@ -29,8 +29,8 @@ class ProgramMetadata;
namespace Kernel {
class KernelCore;
-class ResourceLimit;
-class Thread;
+class KResourceLimit;
+class KThread;
class TLSPage;
struct CodeSet;
@@ -63,7 +63,7 @@ enum class ProcessStatus {
DebugBreak,
};
-class Process final : public SynchronizationObject {
+class Process final : public KSynchronizationObject {
public:
explicit Process(Core::System& system);
~Process() override;
@@ -123,24 +123,30 @@ public:
return handle_table;
}
- /// Gets a reference to the process' address arbiter.
- AddressArbiter& GetAddressArbiter() {
- return address_arbiter;
+ ResultCode SignalToAddress(VAddr address) {
+ return condition_var.SignalToAddress(address);
}
- /// Gets a const reference to the process' address arbiter.
- const AddressArbiter& GetAddressArbiter() const {
- return address_arbiter;
+ ResultCode WaitForAddress(Handle handle, VAddr address, u32 tag) {
+ return condition_var.WaitForAddress(handle, address, tag);
}
- /// Gets a reference to the process' mutex lock.
- Mutex& GetMutex() {
- return mutex;
+ void SignalConditionVariable(u64 cv_key, int32_t count) {
+ return condition_var.Signal(cv_key, count);
}
- /// Gets a const reference to the process' mutex lock
- const Mutex& GetMutex() const {
- return mutex;
+ ResultCode WaitConditionVariable(VAddr address, u64 cv_key, u32 tag, s64 ns) {
+ return condition_var.Wait(address, cv_key, tag, ns);
+ }
+
+ ResultCode SignalAddressArbiter(VAddr address, Svc::SignalType signal_type, s32 value,
+ s32 count) {
+ return address_arbiter.SignalToAddress(address, signal_type, value, count);
+ }
+
+ ResultCode WaitAddressArbiter(VAddr address, Svc::ArbitrationType arb_type, s32 value,
+ s64 timeout) {
+ return address_arbiter.WaitForAddress(address, arb_type, value, timeout);
}
/// Gets the address to the process' dedicated TLS region.
@@ -164,13 +170,18 @@ public:
}
/// Gets the resource limit descriptor for this process
- std::shared_ptr<ResourceLimit> GetResourceLimit() const;
+ std::shared_ptr<KResourceLimit> GetResourceLimit() const;
/// Gets the ideal CPU core ID for this process
- u8 GetIdealCore() const {
+ u8 GetIdealCoreId() const {
return ideal_core;
}
+ /// Checks if the specified thread priority is valid.
+ bool CheckThreadPriority(s32 prio) const {
+ return ((1ULL << prio) & GetPriorityMask()) != 0;
+ }
+
/// Gets the bitmask of allowed cores that this process' threads can run on.
u64 GetCoreMask() const {
return capabilities.GetCoreMask();
@@ -206,6 +217,14 @@ public:
return is_64bit_process;
}
+ [[nodiscard]] bool IsSuspended() const {
+ return is_suspended;
+ }
+
+ void SetSuspended(bool suspended) {
+ is_suspended = suspended;
+ }
+
/// Gets the total running time of the process instance in ticks.
u64 GetCPUTimeTicks() const {
return total_process_running_time_ticks;
@@ -216,6 +235,43 @@ public:
total_process_running_time_ticks += ticks;
}
+ /// Gets the process schedule count, used for thread yelding
+ s64 GetScheduledCount() const {
+ return schedule_count;
+ }
+
+ /// Increments the process schedule count, used for thread yielding.
+ void IncrementScheduledCount() {
+ ++schedule_count;
+ }
+
+ void IncrementThreadCount();
+ void DecrementThreadCount();
+
+ void SetRunningThread(s32 core, KThread* thread, u64 idle_count) {
+ running_threads[core] = thread;
+ running_thread_idle_counts[core] = idle_count;
+ }
+
+ void ClearRunningThread(KThread* thread) {
+ for (size_t i = 0; i < running_threads.size(); ++i) {
+ if (running_threads[i] == thread) {
+ running_threads[i] = nullptr;
+ }
+ }
+ }
+
+ [[nodiscard]] KThread* GetRunningThread(s32 core) const {
+ return running_threads[core];
+ }
+
+ bool ReleaseUserException(KThread* thread);
+
+ [[nodiscard]] KThread* GetPinnedThread(s32 core_id) const {
+ ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+ return pinned_threads[core_id];
+ }
+
/// Gets 8 bytes of random data for svcGetInfo RandomEntropy
u64 GetRandomEntropy(std::size_t index) const {
return random_entropy.at(index);
@@ -236,26 +292,17 @@ public:
u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const;
/// Gets the list of all threads created with this process as their owner.
- const std::list<const Thread*>& GetThreadList() const {
+ const std::list<const KThread*>& GetThreadList() const {
return thread_list;
}
- /// Insert a thread into the condition variable wait container
- void InsertConditionVariableThread(std::shared_ptr<Thread> thread);
-
- /// Remove a thread from the condition variable wait container
- void RemoveConditionVariableThread(std::shared_ptr<Thread> thread);
-
- /// Obtain all condition variable threads waiting for some address
- std::vector<std::shared_ptr<Thread>> GetConditionVariableThreads(VAddr cond_var_addr);
-
/// Registers a thread as being created under this process,
/// adding it to this process' thread list.
- void RegisterThread(const Thread* thread);
+ void RegisterThread(const KThread* thread);
/// Unregisters a thread from this process, removing it
/// from this process' thread list.
- void UnregisterThread(const Thread* thread);
+ void UnregisterThread(const KThread* thread);
/// Clears the signaled state of the process if and only if it's signaled.
///
@@ -265,7 +312,7 @@ public:
/// @pre The process must be in a signaled state. If this is called on a
/// process instance that is not signaled, ERR_INVALID_STATE will be
/// returned.
- ResultCode ClearSignalState();
+ ResultCode Reset();
/**
* Loads process-specifics configuration info with metadata provided
@@ -294,6 +341,17 @@ public:
void LoadModule(CodeSet code_set, VAddr base_addr);
+ bool IsSignaled() const override;
+
+ void Finalize() override {}
+
+ void PinCurrentThread();
+ void UnpinCurrentThread();
+
+ KLightLock& GetStateLock() {
+ return state_lock;
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Thread-local storage management
@@ -304,11 +362,19 @@ public:
void FreeTLSRegion(VAddr tls_address);
private:
- /// Checks if the specified thread should wait until this process is available.
- bool ShouldWait(const Thread* thread) const override;
+ void PinThread(s32 core_id, KThread* thread) {
+ ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+ ASSERT(thread != nullptr);
+ ASSERT(pinned_threads[core_id] == nullptr);
+ pinned_threads[core_id] = thread;
+ }
- /// Acquires/locks this process for the specified thread if it's available.
- void Acquire(Thread* thread) override;
+ void UnpinThread(s32 core_id, KThread* thread) {
+ ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
+ ASSERT(thread != nullptr);
+ ASSERT(pinned_threads[core_id] == thread);
+ pinned_threads[core_id] = nullptr;
+ }
/// Changes the process status. If the status is different
/// from the current process status, then this will trigger
@@ -336,7 +402,7 @@ private:
u32 system_resource_size = 0;
/// Resource limit descriptor for this process
- std::shared_ptr<ResourceLimit> resource_limit;
+ std::shared_ptr<KResourceLimit> resource_limit;
/// The ideal CPU core for this process, threads are scheduled on this core by default.
u8 ideal_core = 0;
@@ -363,12 +429,12 @@ private:
HandleTable handle_table;
/// Per-process address arbiter.
- AddressArbiter address_arbiter;
+ KAddressArbiter address_arbiter;
/// The per-process mutex lock instance used for handling various
/// forms of services, such as lock arbitration, and condition
/// variable related facilities.
- Mutex mutex;
+ KConditionVariable condition_var;
/// Address indicating the location of the process' dedicated TLS region.
VAddr tls_region_address = 0;
@@ -377,10 +443,7 @@ private:
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy{};
/// List of threads that are running with this process as their owner.
- std::list<const Thread*> thread_list;
-
- /// List of threads waiting for a condition variable
- std::unordered_map<VAddr, std::list<std::shared_ptr<Thread>>> cond_var_threads;
+ std::list<const KThread*> thread_list;
/// Address of the top of the main thread's stack
VAddr main_thread_stack_top{};
@@ -397,6 +460,24 @@ private:
/// Name of this process
std::string name;
+ /// Schedule count of this process
+ s64 schedule_count{};
+
+ bool is_signaled{};
+ bool is_suspended{};
+
+ std::atomic<s32> num_created_threads{};
+ std::atomic<u16> num_threads{};
+ u16 peak_num_threads{};
+
+ std::array<KThread*, Core::Hardware::NUM_CPU_CORES> running_threads{};
+ std::array<u64, Core::Hardware::NUM_CPU_CORES> running_thread_idle_counts{};
+ std::array<KThread*, Core::Hardware::NUM_CPU_CORES> pinned_threads{};
+
+ KThread* exception_thread{};
+
+ KLightLock state_lock;
+
/// System context
Core::System& system;
};
diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp
index 63880f13d..7c567049e 100644
--- a/src/core/hle/kernel/process_capability.cpp
+++ b/src/core/hle/kernel/process_capability.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <bit>
+
#include "common/bit_util.h"
#include "common/logging/log.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process_capability.h"
+#include "core/hle/kernel/svc_results.h"
namespace Kernel {
namespace {
@@ -60,7 +62,7 @@ constexpr CapabilityType GetCapabilityType(u32 value) {
u32 GetFlagBitOffset(CapabilityType type) {
const auto value = static_cast<u32>(type);
- return static_cast<u32>(Common::BitSize<u32>() - Common::CountLeadingZeroes32(value));
+ return static_cast<u32>(Common::BitSize<u32>() - static_cast<u32>(std::countl_zero(value)));
}
} // Anonymous namespace
@@ -121,13 +123,13 @@ ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities,
// If there's only one, then there's a problem.
if (i >= num_capabilities) {
LOG_ERROR(Kernel, "Invalid combination! i={}", i);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
const auto size_flags = capabilities[i];
if (GetCapabilityType(size_flags) != CapabilityType::MapPhysical) {
LOG_ERROR(Kernel, "Invalid capability type! size_flags={}", size_flags);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
const auto result = HandleMapPhysicalFlags(descriptor, size_flags, page_table);
@@ -157,7 +159,7 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s
const auto type = GetCapabilityType(flag);
if (type == CapabilityType::Unset) {
- return ERR_INVALID_CAPABILITY_DESCRIPTOR;
+ return ResultInvalidCapabilityDescriptor;
}
// Bail early on ignorable entries, as one would expect,
@@ -174,7 +176,7 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s
LOG_ERROR(Kernel,
"Attempted to initialize flags that may only be initialized once. set_flags={}",
set_flags);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
set_flags |= set_flag;
@@ -199,8 +201,8 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s
break;
}
- LOG_ERROR(Kernel, "Invalid capability type! type={}", static_cast<u32>(type));
- return ERR_INVALID_CAPABILITY_DESCRIPTOR;
+ LOG_ERROR(Kernel, "Invalid capability type! type={}", type);
+ return ResultInvalidCapabilityDescriptor;
}
void ProcessCapabilities::Clear() {
@@ -223,7 +225,7 @@ ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
if (priority_mask != 0 || core_mask != 0) {
LOG_ERROR(Kernel, "Core or priority mask are not zero! priority_mask={}, core_mask={}",
priority_mask, core_mask);
- return ERR_INVALID_CAPABILITY_DESCRIPTOR;
+ return ResultInvalidCapabilityDescriptor;
}
const u32 core_num_min = (flags >> 16) & 0xFF;
@@ -231,7 +233,7 @@ ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
if (core_num_min > core_num_max) {
LOG_ERROR(Kernel, "Core min is greater than core max! core_num_min={}, core_num_max={}",
core_num_min, core_num_max);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
const u32 priority_min = (flags >> 10) & 0x3F;
@@ -240,13 +242,13 @@ ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
LOG_ERROR(Kernel,
"Priority min is greater than priority max! priority_min={}, priority_max={}",
core_num_min, priority_max);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
// The switch only has 4 usable cores.
if (core_num_max >= 4) {
LOG_ERROR(Kernel, "Invalid max cores specified! core_num_max={}", core_num_max);
- return ERR_INVALID_PROCESSOR_ID;
+ return ResultInvalidCoreId;
}
const auto make_mask = [](u64 min, u64 max) {
@@ -267,7 +269,7 @@ ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags)
// If we've already set this svc before, bail.
if ((set_svc_bits & svc_bit) != 0) {
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
set_svc_bits |= svc_bit;
@@ -281,7 +283,7 @@ ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags)
if (svc_number >= svc_capabilities.size()) {
LOG_ERROR(Kernel, "Process svc capability is out of range! svc_number={}", svc_number);
- return ERR_OUT_OF_RANGE;
+ return ResultOutOfRange;
}
svc_capabilities[svc_number] = true;
@@ -319,7 +321,7 @@ ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) {
if (interrupt >= interrupt_capabilities.size()) {
LOG_ERROR(Kernel, "Process interrupt capability is out of range! svc_number={}",
interrupt);
- return ERR_OUT_OF_RANGE;
+ return ResultOutOfRange;
}
interrupt_capabilities[interrupt] = true;
@@ -332,7 +334,7 @@ ResultCode ProcessCapabilities::HandleProgramTypeFlags(u32 flags) {
const u32 reserved = flags >> 17;
if (reserved != 0) {
LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
- return ERR_RESERVED_VALUE;
+ return ResultReservedValue;
}
program_type = static_cast<ProgramType>((flags >> 14) & 0b111);
@@ -352,7 +354,7 @@ ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
LOG_ERROR(Kernel,
"Kernel version is non zero or flags are too small! major_version={}, flags={}",
major_version, flags);
- return ERR_INVALID_CAPABILITY_DESCRIPTOR;
+ return ResultInvalidCapabilityDescriptor;
}
kernel_version = flags;
@@ -363,7 +365,7 @@ ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
const u32 reserved = flags >> 26;
if (reserved != 0) {
LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
- return ERR_RESERVED_VALUE;
+ return ResultReservedValue;
}
handle_table_size = static_cast<s32>((flags >> 16) & 0x3FF);
@@ -374,7 +376,7 @@ ResultCode ProcessCapabilities::HandleDebugFlags(u32 flags) {
const u32 reserved = flags >> 19;
if (reserved != 0) {
LOG_ERROR(Kernel, "Reserved value is non-zero! reserved={}", reserved);
- return ERR_RESERVED_VALUE;
+ return ResultReservedValue;
}
is_debuggable = (flags & 0x20000) != 0;
diff --git a/src/core/hle/kernel/readable_event.cpp b/src/core/hle/kernel/readable_event.cpp
deleted file mode 100644
index 6e286419e..000000000
--- a/src/core/hle/kernel/readable_event.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
-
-namespace Kernel {
-
-ReadableEvent::ReadableEvent(KernelCore& kernel) : SynchronizationObject{kernel} {}
-ReadableEvent::~ReadableEvent() = default;
-
-bool ReadableEvent::ShouldWait(const Thread* thread) const {
- return !is_signaled;
-}
-
-void ReadableEvent::Acquire(Thread* thread) {
- ASSERT_MSG(IsSignaled(), "object unavailable!");
-}
-
-void ReadableEvent::Signal() {
- if (is_signaled) {
- return;
- }
-
- is_signaled = true;
- SynchronizationObject::Signal();
-}
-
-void ReadableEvent::Clear() {
- is_signaled = false;
-}
-
-ResultCode ReadableEvent::Reset() {
- SchedulerLock lock(kernel);
- if (!is_signaled) {
- LOG_TRACE(Kernel, "Handle is not signaled! object_id={}, object_type={}, object_name={}",
- GetObjectId(), GetTypeName(), GetName());
- return ERR_INVALID_STATE;
- }
-
- Clear();
-
- return RESULT_SUCCESS;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/readable_event.h b/src/core/hle/kernel/readable_event.h
deleted file mode 100644
index 3264dd066..000000000
--- a/src/core/hle/kernel/readable_event.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/synchronization_object.h"
-
-union ResultCode;
-
-namespace Kernel {
-
-class KernelCore;
-class WritableEvent;
-
-class ReadableEvent final : public SynchronizationObject {
- friend class WritableEvent;
-
-public:
- ~ReadableEvent() override;
-
- std::string GetTypeName() const override {
- return "ReadableEvent";
- }
- std::string GetName() const override {
- return name;
- }
-
- static constexpr HandleType HANDLE_TYPE = HandleType::ReadableEvent;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
-
- bool ShouldWait(const Thread* thread) const override;
- void Acquire(Thread* thread) override;
-
- /// Unconditionally clears the readable event's state.
- void Clear();
-
- /// Clears the readable event's state if and only if it
- /// has already been signaled.
- ///
- /// @pre The event must be in a signaled state. If this event
- /// is in an unsignaled state and this function is called,
- /// then ERR_INVALID_STATE will be returned.
- ResultCode Reset();
-
- void Signal() override;
-
-private:
- explicit ReadableEvent(KernelCore& kernel);
-
- std::string name; ///< Name of event (optional)
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp
deleted file mode 100644
index 212e442f4..000000000
--- a/src/core/hle/kernel/resource_limit.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/resource_limit.h"
-#include "core/hle/result.h"
-
-namespace Kernel {
-namespace {
-constexpr std::size_t ResourceTypeToIndex(ResourceType type) {
- return static_cast<std::size_t>(type);
-}
-} // Anonymous namespace
-
-ResourceLimit::ResourceLimit(KernelCore& kernel) : Object{kernel} {}
-ResourceLimit::~ResourceLimit() = default;
-
-bool ResourceLimit::Reserve(ResourceType resource, s64 amount) {
- return Reserve(resource, amount, 10000000000);
-}
-
-bool ResourceLimit::Reserve(ResourceType resource, s64 amount, u64 timeout) {
- const std::size_t index{ResourceTypeToIndex(resource)};
-
- s64 new_value = current[index] + amount;
- if (new_value > limit[index] && available[index] + amount <= limit[index]) {
- // TODO(bunnei): This is wrong for multicore, we should wait the calling thread for timeout
- new_value = current[index] + amount;
- }
-
- if (new_value <= limit[index]) {
- current[index] = new_value;
- return true;
- }
- return false;
-}
-
-void ResourceLimit::Release(ResourceType resource, u64 amount) {
- Release(resource, amount, amount);
-}
-
-void ResourceLimit::Release(ResourceType resource, u64 used_amount, u64 available_amount) {
- const std::size_t index{ResourceTypeToIndex(resource)};
-
- current[index] -= used_amount;
- available[index] -= available_amount;
-}
-
-std::shared_ptr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel) {
- return std::make_shared<ResourceLimit>(kernel);
-}
-
-s64 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
- return limit.at(ResourceTypeToIndex(resource)) - current.at(ResourceTypeToIndex(resource));
-}
-
-s64 ResourceLimit::GetMaxResourceValue(ResourceType resource) const {
- return limit.at(ResourceTypeToIndex(resource));
-}
-
-ResultCode ResourceLimit::SetLimitValue(ResourceType resource, s64 value) {
- const std::size_t index{ResourceTypeToIndex(resource)};
- if (current[index] <= value) {
- limit[index] = value;
- return RESULT_SUCCESS;
- } else {
- LOG_ERROR(Kernel, "Limit value is too large! resource={}, value={}, index={}",
- static_cast<u32>(resource), value, index);
- return ERR_INVALID_STATE;
- }
-}
-} // namespace Kernel
diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h
deleted file mode 100644
index 936cc4d0f..000000000
--- a/src/core/hle/kernel/resource_limit.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <memory>
-
-#include "common/common_types.h"
-#include "core/hle/kernel/object.h"
-
-union ResultCode;
-
-namespace Kernel {
-
-class KernelCore;
-
-enum class ResourceType : u32 {
- PhysicalMemory,
- Threads,
- Events,
- TransferMemory,
- Sessions,
-
- // Used as a count, not an actual type.
- ResourceTypeCount
-};
-
-constexpr bool IsValidResourceType(ResourceType type) {
- return type < ResourceType::ResourceTypeCount;
-}
-
-class ResourceLimit final : public Object {
-public:
- explicit ResourceLimit(KernelCore& kernel);
- ~ResourceLimit() override;
-
- /// Creates a resource limit object.
- static std::shared_ptr<ResourceLimit> Create(KernelCore& kernel);
-
- std::string GetTypeName() const override {
- return "ResourceLimit";
- }
- std::string GetName() const override {
- return GetTypeName();
- }
-
- static constexpr HandleType HANDLE_TYPE = HandleType::ResourceLimit;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
-
- bool Reserve(ResourceType resource, s64 amount);
- bool Reserve(ResourceType resource, s64 amount, u64 timeout);
- void Release(ResourceType resource, u64 amount);
- void Release(ResourceType resource, u64 used_amount, u64 available_amount);
-
- /**
- * Gets the current value for the specified resource.
- * @param resource Requested resource type
- * @returns The current value of the resource type
- */
- s64 GetCurrentResourceValue(ResourceType resource) const;
-
- /**
- * Gets the max value for the specified resource.
- * @param resource Requested resource type
- * @returns The max value of the resource type
- */
- s64 GetMaxResourceValue(ResourceType resource) const;
-
- /**
- * Sets the limit value for a given resource type.
- *
- * @param resource The resource type to apply the limit to.
- * @param value The limit to apply to the given resource type.
- *
- * @return A result code indicating if setting the limit value
- * was successful or not.
- *
- * @note The supplied limit value *must* be greater than or equal to
- * the current resource value for the given resource type,
- * otherwise ERR_INVALID_STATE will be returned.
- */
- ResultCode SetLimitValue(ResourceType resource, s64 value);
-
-private:
- // TODO(Subv): Increment resource limit current values in their respective Kernel::T::Create
- // functions
- //
- // Currently we have no way of distinguishing if a Create was called by the running application,
- // or by a service module. Approach this once we have separated the service modules into their
- // own processes
-
- using ResourceArray =
- std::array<s64, static_cast<std::size_t>(ResourceType::ResourceTypeCount)>;
-
- ResourceArray limit{};
- ResourceArray current{};
- ResourceArray available{};
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
deleted file mode 100644
index a4b234424..000000000
--- a/src/core/hle/kernel/scheduler.cpp
+++ /dev/null
@@ -1,845 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-//
-// SelectThreads, Yield functions originally by TuxSH.
-// licensed under GPLv2 or later under exception provided by the author.
-
-#include <algorithm>
-#include <mutex>
-#include <set>
-#include <unordered_set>
-#include <utility>
-
-#include "common/assert.h"
-#include "common/bit_util.h"
-#include "common/fiber.h"
-#include "common/logging/log.h"
-#include "core/arm/arm_interface.h"
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/cpu_manager.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/physical_core.h"
-#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/time_manager.h"
-
-namespace Kernel {
-
-GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {}
-
-GlobalScheduler::~GlobalScheduler() = default;
-
-void GlobalScheduler::AddThread(std::shared_ptr<Thread> thread) {
- std::scoped_lock lock{global_list_guard};
- thread_list.push_back(std::move(thread));
-}
-
-void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) {
- std::scoped_lock lock{global_list_guard};
- thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
- thread_list.end());
-}
-
-u32 GlobalScheduler::SelectThreads() {
- ASSERT(is_locked);
- const auto update_thread = [](Thread* thread, Scheduler& sched) {
- std::scoped_lock lock{sched.guard};
- if (thread != sched.selected_thread_set.get()) {
- if (thread == nullptr) {
- ++sched.idle_selection_count;
- }
- sched.selected_thread_set = SharedFrom(thread);
- }
- const bool reschedule_pending =
- sched.is_context_switch_pending || (sched.selected_thread_set != sched.current_thread);
- sched.is_context_switch_pending = reschedule_pending;
- std::atomic_thread_fence(std::memory_order_seq_cst);
- return reschedule_pending;
- };
- if (!is_reselection_pending.load()) {
- return 0;
- }
- std::array<Thread*, Core::Hardware::NUM_CPU_CORES> top_threads{};
-
- u32 idle_cores{};
-
- // Step 1: Get top thread in schedule queue.
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- Thread* top_thread =
- scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front();
- if (top_thread != nullptr) {
- // TODO(Blinkhawk): Implement Thread Pinning
- } else {
- idle_cores |= (1ul << core);
- }
- top_threads[core] = top_thread;
- }
-
- while (idle_cores != 0) {
- u32 core_id = Common::CountTrailingZeroes32(idle_cores);
-
- if (!suggested_queue[core_id].empty()) {
- std::array<s32, Core::Hardware::NUM_CPU_CORES> migration_candidates{};
- std::size_t num_candidates = 0;
- auto iter = suggested_queue[core_id].begin();
- Thread* suggested = nullptr;
- // Step 2: Try selecting a suggested thread.
- while (iter != suggested_queue[core_id].end()) {
- suggested = *iter;
- iter++;
- s32 suggested_core_id = suggested->GetProcessorID();
- Thread* top_thread =
- suggested_core_id >= 0 ? top_threads[suggested_core_id] : nullptr;
- if (top_thread != suggested) {
- if (top_thread != nullptr &&
- top_thread->GetPriority() < THREADPRIO_MAX_CORE_MIGRATION) {
- suggested = nullptr;
- break;
- // There's a too high thread to do core migration, cancel
- }
- TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id), suggested);
- break;
- }
- suggested = nullptr;
- migration_candidates[num_candidates++] = suggested_core_id;
- }
- // Step 3: Select a suggested thread from another core
- if (suggested == nullptr) {
- for (std::size_t i = 0; i < num_candidates; i++) {
- s32 candidate_core = migration_candidates[i];
- suggested = top_threads[candidate_core];
- auto it = scheduled_queue[candidate_core].begin();
- it++;
- Thread* next = it != scheduled_queue[candidate_core].end() ? *it : nullptr;
- if (next != nullptr) {
- TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id),
- suggested);
- top_threads[candidate_core] = next;
- break;
- } else {
- suggested = nullptr;
- }
- }
- }
- top_threads[core_id] = suggested;
- }
-
- idle_cores &= ~(1ul << core_id);
- }
- u32 cores_needing_context_switch{};
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- Scheduler& sched = kernel.Scheduler(core);
- ASSERT(top_threads[core] == nullptr ||
- static_cast<u32>(top_threads[core]->GetProcessorID()) == core);
- if (update_thread(top_threads[core], sched)) {
- cores_needing_context_switch |= (1ul << core);
- }
- }
- return cores_needing_context_switch;
-}
-
-bool GlobalScheduler::YieldThread(Thread* yielding_thread) {
- ASSERT(is_locked);
- // Note: caller should use critical section, etc.
- if (!yielding_thread->IsRunnable()) {
- // Normally this case shouldn't happen except for SetThreadActivity.
- is_reselection_pending.store(true, std::memory_order_release);
- return false;
- }
- const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
- const u32 priority = yielding_thread->GetPriority();
-
- // Yield the thread
- Reschedule(priority, core_id, yielding_thread);
- const Thread* const winner = scheduled_queue[core_id].front();
- if (kernel.GetCurrentHostThreadID() != core_id) {
- is_reselection_pending.store(true, std::memory_order_release);
- }
-
- return AskForReselectionOrMarkRedundant(yielding_thread, winner);
-}
-
-bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
- ASSERT(is_locked);
- // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
- // etc.
- if (!yielding_thread->IsRunnable()) {
- // Normally this case shouldn't happen except for SetThreadActivity.
- is_reselection_pending.store(true, std::memory_order_release);
- return false;
- }
- const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
- const u32 priority = yielding_thread->GetPriority();
-
- // Yield the thread
- Reschedule(priority, core_id, yielding_thread);
-
- std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
- for (std::size_t i = 0; i < current_threads.size(); i++) {
- current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
- }
-
- Thread* next_thread = scheduled_queue[core_id].front(priority);
- Thread* winner = nullptr;
- for (auto& thread : suggested_queue[core_id]) {
- const s32 source_core = thread->GetProcessorID();
- if (source_core >= 0) {
- if (current_threads[source_core] != nullptr) {
- if (thread == current_threads[source_core] ||
- current_threads[source_core]->GetPriority() < min_regular_priority) {
- continue;
- }
- }
- }
- if (next_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks() ||
- next_thread->GetPriority() < thread->GetPriority()) {
- if (thread->GetPriority() <= priority) {
- winner = thread;
- break;
- }
- }
- }
-
- if (winner != nullptr) {
- if (winner != yielding_thread) {
- TransferToCore(winner->GetPriority(), s32(core_id), winner);
- }
- } else {
- winner = next_thread;
- }
-
- if (kernel.GetCurrentHostThreadID() != core_id) {
- is_reselection_pending.store(true, std::memory_order_release);
- }
-
- return AskForReselectionOrMarkRedundant(yielding_thread, winner);
-}
-
-bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) {
- ASSERT(is_locked);
- // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
- // etc.
- if (!yielding_thread->IsRunnable()) {
- // Normally this case shouldn't happen except for SetThreadActivity.
- is_reselection_pending.store(true, std::memory_order_release);
- return false;
- }
- Thread* winner = nullptr;
- const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
-
- // Remove the thread from its scheduled mlq, put it on the corresponding "suggested" one instead
- TransferToCore(yielding_thread->GetPriority(), -1, yielding_thread);
-
- // If the core is idle, perform load balancing, excluding the threads that have just used this
- // function...
- if (scheduled_queue[core_id].empty()) {
- // Here, "current_threads" is calculated after the ""yield"", unlike yield -1
- std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
- for (std::size_t i = 0; i < current_threads.size(); i++) {
- current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
- }
- for (auto& thread : suggested_queue[core_id]) {
- const s32 source_core = thread->GetProcessorID();
- if (source_core < 0 || thread == current_threads[source_core]) {
- continue;
- }
- if (current_threads[source_core] == nullptr ||
- current_threads[source_core]->GetPriority() >= min_regular_priority) {
- winner = thread;
- }
- break;
- }
- if (winner != nullptr) {
- if (winner != yielding_thread) {
- TransferToCore(winner->GetPriority(), static_cast<s32>(core_id), winner);
- }
- } else {
- winner = yielding_thread;
- }
- } else {
- winner = scheduled_queue[core_id].front();
- }
-
- if (kernel.GetCurrentHostThreadID() != core_id) {
- is_reselection_pending.store(true, std::memory_order_release);
- }
-
- return AskForReselectionOrMarkRedundant(yielding_thread, winner);
-}
-
-void GlobalScheduler::PreemptThreads() {
- ASSERT(is_locked);
- for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
- const u32 priority = preemption_priorities[core_id];
-
- if (scheduled_queue[core_id].size(priority) > 0) {
- if (scheduled_queue[core_id].size(priority) > 1) {
- scheduled_queue[core_id].front(priority)->IncrementYieldCount();
- }
- scheduled_queue[core_id].yield(priority);
- if (scheduled_queue[core_id].size(priority) > 1) {
- scheduled_queue[core_id].front(priority)->IncrementYieldCount();
- }
- }
-
- Thread* current_thread =
- scheduled_queue[core_id].empty() ? nullptr : scheduled_queue[core_id].front();
- Thread* winner = nullptr;
- for (auto& thread : suggested_queue[core_id]) {
- const s32 source_core = thread->GetProcessorID();
- if (thread->GetPriority() != priority) {
- continue;
- }
- if (source_core >= 0) {
- Thread* next_thread = scheduled_queue[source_core].empty()
- ? nullptr
- : scheduled_queue[source_core].front();
- if (next_thread != nullptr && next_thread->GetPriority() < 2) {
- break;
- }
- if (next_thread == thread) {
- continue;
- }
- }
- if (current_thread != nullptr &&
- current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
- winner = thread;
- break;
- }
- }
-
- if (winner != nullptr) {
- TransferToCore(winner->GetPriority(), s32(core_id), winner);
- current_thread =
- winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread;
- }
-
- if (current_thread != nullptr && current_thread->GetPriority() > priority) {
- for (auto& thread : suggested_queue[core_id]) {
- const s32 source_core = thread->GetProcessorID();
- if (thread->GetPriority() < priority) {
- continue;
- }
- if (source_core >= 0) {
- Thread* next_thread = scheduled_queue[source_core].empty()
- ? nullptr
- : scheduled_queue[source_core].front();
- if (next_thread != nullptr && next_thread->GetPriority() < 2) {
- break;
- }
- if (next_thread == thread) {
- continue;
- }
- }
- if (current_thread != nullptr &&
- current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
- winner = thread;
- break;
- }
- }
-
- if (winner != nullptr) {
- TransferToCore(winner->GetPriority(), s32(core_id), winner);
- current_thread = winner;
- }
- }
-
- is_reselection_pending.store(true, std::memory_order_release);
- }
-}
-
-void GlobalScheduler::EnableInterruptAndSchedule(u32 cores_pending_reschedule,
- Core::EmuThreadHandle global_thread) {
- u32 current_core = global_thread.host_handle;
- bool must_context_switch = global_thread.guest_handle != InvalidHandle &&
- (current_core < Core::Hardware::NUM_CPU_CORES);
- while (cores_pending_reschedule != 0) {
- u32 core = Common::CountTrailingZeroes32(cores_pending_reschedule);
- ASSERT(core < Core::Hardware::NUM_CPU_CORES);
- if (!must_context_switch || core != current_core) {
- auto& phys_core = kernel.PhysicalCore(core);
- phys_core.Interrupt();
- } else {
- must_context_switch = true;
- }
- cores_pending_reschedule &= ~(1ul << core);
- }
- if (must_context_switch) {
- auto& core_scheduler = kernel.CurrentScheduler();
- kernel.ExitSVCProfile();
- core_scheduler.TryDoContextSwitch();
- kernel.EnterSVCProfile();
- }
-}
-
-void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) {
- ASSERT(is_locked);
- suggested_queue[core].add(thread, priority);
-}
-
-void GlobalScheduler::Unsuggest(u32 priority, std::size_t core, Thread* thread) {
- ASSERT(is_locked);
- suggested_queue[core].remove(thread, priority);
-}
-
-void GlobalScheduler::Schedule(u32 priority, std::size_t core, Thread* thread) {
- ASSERT(is_locked);
- ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
- scheduled_queue[core].add(thread, priority);
-}
-
-void GlobalScheduler::SchedulePrepend(u32 priority, std::size_t core, Thread* thread) {
- ASSERT(is_locked);
- ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
- scheduled_queue[core].add(thread, priority, false);
-}
-
-void GlobalScheduler::Reschedule(u32 priority, std::size_t core, Thread* thread) {
- ASSERT(is_locked);
- scheduled_queue[core].remove(thread, priority);
- scheduled_queue[core].add(thread, priority);
-}
-
-void GlobalScheduler::Unschedule(u32 priority, std::size_t core, Thread* thread) {
- ASSERT(is_locked);
- scheduled_queue[core].remove(thread, priority);
-}
-
-void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) {
- ASSERT(is_locked);
- const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT;
- const s32 source_core = thread->GetProcessorID();
- if (source_core == destination_core || !schedulable) {
- return;
- }
- thread->SetProcessorID(destination_core);
- if (source_core >= 0) {
- Unschedule(priority, static_cast<u32>(source_core), thread);
- }
- if (destination_core >= 0) {
- Unsuggest(priority, static_cast<u32>(destination_core), thread);
- Schedule(priority, static_cast<u32>(destination_core), thread);
- }
- if (source_core >= 0) {
- Suggest(priority, static_cast<u32>(source_core), thread);
- }
-}
-
-bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread,
- const Thread* winner) {
- if (current_thread == winner) {
- current_thread->IncrementYieldCount();
- return true;
- } else {
- is_reselection_pending.store(true, std::memory_order_release);
- return false;
- }
-}
-
-void GlobalScheduler::AdjustSchedulingOnStatus(Thread* thread, u32 old_flags) {
- if (old_flags == thread->scheduling_state) {
- return;
- }
- ASSERT(is_locked);
-
- if (old_flags == static_cast<u32>(ThreadSchedStatus::Runnable)) {
- // In this case the thread was running, now it's pausing/exitting
- if (thread->processor_id >= 0) {
- Unschedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (core != static_cast<u32>(thread->processor_id) &&
- ((thread->affinity_mask >> core) & 1) != 0) {
- Unsuggest(thread->current_priority, core, thread);
- }
- }
- } else if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
- // The thread is now set to running from being stopped
- if (thread->processor_id >= 0) {
- Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (core != static_cast<u32>(thread->processor_id) &&
- ((thread->affinity_mask >> core) & 1) != 0) {
- Suggest(thread->current_priority, core, thread);
- }
- }
- }
-
- SetReselectionPending();
-}
-
-void GlobalScheduler::AdjustSchedulingOnPriority(Thread* thread, u32 old_priority) {
- if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable)) {
- return;
- }
- ASSERT(is_locked);
- if (thread->processor_id >= 0) {
- Unschedule(old_priority, static_cast<u32>(thread->processor_id), thread);
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (core != static_cast<u32>(thread->processor_id) &&
- ((thread->affinity_mask >> core) & 1) != 0) {
- Unsuggest(old_priority, core, thread);
- }
- }
-
- if (thread->processor_id >= 0) {
- if (thread == kernel.CurrentScheduler().GetCurrentThread()) {
- SchedulePrepend(thread->current_priority, static_cast<u32>(thread->processor_id),
- thread);
- } else {
- Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
- }
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (core != static_cast<u32>(thread->processor_id) &&
- ((thread->affinity_mask >> core) & 1) != 0) {
- Suggest(thread->current_priority, core, thread);
- }
- }
- thread->IncrementYieldCount();
- SetReselectionPending();
-}
-
-void GlobalScheduler::AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask,
- s32 old_core) {
- if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable) ||
- thread->current_priority >= THREADPRIO_COUNT) {
- return;
- }
- ASSERT(is_locked);
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (((old_affinity_mask >> core) & 1) != 0) {
- if (core == static_cast<u32>(old_core)) {
- Unschedule(thread->current_priority, core, thread);
- } else {
- Unsuggest(thread->current_priority, core, thread);
- }
- }
- }
-
- for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- if (((thread->affinity_mask >> core) & 1) != 0) {
- if (core == static_cast<u32>(thread->processor_id)) {
- Schedule(thread->current_priority, core, thread);
- } else {
- Suggest(thread->current_priority, core, thread);
- }
- }
- }
-
- thread->IncrementYieldCount();
- SetReselectionPending();
-}
-
-void GlobalScheduler::Shutdown() {
- for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
- scheduled_queue[core].clear();
- suggested_queue[core].clear();
- }
- thread_list.clear();
-}
-
-void GlobalScheduler::Lock() {
- Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID();
- ASSERT(!current_thread.IsInvalid());
- if (current_thread == current_owner) {
- ++scope_lock;
- } else {
- inner_lock.lock();
- is_locked = true;
- current_owner = current_thread;
- ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle());
- scope_lock = 1;
- }
-}
-
-void GlobalScheduler::Unlock() {
- if (--scope_lock != 0) {
- ASSERT(scope_lock > 0);
- return;
- }
- u32 cores_pending_reschedule = SelectThreads();
- Core::EmuThreadHandle leaving_thread = current_owner;
- current_owner = Core::EmuThreadHandle::InvalidHandle();
- scope_lock = 1;
- is_locked = false;
- inner_lock.unlock();
- EnableInterruptAndSchedule(cores_pending_reschedule, leaving_thread);
-}
-
-Scheduler::Scheduler(Core::System& system, std::size_t core_id) : system(system), core_id(core_id) {
- switch_fiber = std::make_shared<Common::Fiber>(std::function<void(void*)>(OnSwitch), this);
-}
-
-Scheduler::~Scheduler() = default;
-
-bool Scheduler::HaveReadyThreads() const {
- return system.GlobalScheduler().HaveReadyThreads(core_id);
-}
-
-Thread* Scheduler::GetCurrentThread() const {
- if (current_thread) {
- return current_thread.get();
- }
- return idle_thread.get();
-}
-
-Thread* Scheduler::GetSelectedThread() const {
- return selected_thread.get();
-}
-
-u64 Scheduler::GetLastContextSwitchTicks() const {
- return last_context_switch_time;
-}
-
-void Scheduler::TryDoContextSwitch() {
- auto& phys_core = system.Kernel().CurrentPhysicalCore();
- if (phys_core.IsInterrupted()) {
- phys_core.ClearInterrupt();
- }
- guard.lock();
- if (is_context_switch_pending) {
- SwitchContext();
- } else {
- guard.unlock();
- }
-}
-
-void Scheduler::OnThreadStart() {
- SwitchContextStep2();
-}
-
-void Scheduler::Unload() {
- Thread* thread = current_thread.get();
- if (thread) {
- thread->SetContinuousOnSVC(false);
- thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
- thread->SetIsRunning(false);
- if (!thread->IsHLEThread() && !thread->HasExited()) {
- Core::ARM_Interface& cpu_core = thread->ArmInterface();
- cpu_core.SaveContext(thread->GetContext32());
- cpu_core.SaveContext(thread->GetContext64());
- // Save the TPIDR_EL0 system register in case it was modified.
- thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
- cpu_core.ClearExclusiveState();
- }
- thread->context_guard.unlock();
- }
-}
-
-void Scheduler::Reload() {
- Thread* thread = current_thread.get();
- if (thread) {
- ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
- "Thread must be runnable.");
-
- // Cancel any outstanding wakeup events for this thread
- thread->SetIsRunning(true);
- thread->SetWasRunning(false);
- thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
-
- auto* const thread_owner_process = thread->GetOwnerProcess();
- if (thread_owner_process != nullptr) {
- system.Kernel().MakeCurrentProcess(thread_owner_process);
- }
- if (!thread->IsHLEThread()) {
- Core::ARM_Interface& cpu_core = thread->ArmInterface();
- cpu_core.LoadContext(thread->GetContext32());
- cpu_core.LoadContext(thread->GetContext64());
- cpu_core.SetTlsAddress(thread->GetTLSAddress());
- cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
- cpu_core.ChangeProcessorID(this->core_id);
- cpu_core.ClearExclusiveState();
- }
- }
-}
-
-void Scheduler::SwitchContextStep2() {
- // Load context of new thread
- if (selected_thread) {
- ASSERT_MSG(selected_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
- "Thread must be runnable.");
-
- // Cancel any outstanding wakeup events for this thread
- selected_thread->SetIsRunning(true);
- selected_thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
- selected_thread->SetWasRunning(false);
-
- auto* const thread_owner_process = current_thread->GetOwnerProcess();
- if (thread_owner_process != nullptr) {
- system.Kernel().MakeCurrentProcess(thread_owner_process);
- }
- if (!selected_thread->IsHLEThread()) {
- Core::ARM_Interface& cpu_core = selected_thread->ArmInterface();
- cpu_core.LoadContext(selected_thread->GetContext32());
- cpu_core.LoadContext(selected_thread->GetContext64());
- cpu_core.SetTlsAddress(selected_thread->GetTLSAddress());
- cpu_core.SetTPIDR_EL0(selected_thread->GetTPIDR_EL0());
- cpu_core.ChangeProcessorID(this->core_id);
- cpu_core.ClearExclusiveState();
- }
- }
-
- TryDoContextSwitch();
-}
-
-void Scheduler::SwitchContext() {
- current_thread_prev = current_thread;
- selected_thread = selected_thread_set;
- Thread* previous_thread = current_thread_prev.get();
- Thread* new_thread = selected_thread.get();
- current_thread = selected_thread;
-
- is_context_switch_pending = false;
-
- if (new_thread == previous_thread) {
- guard.unlock();
- return;
- }
-
- Process* const previous_process = system.Kernel().CurrentProcess();
-
- UpdateLastContextSwitchTime(previous_thread, previous_process);
-
- // Save context for previous thread
- if (previous_thread) {
- if (new_thread != nullptr && new_thread->IsSuspendThread()) {
- previous_thread->SetWasRunning(true);
- }
- previous_thread->SetContinuousOnSVC(false);
- previous_thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
- previous_thread->SetIsRunning(false);
- if (!previous_thread->IsHLEThread() && !previous_thread->HasExited()) {
- Core::ARM_Interface& cpu_core = previous_thread->ArmInterface();
- cpu_core.SaveContext(previous_thread->GetContext32());
- cpu_core.SaveContext(previous_thread->GetContext64());
- // Save the TPIDR_EL0 system register in case it was modified.
- previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
- cpu_core.ClearExclusiveState();
- }
- previous_thread->context_guard.unlock();
- }
-
- std::shared_ptr<Common::Fiber>* old_context;
- if (previous_thread != nullptr) {
- old_context = &previous_thread->GetHostContext();
- } else {
- old_context = &idle_thread->GetHostContext();
- }
- guard.unlock();
-
- Common::Fiber::YieldTo(*old_context, switch_fiber);
- /// When a thread wakes up, the scheduler may have changed to other in another core.
- auto& next_scheduler = system.Kernel().CurrentScheduler();
- next_scheduler.SwitchContextStep2();
-}
-
-void Scheduler::OnSwitch(void* this_scheduler) {
- Scheduler* sched = static_cast<Scheduler*>(this_scheduler);
- sched->SwitchToCurrent();
-}
-
-void Scheduler::SwitchToCurrent() {
- while (true) {
- {
- std::scoped_lock lock{guard};
- selected_thread = selected_thread_set;
- current_thread = selected_thread;
- is_context_switch_pending = false;
- }
- while (!is_context_switch_pending) {
- if (current_thread != nullptr && !current_thread->IsHLEThread()) {
- current_thread->context_guard.lock();
- if (!current_thread->IsRunnable()) {
- current_thread->context_guard.unlock();
- break;
- }
- if (current_thread->GetProcessorID() != core_id) {
- current_thread->context_guard.unlock();
- break;
- }
- }
- std::shared_ptr<Common::Fiber>* next_context;
- if (current_thread != nullptr) {
- next_context = &current_thread->GetHostContext();
- } else {
- next_context = &idle_thread->GetHostContext();
- }
- Common::Fiber::YieldTo(switch_fiber, *next_context);
- }
- }
-}
-
-void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
- const u64 prev_switch_ticks = last_context_switch_time;
- const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
- const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
-
- if (thread != nullptr) {
- thread->UpdateCPUTimeTicks(update_ticks);
- }
-
- if (process != nullptr) {
- process->UpdateCPUTimeTicks(update_ticks);
- }
-
- last_context_switch_time = most_recent_switch_ticks;
-}
-
-void Scheduler::Initialize() {
- std::string name = "Idle Thread Id:" + std::to_string(core_id);
- std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc();
- void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
- ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE);
- auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(core_id), 0,
- nullptr, std::move(init_func), init_func_parameter);
- idle_thread = std::move(thread_res).Unwrap();
-}
-
-void Scheduler::Shutdown() {
- current_thread = nullptr;
- selected_thread = nullptr;
-}
-
-SchedulerLock::SchedulerLock(KernelCore& kernel) : kernel{kernel} {
- kernel.GlobalScheduler().Lock();
-}
-
-SchedulerLock::~SchedulerLock() {
- kernel.GlobalScheduler().Unlock();
-}
-
-SchedulerLockAndSleep::SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle,
- Thread* time_task, s64 nanoseconds)
- : SchedulerLock{kernel}, event_handle{event_handle}, time_task{time_task}, nanoseconds{
- nanoseconds} {
- event_handle = InvalidHandle;
-}
-
-SchedulerLockAndSleep::~SchedulerLockAndSleep() {
- if (sleep_cancelled) {
- return;
- }
- auto& time_manager = kernel.TimeManager();
- time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
-}
-
-void SchedulerLockAndSleep::Release() {
- if (sleep_cancelled) {
- return;
- }
- auto& time_manager = kernel.TimeManager();
- time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
- sleep_cancelled = true;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
deleted file mode 100644
index 36e3c26fb..000000000
--- a/src/core/hle/kernel/scheduler.h
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <atomic>
-#include <memory>
-#include <mutex>
-#include <vector>
-
-#include "common/common_types.h"
-#include "common/multi_level_queue.h"
-#include "common/spin_lock.h"
-#include "core/hardware_properties.h"
-#include "core/hle/kernel/thread.h"
-
-namespace Common {
-class Fiber;
-}
-
-namespace Core {
-class ARM_Interface;
-class System;
-} // namespace Core
-
-namespace Kernel {
-
-class KernelCore;
-class Process;
-class SchedulerLock;
-
-class GlobalScheduler final {
-public:
- explicit GlobalScheduler(KernelCore& kernel);
- ~GlobalScheduler();
-
- /// Adds a new thread to the scheduler
- void AddThread(std::shared_ptr<Thread> thread);
-
- /// Removes a thread from the scheduler
- void RemoveThread(std::shared_ptr<Thread> thread);
-
- /// Returns a list of all threads managed by the scheduler
- const std::vector<std::shared_ptr<Thread>>& GetThreadList() const {
- return thread_list;
- }
-
- /// Notify the scheduler a thread's status has changed.
- void AdjustSchedulingOnStatus(Thread* thread, u32 old_flags);
-
- /// Notify the scheduler a thread's priority has changed.
- void AdjustSchedulingOnPriority(Thread* thread, u32 old_priority);
-
- /// Notify the scheduler a thread's core and/or affinity mask has changed.
- void AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, s32 old_core);
-
- /**
- * Takes care of selecting the new scheduled threads in three steps:
- *
- * 1. First a thread is selected from the top of the priority queue. If no thread
- * is obtained then we move to step two, else we are done.
- *
- * 2. Second we try to get a suggested thread that's not assigned to any core or
- * that is not the top thread in that core.
- *
- * 3. Third is no suggested thread is found, we do a second pass and pick a running
- * thread in another core and swap it with its current thread.
- *
- * returns the cores needing scheduling.
- */
- u32 SelectThreads();
-
- bool HaveReadyThreads(std::size_t core_id) const {
- return !scheduled_queue[core_id].empty();
- }
-
- /**
- * Takes a thread and moves it to the back of the it's priority list.
- *
- * @note This operation can be redundant and no scheduling is changed if marked as so.
- */
- bool YieldThread(Thread* thread);
-
- /**
- * Takes a thread and moves it to the back of the it's priority list.
- * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
- * a better priority than the next thread in the core.
- *
- * @note This operation can be redundant and no scheduling is changed if marked as so.
- */
- bool YieldThreadAndBalanceLoad(Thread* thread);
-
- /**
- * Takes a thread and moves it out of the scheduling queue.
- * and into the suggested queue. If no thread can be scheduled afterwards in that core,
- * a suggested thread is obtained instead.
- *
- * @note This operation can be redundant and no scheduling is changed if marked as so.
- */
- bool YieldThreadAndWaitForLoadBalancing(Thread* thread);
-
- /**
- * Rotates the scheduling queues of threads at a preemption priority and then does
- * some core rebalancing. Preemption priorities can be found in the array
- * 'preemption_priorities'.
- *
- * @note This operation happens every 10ms.
- */
- void PreemptThreads();
-
- u32 CpuCoresCount() const {
- return Core::Hardware::NUM_CPU_CORES;
- }
-
- void SetReselectionPending() {
- is_reselection_pending.store(true, std::memory_order_release);
- }
-
- bool IsReselectionPending() const {
- return is_reselection_pending.load(std::memory_order_acquire);
- }
-
- void Shutdown();
-
-private:
- friend class SchedulerLock;
-
- /// Lock the scheduler to the current thread.
- void Lock();
-
- /// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling
- /// and reschedules current core if needed.
- void Unlock();
-
- void EnableInterruptAndSchedule(u32 cores_pending_reschedule,
- Core::EmuThreadHandle global_thread);
-
- /**
- * Add a thread to the suggested queue of a cpu core. Suggested threads may be
- * picked if no thread is scheduled to run on the core.
- */
- void Suggest(u32 priority, std::size_t core, Thread* thread);
-
- /**
- * Remove a thread to the suggested queue of a cpu core. Suggested threads may be
- * picked if no thread is scheduled to run on the core.
- */
- void Unsuggest(u32 priority, std::size_t core, Thread* thread);
-
- /**
- * Add a thread to the scheduling queue of a cpu core. The thread is added at the
- * back the queue in its priority level.
- */
- void Schedule(u32 priority, std::size_t core, Thread* thread);
-
- /**
- * Add a thread to the scheduling queue of a cpu core. The thread is added at the
- * front the queue in its priority level.
- */
- void SchedulePrepend(u32 priority, std::size_t core, Thread* thread);
-
- /// Reschedule an already scheduled thread based on a new priority
- void Reschedule(u32 priority, std::size_t core, Thread* thread);
-
- /// Unschedules a thread.
- void Unschedule(u32 priority, std::size_t core, Thread* thread);
-
- /**
- * Transfers a thread into an specific core. If the destination_core is -1
- * it will be unscheduled from its source code and added into its suggested
- * queue.
- */
- void TransferToCore(u32 priority, s32 destination_core, Thread* thread);
-
- bool AskForReselectionOrMarkRedundant(Thread* current_thread, const Thread* winner);
-
- static constexpr u32 min_regular_priority = 2;
- std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES>
- scheduled_queue;
- std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES>
- suggested_queue;
- std::atomic<bool> is_reselection_pending{false};
-
- // The priority levels at which the global scheduler preempts threads every 10 ms. They are
- // ordered from Core 0 to Core 3.
- std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62};
-
- /// Scheduler lock mechanisms.
- bool is_locked{};
- Common::SpinLock inner_lock{};
- std::atomic<s64> scope_lock{};
- Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()};
-
- Common::SpinLock global_list_guard{};
-
- /// Lists all thread ids that aren't deleted/etc.
- std::vector<std::shared_ptr<Thread>> thread_list;
- KernelCore& kernel;
-};
-
-class Scheduler final {
-public:
- explicit Scheduler(Core::System& system, std::size_t core_id);
- ~Scheduler();
-
- /// Returns whether there are any threads that are ready to run.
- bool HaveReadyThreads() const;
-
- /// Reschedules to the next available thread (call after current thread is suspended)
- void TryDoContextSwitch();
-
- /// The next two are for SingleCore Only.
- /// Unload current thread before preempting core.
- void Unload();
- /// Reload current thread after core preemption.
- void Reload();
-
- /// Gets the current running thread
- Thread* GetCurrentThread() const;
-
- /// Gets the currently selected thread from the top of the multilevel queue
- Thread* GetSelectedThread() const;
-
- /// Gets the timestamp for the last context switch in ticks.
- u64 GetLastContextSwitchTicks() const;
-
- bool ContextSwitchPending() const {
- return is_context_switch_pending;
- }
-
- void Initialize();
-
- /// Shutdowns the scheduler.
- void Shutdown();
-
- void OnThreadStart();
-
- std::shared_ptr<Common::Fiber>& ControlContext() {
- return switch_fiber;
- }
-
- const std::shared_ptr<Common::Fiber>& ControlContext() const {
- return switch_fiber;
- }
-
-private:
- friend class GlobalScheduler;
-
- /// Switches the CPU's active thread context to that of the specified thread
- void SwitchContext();
-
- /// When a thread wakes up, it must run this through it's new scheduler
- void SwitchContextStep2();
-
- /**
- * Called on every context switch to update the internal timestamp
- * This also updates the running time ticks for the given thread and
- * process using the following difference:
- *
- * ticks += most_recent_ticks - last_context_switch_ticks
- *
- * The internal tick timestamp for the scheduler is simply the
- * most recent tick count retrieved. No special arithmetic is
- * applied to it.
- */
- void UpdateLastContextSwitchTime(Thread* thread, Process* process);
-
- static void OnSwitch(void* this_scheduler);
- void SwitchToCurrent();
-
- std::shared_ptr<Thread> current_thread = nullptr;
- std::shared_ptr<Thread> selected_thread = nullptr;
- std::shared_ptr<Thread> current_thread_prev = nullptr;
- std::shared_ptr<Thread> selected_thread_set = nullptr;
- std::shared_ptr<Thread> idle_thread = nullptr;
-
- std::shared_ptr<Common::Fiber> switch_fiber = nullptr;
-
- Core::System& system;
- u64 last_context_switch_time = 0;
- u64 idle_selection_count = 0;
- const std::size_t core_id;
-
- Common::SpinLock guard{};
-
- bool is_context_switch_pending = false;
-};
-
-class SchedulerLock {
-public:
- [[nodiscard]] explicit SchedulerLock(KernelCore& kernel);
- ~SchedulerLock();
-
-protected:
- KernelCore& kernel;
-};
-
-class SchedulerLockAndSleep : public SchedulerLock {
-public:
- explicit SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* time_task,
- s64 nanoseconds);
- ~SchedulerLockAndSleep();
-
- void CancelSleep() {
- sleep_cancelled = true;
- }
-
- void Release();
-
-private:
- Handle& event_handle;
- Thread* time_task;
- s64 nanoseconds;
- bool sleep_cancelled{};
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index a549ae9d7..5d17346ad 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -5,20 +5,20 @@
#include <tuple>
#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
-#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
-#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/svc_results.h"
namespace Kernel {
-ServerPort::ServerPort(KernelCore& kernel) : SynchronizationObject{kernel} {}
+ServerPort::ServerPort(KernelCore& kernel) : KSynchronizationObject{kernel} {}
ServerPort::~ServerPort() = default;
ResultVal<std::shared_ptr<ServerSession>> ServerPort::Accept() {
if (pending_sessions.empty()) {
- return ERR_NOT_FOUND;
+ return ResultNotFound;
}
auto session = std::move(pending_sessions.back());
@@ -28,15 +28,9 @@ ResultVal<std::shared_ptr<ServerSession>> ServerPort::Accept() {
void ServerPort::AppendPendingSession(std::shared_ptr<ServerSession> pending_session) {
pending_sessions.push_back(std::move(pending_session));
-}
-
-bool ServerPort::ShouldWait(const Thread* thread) const {
- // If there are no pending sessions, we wait until a new one is added.
- return pending_sessions.empty();
-}
-
-void ServerPort::Acquire(Thread* thread) {
- ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
+ if (pending_sessions.size() == 1) {
+ NotifyAvailable();
+ }
}
bool ServerPort::IsSignaled() const {
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index 41b191b86..29b4f2509 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -9,8 +9,8 @@
#include <utility>
#include <vector>
#include "common/common_types.h"
+#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/synchronization_object.h"
#include "core/hle/result.h"
namespace Kernel {
@@ -20,7 +20,7 @@ class KernelCore;
class ServerSession;
class SessionRequestHandler;
-class ServerPort final : public SynchronizationObject {
+class ServerPort final : public KSynchronizationObject {
public:
explicit ServerPort(KernelCore& kernel);
~ServerPort() override;
@@ -79,11 +79,10 @@ public:
/// waiting to be accepted by this port.
void AppendPendingSession(std::shared_ptr<ServerSession> pending_session);
- bool ShouldWait(const Thread* thread) const override;
- void Acquire(Thread* thread) override;
-
bool IsSignaled() const override;
+ void Finalize() override {}
+
private:
/// ServerSessions waiting to be accepted by the port
std::vector<std::shared_ptr<ServerSession>> pending_sessions;
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 7e6391c6c..790dbb998 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -8,51 +8,40 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
-#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
-#include "core/hle/kernel/thread.h"
#include "core/memory.h"
namespace Kernel {
-ServerSession::ServerSession(KernelCore& kernel) : SynchronizationObject{kernel} {}
-ServerSession::~ServerSession() = default;
+ServerSession::ServerSession(KernelCore& kernel) : KSynchronizationObject{kernel} {}
+
+ServerSession::~ServerSession() {
+ kernel.ReleaseServiceThread(service_thread);
+}
ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kernel,
std::shared_ptr<Session> parent,
std::string name) {
std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)};
- session->request_event =
- Core::Timing::CreateEvent(name, [session](std::uintptr_t, std::chrono::nanoseconds) {
- session->CompleteSyncRequest();
- });
session->name = std::move(name);
session->parent = std::move(parent);
+ session->service_thread = kernel.CreateServiceThread(session->name);
return MakeResult(std::move(session));
}
-bool ServerSession::ShouldWait(const Thread* thread) const {
- // Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
- if (!parent->Client()) {
- return false;
- }
-
- // Wait if we have no pending requests, or if we're currently handling a request.
- return pending_requesting_threads.empty() || currently_handling != nullptr;
-}
-
bool ServerSession::IsSignaled() const {
// Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
if (!parent->Client()) {
@@ -63,15 +52,6 @@ bool ServerSession::IsSignaled() const {
return !pending_requesting_threads.empty() && currently_handling == nullptr;
}
-void ServerSession::Acquire(Thread* thread) {
- ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
- // We are now handling a request, pop it from the stack.
- // TODO(Subv): What happens if the client endpoint is closed before any requests are made?
- ASSERT(!pending_requesting_threads.empty());
- currently_handling = pending_requesting_threads.back();
- pending_requesting_threads.pop_back();
-}
-
void ServerSession::ClientDisconnected() {
// We keep a shared pointer to the hle handler to keep it alive throughout
// the call to ClientDisconnected, as ClientDisconnected invalidates the
@@ -131,29 +111,28 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
}
}
- LOG_CRITICAL(IPC, "Unknown domain command={}",
- static_cast<int>(domain_message_header.command.Value()));
+ LOG_CRITICAL(IPC, "Unknown domain command={}", domain_message_header.command.Value());
ASSERT(false);
return RESULT_SUCCESS;
}
-ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<Thread> thread,
+ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<KThread> thread,
Core::Memory::Memory& memory) {
u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(thread->GetTLSAddress()))};
auto context =
std::make_shared<HLERequestContext>(kernel, memory, SharedFrom(this), std::move(thread));
context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
- request_queue.Push(std::move(context));
+
+ if (auto strong_ptr = service_thread.lock()) {
+ strong_ptr->QueueSyncRequest(*this, std::move(context));
+ return RESULT_SUCCESS;
+ }
return RESULT_SUCCESS;
}
-ResultCode ServerSession::CompleteSyncRequest() {
- ASSERT(!request_queue.Empty());
-
- auto& context = *request_queue.Front();
-
+ResultCode ServerSession::CompleteSyncRequest(HLERequestContext& context) {
ResultCode result = RESULT_SUCCESS;
// If the session has been converted to a domain, handle the domain request
if (IsDomain() && context.HasDomainMessageHeader()) {
@@ -172,24 +151,20 @@ ResultCode ServerSession::CompleteSyncRequest() {
// Some service requests require the thread to block
{
- SchedulerLock lock(kernel);
+ KScopedSchedulerLock lock(kernel);
if (!context.IsThreadWaiting()) {
- context.GetThread().ResumeFromWait();
- context.GetThread().SetSynchronizationResults(nullptr, result);
+ context.GetThread().Wakeup();
+ context.GetThread().SetSyncedObject(nullptr, result);
}
}
- request_queue.Pop();
-
return result;
}
-ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
- Core::Memory::Memory& memory) {
- const ResultCode result = QueueSyncRequest(std::move(thread), memory);
- const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000};
- Core::System::GetInstance().CoreTiming().ScheduleEvent(delay, request_event, {});
- return result;
+ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<KThread> thread,
+ Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing) {
+ return QueueSyncRequest(std::move(thread), memory);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 403aaf10b..c42d5ee59 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -10,7 +10,8 @@
#include <vector>
#include "common/threadsafe_queue.h"
-#include "core/hle/kernel/synchronization_object.h"
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/service_thread.h"
#include "core/hle/result.h"
namespace Core::Memory {
@@ -18,8 +19,9 @@ class Memory;
}
namespace Core::Timing {
+class CoreTiming;
struct EventType;
-}
+} // namespace Core::Timing
namespace Kernel {
@@ -27,7 +29,7 @@ class HLERequestContext;
class KernelCore;
class Session;
class SessionRequestHandler;
-class Thread;
+class KThread;
/**
* Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS
@@ -41,7 +43,9 @@ class Thread;
* After the server replies to the request, the response is marshalled back to the caller's
* TLS buffer and control is transferred back to it.
*/
-class ServerSession final : public SynchronizationObject {
+class ServerSession final : public KSynchronizationObject {
+ friend class ServiceThread;
+
public:
explicit ServerSession(KernelCore& kernel);
~ServerSession() override;
@@ -73,8 +77,6 @@ public:
return parent.get();
}
- bool IsSignaled() const override;
-
/**
* Sets the HLE handler for the session. This handler will be called to service IPC requests
* instead of the regular IPC machinery. (The regular IPC machinery is currently not
@@ -87,16 +89,14 @@ public:
/**
* Handle a sync request from the emulated application.
*
- * @param thread Thread that initiated the request.
- * @param memory Memory context to handle the sync request under.
+ * @param thread Thread that initiated the request.
+ * @param memory Memory context to handle the sync request under.
+ * @param core_timing Core timing context to schedule the request event under.
*
* @returns ResultCode from the operation.
*/
- ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory);
-
- bool ShouldWait(const Thread* thread) const override;
-
- void Acquire(Thread* thread) override;
+ ResultCode HandleSyncRequest(std::shared_ptr<KThread> thread, Core::Memory::Memory& memory,
+ Core::Timing::CoreTiming& core_timing);
/// Called when a client disconnection occurs.
void ClientDisconnected();
@@ -124,12 +124,16 @@ public:
convert_to_domain = true;
}
+ bool IsSignaled() const override;
+
+ void Finalize() override {}
+
private:
/// Queues a sync request from the emulated application.
- ResultCode QueueSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory);
+ ResultCode QueueSyncRequest(std::shared_ptr<KThread> thread, Core::Memory::Memory& memory);
/// Completes a sync request from the emulated application.
- ResultCode CompleteSyncRequest();
+ ResultCode CompleteSyncRequest(HLERequestContext& context);
/// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an
/// object handle.
@@ -147,12 +151,12 @@ private:
/// List of threads that are pending a response after a sync request. This list is processed in
/// a LIFO manner, thus, the last request will be dispatched first.
/// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test.
- std::vector<std::shared_ptr<Thread>> pending_requesting_threads;
+ std::vector<std::shared_ptr<KThread>> pending_requesting_threads;
/// Thread whose request is currently being handled. A request is considered "handled" when a
/// response is sent via svcReplyAndReceive.
/// TODO(Subv): Find a better name for this.
- std::shared_ptr<Thread> currently_handling;
+ std::shared_ptr<KThread> currently_handling;
/// When set to True, converts the session to a domain at the end of the command
bool convert_to_domain{};
@@ -160,11 +164,8 @@ private:
/// The name of this session (optional)
std::string name;
- /// Core timing event used to schedule the service request at some point in the future
- std::shared_ptr<Core::Timing::EventType> request_event;
-
- /// Queue of scheduled service requests
- Common::MPSCQueue<std::shared_ptr<Kernel::HLERequestContext>> request_queue;
+ /// Thread to dispatch service requests
+ std::weak_ptr<ServiceThread> service_thread;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp
new file mode 100644
index 000000000..ee46f3e21
--- /dev/null
+++ b/src/core/hle/kernel/service_thread.cpp
@@ -0,0 +1,110 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <thread>
+#include <vector>
+#include <queue>
+
+#include "common/assert.h"
+#include "common/scope_exit.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/service_thread.h"
+#include "core/hle/lock.h"
+#include "video_core/renderer_base.h"
+
+namespace Kernel {
+
+class ServiceThread::Impl final {
+public:
+ explicit Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name);
+ ~Impl();
+
+ void QueueSyncRequest(ServerSession& session, std::shared_ptr<HLERequestContext>&& context);
+
+private:
+ std::vector<std::thread> threads;
+ std::queue<std::function<void()>> requests;
+ std::mutex queue_mutex;
+ std::condition_variable condition;
+ const std::string service_name;
+ bool stop{};
+};
+
+ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name)
+ : service_name{name} {
+ for (std::size_t i = 0; i < num_threads; ++i)
+ threads.emplace_back([this, &kernel] {
+ Common::SetCurrentThreadName(std::string{"yuzu:HleService:" + service_name}.c_str());
+
+ // Wait for first request before trying to acquire a render context
+ {
+ std::unique_lock lock{queue_mutex};
+ condition.wait(lock, [this] { return stop || !requests.empty(); });
+ }
+
+ kernel.RegisterHostThread();
+
+ while (true) {
+ std::function<void()> task;
+
+ {
+ std::unique_lock lock{queue_mutex};
+ condition.wait(lock, [this] { return stop || !requests.empty(); });
+ if (stop || requests.empty()) {
+ return;
+ }
+ task = std::move(requests.front());
+ requests.pop();
+ }
+
+ task();
+ }
+ });
+}
+
+void ServiceThread::Impl::QueueSyncRequest(ServerSession& session,
+ std::shared_ptr<HLERequestContext>&& context) {
+ {
+ std::unique_lock lock{queue_mutex};
+
+ // ServerSession owns the service thread, so we cannot caption a strong pointer here in the
+ // event that the ServerSession is terminated.
+ std::weak_ptr<ServerSession> weak_ptr{SharedFrom(&session)};
+ requests.emplace([weak_ptr, context{std::move(context)}]() {
+ if (auto strong_ptr = weak_ptr.lock()) {
+ strong_ptr->CompleteSyncRequest(*context);
+ }
+ });
+ }
+ condition.notify_one();
+}
+
+ServiceThread::Impl::~Impl() {
+ {
+ std::unique_lock lock{queue_mutex};
+ stop = true;
+ }
+ condition.notify_all();
+ for (std::thread& thread : threads) {
+ thread.join();
+ }
+}
+
+ServiceThread::ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name)
+ : impl{std::make_unique<Impl>(kernel, num_threads, name)} {}
+
+ServiceThread::~ServiceThread() = default;
+
+void ServiceThread::QueueSyncRequest(ServerSession& session,
+ std::shared_ptr<HLERequestContext>&& context) {
+ impl->QueueSyncRequest(session, std::move(context));
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/service_thread.h b/src/core/hle/kernel/service_thread.h
new file mode 100644
index 000000000..025ab8fb5
--- /dev/null
+++ b/src/core/hle/kernel/service_thread.h
@@ -0,0 +1,28 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+namespace Kernel {
+
+class HLERequestContext;
+class KernelCore;
+class ServerSession;
+
+class ServiceThread final {
+public:
+ explicit ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name);
+ ~ServiceThread();
+
+ void QueueSyncRequest(ServerSession& session, std::shared_ptr<HLERequestContext>&& context);
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/session.cpp b/src/core/hle/kernel/session.cpp
index e4dd53e24..8830d4e91 100644
--- a/src/core/hle/kernel/session.cpp
+++ b/src/core/hle/kernel/session.cpp
@@ -4,15 +4,23 @@
#include "common/assert.h"
#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
namespace Kernel {
-Session::Session(KernelCore& kernel) : SynchronizationObject{kernel} {}
-Session::~Session() = default;
+Session::Session(KernelCore& kernel) : KSynchronizationObject{kernel} {}
+Session::~Session() {
+ // Release reserved resource when the Session pair was created.
+ kernel.GetSystemResourceLimit()->Release(LimitableResource::Sessions, 1);
+}
Session::SessionPair Session::Create(KernelCore& kernel, std::string name) {
+ // Reserve a new session from the resource limit.
+ KScopedResourceReservation session_reservation(kernel.GetSystemResourceLimit(),
+ LimitableResource::Sessions);
+ ASSERT(session_reservation.Succeeded());
auto session{std::make_shared<Session>(kernel)};
auto client_session{Kernel::ClientSession::Create(kernel, session, name + "_Client").Unwrap()};
auto server_session{Kernel::ServerSession::Create(kernel, session, name + "_Server").Unwrap()};
@@ -21,21 +29,13 @@ Session::SessionPair Session::Create(KernelCore& kernel, std::string name) {
session->client = client_session;
session->server = server_session;
+ session_reservation.Commit();
return std::make_pair(std::move(client_session), std::move(server_session));
}
-bool Session::ShouldWait(const Thread* thread) const {
- UNIMPLEMENTED();
- return {};
-}
-
bool Session::IsSignaled() const {
UNIMPLEMENTED();
return true;
}
-void Session::Acquire(Thread* thread) {
- UNIMPLEMENTED();
-}
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h
index 7cd9c0d77..fa3c5651a 100644
--- a/src/core/hle/kernel/session.h
+++ b/src/core/hle/kernel/session.h
@@ -8,7 +8,7 @@
#include <string>
#include <utility>
-#include "core/hle/kernel/synchronization_object.h"
+#include "core/hle/kernel/k_synchronization_object.h"
namespace Kernel {
@@ -19,7 +19,7 @@ class ServerSession;
* Parent structure to link the client and server endpoints of a session with their associated
* client port.
*/
-class Session final : public SynchronizationObject {
+class Session final : public KSynchronizationObject {
public:
explicit Session(KernelCore& kernel);
~Session() override;
@@ -37,11 +37,9 @@ public:
return HANDLE_TYPE;
}
- bool ShouldWait(const Thread* thread) const override;
-
bool IsSignaled() const override;
- void Acquire(Thread* thread) override;
+ void Finalize() override {}
std::shared_ptr<ClientSession> Client() {
if (auto result{client.lock()}) {
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index 0cd467110..2eadd51d7 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -4,6 +4,7 @@
#include "common/assert.h"
#include "core/core.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/shared_memory.h"
@@ -13,7 +14,9 @@ namespace Kernel {
SharedMemory::SharedMemory(KernelCore& kernel, Core::DeviceMemory& device_memory)
: Object{kernel}, device_memory{device_memory} {}
-SharedMemory::~SharedMemory() = default;
+SharedMemory::~SharedMemory() {
+ kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemory, size);
+}
std::shared_ptr<SharedMemory> SharedMemory::Create(
KernelCore& kernel, Core::DeviceMemory& device_memory, Process* owner_process,
@@ -21,6 +24,11 @@ std::shared_ptr<SharedMemory> SharedMemory::Create(
Memory::MemoryPermission user_permission, PAddr physical_address, std::size_t size,
std::string name) {
+ const auto resource_limit = kernel.GetSystemResourceLimit();
+ KScopedResourceReservation memory_reservation(resource_limit, LimitableResource::PhysicalMemory,
+ size);
+ ASSERT(memory_reservation.Succeeded());
+
std::shared_ptr<SharedMemory> shared_memory{
std::make_shared<SharedMemory>(kernel, device_memory)};
@@ -32,6 +40,7 @@ std::shared_ptr<SharedMemory> SharedMemory::Create(
shared_memory->size = size;
shared_memory->name = name;
+ memory_reservation.Commit();
return shared_memory;
}
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index 0ef87235c..623bd8b11 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -71,6 +71,8 @@ public:
return device_memory.GetPointer(physical_address + offset);
}
+ void Finalize() override {}
+
private:
Core::DeviceMemory& device_memory;
Process* owner_process{};
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 01ae57053..31d899e06 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -10,38 +10,44 @@
#include "common/alignment.h"
#include "common/assert.h"
+#include "common/common_funcs.h"
#include "common/fiber.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
+#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/cpu_manager.h"
-#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/k_address_arbiter.h"
+#include "core/hle/kernel/k_condition_variable.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_resource_limit.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/memory_block.h"
+#include "core/hle/kernel/memory/memory_layout.h"
#include "core/hle/kernel/memory/page_table.h"
-#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/resource_limit.h"
-#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/svc.h"
+#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/kernel/svc_wrap.h"
-#include "core/hle/kernel/synchronization.h"
-#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/kernel/transfer_memory.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/lock.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
@@ -65,49 +71,49 @@ ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr ds
VAddr src_addr, u64 size) {
if (!Common::Is4KBAligned(dst_addr)) {
LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (!Common::Is4KBAligned(src_addr)) {
LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (size == 0) {
LOG_ERROR(Kernel_SVC, "Size is 0");
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (!Common::Is4KBAligned(size)) {
LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (!IsValidAddressRange(dst_addr, size)) {
LOG_ERROR(Kernel_SVC,
"Destination is not a valid address range, addr=0x{:016X}, size=0x{:016X}",
dst_addr, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (!IsValidAddressRange(src_addr, size)) {
LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr=0x{:016X}, size=0x{:016X}",
src_addr, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (!manager.IsInsideAddressSpace(src_addr, size)) {
LOG_ERROR(Kernel_SVC,
"Source is not within the address space, addr=0x{:016X}, size=0x{:016X}",
src_addr, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (manager.IsOutsideStackRegion(dst_addr, size)) {
LOG_ERROR(Kernel_SVC,
"Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}",
dst_addr, size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
if (manager.IsInsideHeapRegion(dst_addr, size)) {
@@ -115,7 +121,7 @@ ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr ds
"Destination does not fit within the heap region, addr=0x{:016X}, "
"size=0x{:016X}",
dst_addr, size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
if (manager.IsInsideAliasRegion(dst_addr, size)) {
@@ -123,7 +129,7 @@ ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr ds
"Destination does not fit within the map region, addr=0x{:016X}, "
"size=0x{:016X}",
dst_addr, size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
return RESULT_SUCCESS;
@@ -132,33 +138,40 @@ ResultCode MapUnmapMemorySanityChecks(const Memory::PageTable& manager, VAddr ds
enum class ResourceLimitValueType {
CurrentValue,
LimitValue,
+ PeakValue,
};
ResultVal<s64> RetrieveResourceLimitValue(Core::System& system, Handle resource_limit,
u32 resource_type, ResourceLimitValueType value_type) {
std::lock_guard lock{HLE::g_hle_lock};
- const auto type = static_cast<ResourceType>(resource_type);
+ const auto type = static_cast<LimitableResource>(resource_type);
if (!IsValidResourceType(type)) {
LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type);
- return ERR_INVALID_ENUM_VALUE;
+ return ResultInvalidEnumValue;
}
const auto* const current_process = system.Kernel().CurrentProcess();
ASSERT(current_process != nullptr);
const auto resource_limit_object =
- current_process->GetHandleTable().Get<ResourceLimit>(resource_limit);
+ current_process->GetHandleTable().Get<KResourceLimit>(resource_limit);
if (!resource_limit_object) {
LOG_ERROR(Kernel_SVC, "Handle to non-existent resource limit instance used. Handle={:08X}",
resource_limit);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
- if (value_type == ResourceLimitValueType::CurrentValue) {
- return MakeResult(resource_limit_object->GetCurrentResourceValue(type));
+ switch (value_type) {
+ case ResourceLimitValueType::CurrentValue:
+ return MakeResult(resource_limit_object->GetCurrentValue(type));
+ case ResourceLimitValueType::LimitValue:
+ return MakeResult(resource_limit_object->GetLimitValue(type));
+ case ResourceLimitValueType::PeakValue:
+ return MakeResult(resource_limit_object->GetPeakValue(type));
+ default:
+ LOG_ERROR(Kernel_SVC, "Invalid resource value_type: '{}'", value_type);
+ return ResultInvalidEnumValue;
}
-
- return MakeResult(resource_limit_object->GetMaxResourceValue(type));
}
} // Anonymous namespace
@@ -171,12 +184,12 @@ static ResultCode SetHeapSize(Core::System& system, VAddr* heap_addr, u64 heap_s
if ((heap_size % 0x200000) != 0) {
LOG_ERROR(Kernel_SVC, "The heap size is not a multiple of 2MB, heap_size=0x{:016X}",
heap_size);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (heap_size >= 0x200000000) {
LOG_ERROR(Kernel_SVC, "The heap size is not less than 8GB, heap_size=0x{:016X}", heap_size);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
@@ -202,19 +215,19 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si
if (!Common::Is4KBAligned(address)) {
LOG_ERROR(Kernel_SVC, "Address not page aligned (0x{:016X})", address);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (size == 0 || !Common::Is4KBAligned(size)) {
LOG_ERROR(Kernel_SVC, "Invalid size (0x{:X}). Size must be non-zero and page aligned.",
size);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (!IsValidAddressRange(address, size)) {
LOG_ERROR(Kernel_SVC, "Address range overflowed (Address: 0x{:016X}, Size: 0x{:016X})",
address, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
const auto attributes{static_cast<Memory::MemoryAttribute>(mask | attribute)};
@@ -223,7 +236,7 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si
LOG_ERROR(Kernel_SVC,
"Memory attribute doesn't match the given mask (Attribute: 0x{:X}, Mask: {:X}",
attribute, mask);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
auto& page_table{system.Kernel().CurrentProcess()->PageTable()};
@@ -234,8 +247,7 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si
static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask,
u32 attribute) {
- return SetMemoryAttribute(system, static_cast<VAddr>(address), static_cast<std::size_t>(size),
- mask, attribute);
+ return SetMemoryAttribute(system, address, size, mask, attribute);
}
/// Maps a memory range into a different range.
@@ -255,8 +267,7 @@ static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr
}
static ResultCode MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
- return MapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
- static_cast<std::size_t>(size));
+ return MapMemory(system, dst_addr, src_addr, size);
}
/// Unmaps a region that was previously mapped with svcMapMemory
@@ -276,8 +287,7 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad
}
static ResultCode UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
- return UnmapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
- static_cast<std::size_t>(size));
+ return UnmapMemory(system, dst_addr, src_addr, size);
}
/// Connect to an OS service given the port name, returns the handle to the port to out
@@ -290,7 +300,7 @@ static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle,
LOG_ERROR(Kernel_SVC,
"Port Name Address is not a valid virtual address, port_name_address=0x{:016X}",
port_name_address);
- return ERR_NOT_FOUND;
+ return ResultNotFound;
}
static constexpr std::size_t PortNameMaxLength = 11;
@@ -299,7 +309,7 @@ static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle,
if (port_name.size() > PortNameMaxLength) {
LOG_ERROR(Kernel_SVC, "Port name is too long, expected {} but got {}", PortNameMaxLength,
port_name.size());
- return ERR_OUT_OF_RANGE;
+ return ResultOutOfRange;
}
LOG_TRACE(Kernel_SVC, "called port_name={}", port_name);
@@ -308,11 +318,9 @@ static ResultCode ConnectToNamedPort(Core::System& system, Handle* out_handle,
const auto it = kernel.FindNamedPort(port_name);
if (!kernel.IsValidNamedPort(it)) {
LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name);
- return ERR_NOT_FOUND;
+ return ResultNotFound;
}
- ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Sessions, 1));
-
auto client_port = it->second;
std::shared_ptr<ClientSession> client_session;
@@ -332,40 +340,26 @@ static ResultCode ConnectToNamedPort32(Core::System& system, Handle* out_handle,
/// Makes a blocking IPC call to an OS service.
static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
- const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ auto& kernel = system.Kernel();
+ const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
std::shared_ptr<ClientSession> session = handle_table.Get<ClientSession>(handle);
if (!session) {
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
- auto thread = system.CurrentScheduler().GetCurrentThread();
+ auto thread = kernel.CurrentScheduler()->GetCurrentThread();
{
- SchedulerLock lock(system.Kernel());
- thread->InvalidateHLECallback();
- thread->SetStatus(ThreadStatus::WaitIPC);
- session->SendSyncRequest(SharedFrom(thread), system.Memory());
- }
-
- if (thread->HasHLECallback()) {
- Handle event_handle = thread->GetHLETimeEvent();
- if (event_handle != InvalidHandle) {
- auto& time_manager = system.Kernel().TimeManager();
- time_manager.UnscheduleTimeEvent(event_handle);
- }
-
- {
- SchedulerLock lock(system.Kernel());
- auto* sync_object = thread->GetHLESyncObject();
- sync_object->RemoveWaitingThread(SharedFrom(thread));
- }
-
- thread->InvokeHLECallback(SharedFrom(thread));
+ KScopedSchedulerLock lock(kernel);
+ thread->SetState(ThreadState::Waiting);
+ thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
+ session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming());
}
- return thread->GetSignalingResult();
+ KSynchronizationObject* dummy{};
+ return thread->GetWaitResult(std::addressof(dummy));
}
static ResultCode SendSyncRequest32(Core::System& system, Handle handle) {
@@ -373,27 +367,29 @@ static ResultCode SendSyncRequest32(Core::System& system, Handle handle) {
}
/// Get the ID for the specified thread.
-static ResultCode GetThreadId(Core::System& system, u64* thread_id, Handle thread_handle) {
+static ResultCode GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
+ // Get the thread from its handle.
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
+ const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", thread_handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle);
+ return ResultInvalidHandle;
}
- *thread_id = thread->GetThreadID();
+ // Get the thread's id.
+ *out_thread_id = thread->GetThreadID();
return RESULT_SUCCESS;
}
-static ResultCode GetThreadId32(Core::System& system, u32* thread_id_low, u32* thread_id_high,
- Handle thread_handle) {
- u64 thread_id{};
- const ResultCode result{GetThreadId(system, &thread_id, thread_handle)};
+static ResultCode GetThreadId32(Core::System& system, u32* out_thread_id_low,
+ u32* out_thread_id_high, Handle thread_handle) {
+ u64 out_thread_id{};
+ const ResultCode result{GetThreadId(system, &out_thread_id, thread_handle)};
- *thread_id_low = static_cast<u32>(thread_id >> 32);
- *thread_id_high = static_cast<u32>(thread_id & std::numeric_limits<u32>::max());
+ *out_thread_id_low = static_cast<u32>(out_thread_id >> 32);
+ *out_thread_id_high = static_cast<u32>(out_thread_id & std::numeric_limits<u32>::max());
return result;
}
@@ -409,12 +405,12 @@ static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle han
return RESULT_SUCCESS;
}
- const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(handle);
+ const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle);
if (thread) {
const Process* const owner_process = thread->GetOwnerProcess();
if (!owner_process) {
LOG_ERROR(Kernel_SVC, "Non-existent owning process encountered.");
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
*process_id = owner_process->GetProcessID();
@@ -424,7 +420,7 @@ static ResultCode GetProcessId(Core::System& system, u64* process_id, Handle han
// NOTE: This should also handle debug objects before returning.
LOG_ERROR(Kernel_SVC, "Handle does not exist, handle=0x{:08X}", handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
static ResultCode GetProcessId32(Core::System& system, u32* process_id_low, u32* process_id_high,
@@ -437,7 +433,7 @@ static ResultCode GetProcessId32(Core::System& system, u32* process_id_low, u32*
}
/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
-static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr handles_address,
+static ResultCode WaitSynchronization(Core::System& system, s32* index, VAddr handles_address,
u64 handle_count, s64 nano_seconds) {
LOG_TRACE(Kernel_SVC, "called handles_address=0x{:X}, handle_count={}, nano_seconds={}",
handles_address, handle_count, nano_seconds);
@@ -447,7 +443,7 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
LOG_ERROR(Kernel_SVC,
"Handle address is not a valid virtual address, handle_address=0x{:016X}",
handles_address);
- return ERR_INVALID_POINTER;
+ return ResultInvalidPointer;
}
static constexpr u64 MaxHandles = 0x40;
@@ -455,32 +451,30 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
if (handle_count > MaxHandles) {
LOG_ERROR(Kernel_SVC, "Handle count specified is too large, expected {} but got {}",
MaxHandles, handle_count);
- return ERR_OUT_OF_RANGE;
+ return ResultOutOfRange;
}
auto& kernel = system.Kernel();
- Thread::ThreadSynchronizationObjects objects(handle_count);
+ std::vector<KSynchronizationObject*> objects(handle_count);
const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
for (u64 i = 0; i < handle_count; ++i) {
const Handle handle = memory.Read32(handles_address + i * sizeof(Handle));
- const auto object = handle_table.Get<SynchronizationObject>(handle);
+ const auto object = handle_table.Get<KSynchronizationObject>(handle);
if (object == nullptr) {
LOG_ERROR(Kernel_SVC, "Object is a nullptr");
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
- objects[i] = object;
+ objects[i] = object.get();
}
- auto& synchronization = kernel.Synchronization();
- const auto [result, handle_result] = synchronization.WaitFor(objects, nano_seconds);
- *index = handle_result;
- return result;
+ return KSynchronizationObject::Wait(kernel, index, objects.data(),
+ static_cast<s32>(objects.size()), nano_seconds);
}
static ResultCode WaitSynchronization32(Core::System& system, u32 timeout_low, u32 handles_address,
- s32 handle_count, u32 timeout_high, Handle* index) {
+ s32 handle_count, u32 timeout_high, s32* index) {
const s64 nano_seconds{(static_cast<s64>(timeout_high) << 32) | static_cast<s64>(timeout_low)};
return WaitSynchronization(system, index, handles_address, handle_count, nano_seconds);
}
@@ -489,15 +483,17 @@ static ResultCode WaitSynchronization32(Core::System& system, u32 timeout_low, u
static ResultCode CancelSynchronization(Core::System& system, Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle);
+ // Get the thread from its handle.
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
+ std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
+
if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
- thread_handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle);
+ return ResultInvalidHandle;
}
- thread->CancelWait();
+ // Cancel the thread's wait.
+ thread->WaitCancel();
return RESULT_SUCCESS;
}
@@ -505,57 +501,53 @@ static ResultCode CancelSynchronization32(Core::System& system, Handle thread_ha
return CancelSynchronization(system, thread_handle);
}
-/// Attempts to locks a mutex, creating it if it does not already exist
-static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_handle,
- VAddr mutex_addr, Handle requesting_thread_handle) {
- LOG_TRACE(Kernel_SVC,
- "called holding_thread_handle=0x{:08X}, mutex_addr=0x{:X}, "
- "requesting_current_thread_handle=0x{:08X}",
- holding_thread_handle, mutex_addr, requesting_thread_handle);
+/// Attempts to locks a mutex
+static ResultCode ArbitrateLock(Core::System& system, Handle thread_handle, VAddr address,
+ u32 tag) {
+ LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address=0x{:X}, tag=0x{:08X}",
+ thread_handle, address, tag);
- if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) {
- LOG_ERROR(Kernel_SVC, "Mutex Address is a kernel virtual address, mutex_addr={:016X}",
- mutex_addr);
- return ERR_INVALID_ADDRESS_STATE;
+ // Validate the input address.
+ if (Memory::IsKernelAddress(address)) {
+ LOG_ERROR(Kernel_SVC, "Attempting to arbitrate a lock on a kernel address (address={:08X})",
+ address);
+ return ResultInvalidCurrentMemory;
}
-
- if (!Common::IsWordAligned(mutex_addr)) {
- LOG_ERROR(Kernel_SVC, "Mutex Address is not word aligned, mutex_addr={:016X}", mutex_addr);
- return ERR_INVALID_ADDRESS;
+ if (!Common::IsAligned(address, sizeof(u32))) {
+ LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address);
+ return ResultInvalidAddress;
}
- auto* const current_process = system.Kernel().CurrentProcess();
- return current_process->GetMutex().TryAcquire(mutex_addr, holding_thread_handle,
- requesting_thread_handle);
+ return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag);
}
-static ResultCode ArbitrateLock32(Core::System& system, Handle holding_thread_handle,
- u32 mutex_addr, Handle requesting_thread_handle) {
- return ArbitrateLock(system, holding_thread_handle, static_cast<VAddr>(mutex_addr),
- requesting_thread_handle);
+static ResultCode ArbitrateLock32(Core::System& system, Handle thread_handle, u32 address,
+ u32 tag) {
+ return ArbitrateLock(system, thread_handle, address, tag);
}
/// Unlock a mutex
-static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
- LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr);
+static ResultCode ArbitrateUnlock(Core::System& system, VAddr address) {
+ LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address);
- if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) {
- LOG_ERROR(Kernel_SVC, "Mutex Address is a kernel virtual address, mutex_addr={:016X}",
- mutex_addr);
- return ERR_INVALID_ADDRESS_STATE;
- }
+ // Validate the input address.
- if (!Common::IsWordAligned(mutex_addr)) {
- LOG_ERROR(Kernel_SVC, "Mutex Address is not word aligned, mutex_addr={:016X}", mutex_addr);
- return ERR_INVALID_ADDRESS;
+ if (Memory::IsKernelAddress(address)) {
+ LOG_ERROR(Kernel_SVC,
+ "Attempting to arbitrate an unlock on a kernel address (address={:08X})",
+ address);
+ return ResultInvalidCurrentMemory;
+ }
+ if (!Common::IsAligned(address, sizeof(u32))) {
+ LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address);
+ return ResultInvalidAddress;
}
- auto* const current_process = system.Kernel().CurrentProcess();
- return current_process->GetMutex().Release(mutex_addr);
+ return system.Kernel().CurrentProcess()->SignalToAddress(address);
}
-static ResultCode ArbitrateUnlock32(Core::System& system, u32 mutex_addr) {
- return ArbitrateUnlock(system, static_cast<VAddr>(mutex_addr));
+static ResultCode ArbitrateUnlock32(Core::System& system, u32 address) {
+ return ArbitrateUnlock(system, address);
}
enum class BreakType : u32 {
@@ -658,7 +650,6 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt);
if (!break_reason.signal_debugger) {
- SchedulerLock lock(system.Kernel());
LOG_CRITICAL(
Debug_Emulated,
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
@@ -666,22 +657,18 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
handle_debug_buffer(info1, info2);
- auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
- const auto thread_processor_id = current_thread->GetProcessorID();
+ auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
+ const auto thread_processor_id = current_thread->GetActiveCore();
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
-
- // Kill the current thread
- system.Kernel().ExceptionalExit();
- current_thread->Stop();
}
}
static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) {
- Break(system, reason, static_cast<u64>(info1), static_cast<u64>(info2));
+ Break(system, reason, info1, info2);
}
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
-static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr address, u64 len) {
+static void OutputDebugString(Core::System& system, VAddr address, u64 len) {
if (len == 0) {
return;
}
@@ -755,7 +742,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
if (info_sub_id != 0) {
LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id,
info_sub_id);
- return ERR_INVALID_ENUM_VALUE;
+ return ResultInvalidEnumValue;
}
const auto& current_process_handle_table =
@@ -764,7 +751,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
if (!process) {
LOG_ERROR(Kernel_SVC, "Process is not valid! info_id={}, info_sub_id={}, handle={:08X}",
info_id, info_sub_id, handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
switch (info_id_type) {
@@ -846,7 +833,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
}
LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id);
- return ERR_INVALID_ENUM_VALUE;
+ return ResultInvalidEnumValue;
}
case GetInfoType::IsCurrentProcessBeingDebugged:
@@ -856,13 +843,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
case GetInfoType::RegisterResourceLimit: {
if (handle != 0) {
LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
if (info_sub_id != 0) {
LOG_ERROR(Kernel, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id,
info_sub_id);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
Process* const current_process = system.Kernel().CurrentProcess();
@@ -887,13 +874,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
if (handle != 0) {
LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}",
handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
if (info_sub_id >= Process::RANDOM_ENTROPY_SIZE) {
LOG_ERROR(Kernel_SVC, "Entropy size is out of range, expected {} but got {}",
Process::RANDOM_ENTROPY_SIZE, info_sub_id);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
*result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id);
@@ -910,26 +897,26 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus,
info_sub_id);
- return ERR_INVALID_COMBINATION;
+ return ResultInvalidCombination;
}
- const auto thread = system.Kernel().CurrentProcess()->GetHandleTable().Get<Thread>(
+ const auto thread = system.Kernel().CurrentProcess()->GetHandleTable().Get<KThread>(
static_cast<Handle>(handle));
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}",
static_cast<Handle>(handle));
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
const auto& core_timing = system.CoreTiming();
- const auto& scheduler = system.CurrentScheduler();
+ const auto& scheduler = *system.Kernel().CurrentScheduler();
const auto* const current_thread = scheduler.GetCurrentThread();
const bool same_thread = current_thread == thread.get();
const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks();
u64 out_ticks = 0;
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
- const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
+ const u64 thread_ticks = current_thread->GetCpuTime();
out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks);
} else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
@@ -942,13 +929,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
default:
LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id);
- return ERR_INVALID_ENUM_VALUE;
+ return ResultInvalidEnumValue;
}
}
static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low,
u32 info_id, u32 handle, u32 sub_id_high) {
- const u64 sub_id{static_cast<u64>(sub_id_low | (static_cast<u64>(sub_id_high) << 32))};
+ const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)};
u64 res_value{};
const ResultCode result{GetInfo(system, &res_value, info_id, handle, sub_id)};
@@ -965,22 +952,22 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size)
if (!Common::Is4KBAligned(addr)) {
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (!Common::Is4KBAligned(size)) {
LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (size == 0) {
LOG_ERROR(Kernel_SVC, "Size is zero");
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (!(addr < addr + size)) {
LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address");
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
Process* const current_process{system.Kernel().CurrentProcess()};
@@ -988,28 +975,28 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size)
if (current_process->GetSystemResourceSize() == 0) {
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
- return ERR_INVALID_STATE;
+ return ResultInvalidState;
}
if (!page_table.IsInsideAddressSpace(addr, size)) {
LOG_ERROR(Kernel_SVC,
"Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr,
size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
if (page_table.IsOutsideAliasRegion(addr, size)) {
LOG_ERROR(Kernel_SVC,
"Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr,
size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
return page_table.MapPhysicalMemory(addr, size);
}
static ResultCode MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
- return MapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
+ return MapPhysicalMemory(system, addr, size);
}
/// Unmaps memory previously mapped via MapPhysicalMemory
@@ -1019,22 +1006,22 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size
if (!Common::Is4KBAligned(addr)) {
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (!Common::Is4KBAligned(size)) {
LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (size == 0) {
LOG_ERROR(Kernel_SVC, "Size is zero");
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (!(addr < addr + size)) {
LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address");
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
Process* const current_process{system.Kernel().CurrentProcess()};
@@ -1042,153 +1029,164 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size
if (current_process->GetSystemResourceSize() == 0) {
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
- return ERR_INVALID_STATE;
+ return ResultInvalidState;
}
if (!page_table.IsInsideAddressSpace(addr, size)) {
LOG_ERROR(Kernel_SVC,
"Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr,
size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
if (page_table.IsOutsideAliasRegion(addr, size)) {
LOG_ERROR(Kernel_SVC,
"Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr,
size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
return page_table.UnmapPhysicalMemory(addr, size);
}
static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
- return UnmapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
+ return UnmapPhysicalMemory(system, addr, size);
}
/// Sets the thread activity
-static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) {
- LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity);
- if (activity > static_cast<u32>(ThreadActivity::Paused)) {
- return ERR_INVALID_ENUM_VALUE;
+static ResultCode SetThreadActivity(Core::System& system, Handle thread_handle,
+ ThreadActivity thread_activity) {
+ LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle,
+ thread_activity);
+
+ // Validate the activity.
+ constexpr auto IsValidThreadActivity = [](ThreadActivity activity) {
+ return activity == ThreadActivity::Runnable || activity == ThreadActivity::Paused;
+ };
+ if (!IsValidThreadActivity(thread_activity)) {
+ LOG_ERROR(Kernel_SVC, "Invalid thread activity value provided (activity={})",
+ thread_activity);
+ return ResultInvalidEnumValue;
}
- const auto* current_process = system.Kernel().CurrentProcess();
- const std::shared_ptr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
+ // Get the thread from its handle.
+ auto& kernel = system.Kernel();
+ const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
+ const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle);
+ return ResultInvalidHandle;
}
- if (thread->GetOwnerProcess() != current_process) {
- LOG_ERROR(Kernel_SVC,
- "The current process does not own the current thread, thread_handle={:08X} "
- "thread_pid={}, "
- "current_process_pid={}",
- handle, thread->GetOwnerProcess()->GetProcessID(),
- current_process->GetProcessID());
- return ERR_INVALID_HANDLE;
+ // Check that the activity is being set on a non-current thread for the current process.
+ if (thread->GetOwnerProcess() != kernel.CurrentProcess()) {
+ LOG_ERROR(Kernel_SVC, "Invalid owning process for the created thread.");
+ return ResultInvalidHandle;
+ }
+ if (thread.get() == GetCurrentThreadPointer(kernel)) {
+ LOG_ERROR(Kernel_SVC, "Thread is busy");
+ return ResultBusy;
}
- if (thread.get() == system.CurrentScheduler().GetCurrentThread()) {
- LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread");
- return ERR_BUSY;
+ // Set the activity.
+ const auto set_result = thread->SetActivity(thread_activity);
+ if (set_result.IsError()) {
+ LOG_ERROR(Kernel_SVC, "Failed to set thread activity.");
+ return set_result;
}
- return thread->SetActivity(static_cast<ThreadActivity>(activity));
+ return RESULT_SUCCESS;
}
-static ResultCode SetThreadActivity32(Core::System& system, Handle handle, u32 activity) {
- return SetThreadActivity(system, handle, activity);
+static ResultCode SetThreadActivity32(Core::System& system, Handle thread_handle,
+ Svc::ThreadActivity thread_activity) {
+ return SetThreadActivity(system, thread_handle, thread_activity);
}
/// Gets the thread context
-static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, Handle handle) {
- LOG_DEBUG(Kernel_SVC, "called, context=0x{:08X}, thread=0x{:X}", thread_context, handle);
+static ResultCode GetThreadContext(Core::System& system, VAddr out_context, Handle thread_handle) {
+ LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle=0x{:X}", out_context,
+ thread_handle);
+ // Get the thread from its handle.
const auto* current_process = system.Kernel().CurrentProcess();
- const std::shared_ptr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
+ const std::shared_ptr<KThread> thread =
+ current_process->GetHandleTable().Get<KThread>(thread_handle);
if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={})", thread_handle);
+ return ResultInvalidHandle;
}
+ // Require the handle be to a non-current thread in the current process.
if (thread->GetOwnerProcess() != current_process) {
- LOG_ERROR(Kernel_SVC,
- "The current process does not own the current thread, thread_handle={:08X} "
- "thread_pid={}, "
- "current_process_pid={}",
- handle, thread->GetOwnerProcess()->GetProcessID(),
- current_process->GetProcessID());
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Thread owning process is not the current process.");
+ return ResultInvalidHandle;
}
-
- if (thread.get() == system.CurrentScheduler().GetCurrentThread()) {
- LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread");
- return ERR_BUSY;
+ if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) {
+ LOG_ERROR(Kernel_SVC, "Current thread is busy.");
+ return ResultBusy;
}
- Core::ARM_Interface::ThreadContext64 ctx = thread->GetContext64();
- // Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
- ctx.pstate &= 0xFF0FFE20;
-
- // If 64-bit, we can just write the context registers directly and we're good.
- // However, if 32-bit, we have to ensure some registers are zeroed out.
- if (!current_process->Is64BitProcess()) {
- std::fill(ctx.cpu_registers.begin() + 15, ctx.cpu_registers.end(), 0);
- std::fill(ctx.vector_registers.begin() + 16, ctx.vector_registers.end(), u128{});
+ // Get the thread context.
+ std::vector<u8> context;
+ const auto context_result = thread->GetThreadContext3(context);
+ if (context_result.IsError()) {
+ LOG_ERROR(Kernel_SVC, "Unable to successfully retrieve thread context (result: {})",
+ context_result.raw);
+ return context_result;
}
- system.Memory().WriteBlock(thread_context, &ctx, sizeof(ctx));
+ // Copy the thread context to user space.
+ system.Memory().WriteBlock(out_context, context.data(), context.size());
+
return RESULT_SUCCESS;
}
-static ResultCode GetThreadContext32(Core::System& system, u32 thread_context, Handle handle) {
- return GetThreadContext(system, static_cast<VAddr>(thread_context), handle);
+static ResultCode GetThreadContext32(Core::System& system, u32 out_context, Handle thread_handle) {
+ return GetThreadContext(system, out_context, thread_handle);
}
/// Gets the priority for the specified thread
-static ResultCode GetThreadPriority(Core::System& system, u32* priority, Handle handle) {
+static ResultCode GetThreadPriority(Core::System& system, u32* out_priority, Handle handle) {
LOG_TRACE(Kernel_SVC, "called");
+ // Get the thread from its handle.
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(handle);
+ const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle);
if (!thread) {
- *priority = 0;
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", handle);
+ return ResultInvalidHandle;
}
- *priority = thread->GetPriority();
+ // Get the thread's priority.
+ *out_priority = thread->GetPriority();
return RESULT_SUCCESS;
}
-static ResultCode GetThreadPriority32(Core::System& system, u32* priority, Handle handle) {
- return GetThreadPriority(system, priority, handle);
+static ResultCode GetThreadPriority32(Core::System& system, u32* out_priority, Handle handle) {
+ return GetThreadPriority(system, out_priority, handle);
}
/// Sets the priority for the specified thread
static ResultCode SetThreadPriority(Core::System& system, Handle handle, u32 priority) {
LOG_TRACE(Kernel_SVC, "called");
- if (priority > THREADPRIO_LOWEST) {
- LOG_ERROR(
- Kernel_SVC,
- "An invalid priority was specified, expected {} but got {} for thread_handle={:08X}",
- THREADPRIO_LOWEST, priority, handle);
- return ERR_INVALID_THREAD_PRIORITY;
+ // Validate the priority.
+ if (HighestThreadPriority > priority || priority > LowestThreadPriority) {
+ LOG_ERROR(Kernel_SVC, "Invalid thread priority specified (priority={})", priority);
+ return ResultInvalidPriority;
}
- const auto* const current_process = system.Kernel().CurrentProcess();
-
- std::shared_ptr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
+ // Get the thread from its handle.
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle);
if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid handle provided (handle={:08X})", handle);
+ return ResultInvalidHandle;
}
- thread->SetPriority(priority);
-
+ // Set the thread priority.
+ thread->SetBasePriority(priority);
return RESULT_SUCCESS;
}
@@ -1215,23 +1213,23 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
if (!Common::Is4KBAligned(addr)) {
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, addr=0x{:016X}", addr);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (size == 0) {
LOG_ERROR(Kernel_SVC, "Size is 0");
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (!Common::Is4KBAligned(size)) {
LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, size=0x{:016X}", size);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (!IsValidAddressRange(addr, size)) {
LOG_ERROR(Kernel_SVC, "Region is not a valid address range, addr=0x{:016X}, size=0x{:016X}",
addr, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
const auto permission_type = static_cast<Memory::MemoryPermission>(permissions);
@@ -1239,7 +1237,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
Memory::MemoryPermission::ReadAndWrite) {
LOG_ERROR(Kernel_SVC, "Expected Read or ReadWrite permission but got permissions=0x{:08X}",
permissions);
- return ERR_INVALID_MEMORY_PERMISSIONS;
+ return ResultInvalidMemoryPermissions;
}
auto* const current_process{system.Kernel().CurrentProcess()};
@@ -1250,7 +1248,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
"Addr does not fit within the valid region, addr=0x{:016X}, "
"size=0x{:016X}",
addr, size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
if (page_table.IsInsideHeapRegion(addr, size)) {
@@ -1258,7 +1256,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
"Addr does not fit within the heap region, addr=0x{:016X}, "
"size=0x{:016X}",
addr, size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
if (page_table.IsInsideAliasRegion(addr, size)) {
@@ -1266,14 +1264,14 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
"Address does not fit within the map region, addr=0x{:016X}, "
"size=0x{:016X}",
addr, size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
auto shared_memory{current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle)};
if (!shared_memory) {
LOG_ERROR(Kernel_SVC, "Shared memory does not exist, shared_memory_handle=0x{:08X}",
shared_memory_handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
return shared_memory->Map(*current_process, addr, size, permission_type);
@@ -1281,8 +1279,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
static ResultCode MapSharedMemory32(Core::System& system, Handle shared_memory_handle, u32 addr,
u32 size, u32 permissions) {
- return MapSharedMemory(system, shared_memory_handle, static_cast<VAddr>(addr),
- static_cast<std::size_t>(size), permissions);
+ return MapSharedMemory(system, shared_memory_handle, addr, size, permissions);
}
static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address,
@@ -1295,7 +1292,7 @@ static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_add
if (!process) {
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}",
process_handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
auto& memory{system.Memory()};
@@ -1342,18 +1339,18 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
if (!Common::Is4KBAligned(src_address)) {
LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).",
src_address);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (!Common::Is4KBAligned(dst_address)) {
LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).",
dst_address);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (size == 0 || !Common::Is4KBAligned(size)) {
LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X})", size);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (!IsValidAddressRange(dst_address, size)) {
@@ -1361,7 +1358,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
"Destination address range overflows the address space (dst_address=0x{:016X}, "
"size=0x{:016X}).",
dst_address, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (!IsValidAddressRange(src_address, size)) {
@@ -1369,7 +1366,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
"Source address range overflows the address space (src_address=0x{:016X}, "
"size=0x{:016X}).",
src_address, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
@@ -1377,7 +1374,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
if (!process) {
LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).",
process_handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
auto& page_table = process->PageTable();
@@ -1386,7 +1383,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
"Source address range is not within the address space (src_address=0x{:016X}, "
"size=0x{:016X}).",
src_address, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (!page_table.IsInsideASLRRegion(dst_address, size)) {
@@ -1394,7 +1391,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
"Destination address range is not within the ASLR region (dst_address=0x{:016X}, "
"size=0x{:016X}).",
dst_address, size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
return page_table.MapProcessCodeMemory(dst_address, src_address, size);
@@ -1410,18 +1407,18 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
if (!Common::Is4KBAligned(dst_address)) {
LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).",
dst_address);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (!Common::Is4KBAligned(src_address)) {
LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).",
src_address);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (size == 0 || Common::Is4KBAligned(size)) {
LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size);
- return ERR_INVALID_SIZE;
+ return ResultInvalidSize;
}
if (!IsValidAddressRange(dst_address, size)) {
@@ -1429,7 +1426,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
"Destination address range overflows the address space (dst_address=0x{:016X}, "
"size=0x{:016X}).",
dst_address, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (!IsValidAddressRange(src_address, size)) {
@@ -1437,7 +1434,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
"Source address range overflows the address space (src_address=0x{:016X}, "
"size=0x{:016X}).",
src_address, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
@@ -1445,7 +1442,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
if (!process) {
LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).",
process_handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
auto& page_table = process->PageTable();
@@ -1454,7 +1451,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
"Source address range is not within the address space (src_address=0x{:016X}, "
"size=0x{:016X}).",
src_address, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
if (!page_table.IsInsideASLRRegion(dst_address, size)) {
@@ -1462,7 +1459,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
"Destination address range is not within the ASLR region (dst_address=0x{:016X}, "
"size=0x{:016X}).",
dst_address, size);
- return ERR_INVALID_MEMORY_RANGE;
+ return ResultInvalidMemoryRange;
}
return page_table.UnmapProcessCodeMemory(dst_address, src_address, size);
@@ -1480,62 +1477,67 @@ static void ExitProcess(Core::System& system) {
current_process->PrepareForTermination();
// Kill the current thread
- system.CurrentScheduler().GetCurrentThread()->Stop();
+ system.Kernel().CurrentScheduler()->GetCurrentThread()->Exit();
}
static void ExitProcess32(Core::System& system) {
ExitProcess(system);
}
+static constexpr bool IsValidCoreId(int32_t core_id) {
+ return (0 <= core_id && core_id < static_cast<int32_t>(Core::Hardware::NUM_CPU_CORES));
+}
+
/// Creates a new thread
static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr entry_point, u64 arg,
- VAddr stack_top, u32 priority, s32 processor_id) {
+ VAddr stack_bottom, u32 priority, s32 core_id) {
LOG_DEBUG(Kernel_SVC,
- "called entrypoint=0x{:08X}, arg=0x{:08X}, stacktop=0x{:08X}, "
- "threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}",
- entry_point, arg, stack_top, priority, processor_id, *out_handle);
+ "called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, "
+ "priority=0x{:08X}, core_id=0x{:08X}",
+ entry_point, arg, stack_bottom, priority, core_id);
- auto* const current_process = system.Kernel().CurrentProcess();
-
- if (processor_id == THREADPROCESSORID_IDEAL) {
- // Set the target CPU to the one specified by the process.
- processor_id = current_process->GetIdealCore();
- ASSERT(processor_id != THREADPROCESSORID_IDEAL);
+ // Adjust core id, if it's the default magic.
+ auto& kernel = system.Kernel();
+ auto& process = *kernel.CurrentProcess();
+ if (core_id == IdealCoreUseProcessValue) {
+ core_id = process.GetIdealCoreId();
}
- if (processor_id < THREADPROCESSORID_0 || processor_id > THREADPROCESSORID_3) {
- LOG_ERROR(Kernel_SVC, "Invalid thread processor ID: {}", processor_id);
- return ERR_INVALID_PROCESSOR_ID;
+ // Validate arguments.
+ if (!IsValidCoreId(core_id)) {
+ LOG_ERROR(Kernel_SVC, "Invalid Core ID specified (id={})", core_id);
+ return ResultInvalidCoreId;
}
-
- const u64 core_mask = current_process->GetCoreMask();
- if ((core_mask | (1ULL << processor_id)) != core_mask) {
- LOG_ERROR(Kernel_SVC, "Invalid thread core specified ({})", processor_id);
- return ERR_INVALID_PROCESSOR_ID;
+ if (((1ULL << core_id) & process.GetCoreMask()) == 0) {
+ LOG_ERROR(Kernel_SVC, "Core ID doesn't fall within allowable cores (id={})", core_id);
+ return ResultInvalidCoreId;
}
- if (priority > THREADPRIO_LOWEST) {
- LOG_ERROR(Kernel_SVC,
- "Invalid thread priority specified ({}). Must be within the range 0-64",
- priority);
- return ERR_INVALID_THREAD_PRIORITY;
+ if (HighestThreadPriority > priority || priority > LowestThreadPriority) {
+ LOG_ERROR(Kernel_SVC, "Invalid priority specified (priority={})", priority);
+ return ResultInvalidPriority;
}
-
- if (((1ULL << priority) & current_process->GetPriorityMask()) == 0) {
- LOG_ERROR(Kernel_SVC, "Invalid thread priority specified ({})", priority);
- return ERR_INVALID_THREAD_PRIORITY;
+ if (!process.CheckThreadPriority(priority)) {
+ LOG_ERROR(Kernel_SVC, "Invalid allowable thread priority (priority={})", priority);
+ return ResultInvalidPriority;
}
- auto& kernel = system.Kernel();
-
- ASSERT(kernel.CurrentProcess()->GetResourceLimit()->Reserve(ResourceType::Threads, 1));
+ KScopedResourceReservation thread_reservation(
+ kernel.CurrentProcess(), LimitableResource::Threads, 1,
+ system.CoreTiming().GetGlobalTimeNs().count() + 100000000);
+ if (!thread_reservation.Succeeded()) {
+ LOG_ERROR(Kernel_SVC, "Could not reserve a new thread");
+ return ResultResourceLimitedExceeded;
+ }
- ThreadType type = THREADTYPE_USER;
- CASCADE_RESULT(std::shared_ptr<Thread> thread,
- Thread::Create(system, type, "", entry_point, priority, arg, processor_id,
- stack_top, current_process));
+ std::shared_ptr<KThread> thread;
+ {
+ KScopedLightLock lk{process.GetStateLock()};
+ CASCADE_RESULT(thread, KThread::Create(system, ThreadType::User, "", entry_point, priority,
+ arg, core_id, stack_bottom, &process));
+ }
- const auto new_thread_handle = current_process->GetHandleTable().Create(thread);
+ const auto new_thread_handle = process.GetHandleTable().Create(thread);
if (new_thread_handle.Failed()) {
LOG_ERROR(Kernel_SVC, "Failed to create handle with error=0x{:X}",
new_thread_handle.Code().raw);
@@ -1546,31 +1548,38 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
// Set the thread name for debugging purposes.
thread->SetName(
fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle));
+ thread_reservation.Commit();
return RESULT_SUCCESS;
}
static ResultCode CreateThread32(Core::System& system, Handle* out_handle, u32 priority,
u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) {
- return CreateThread(system, out_handle, static_cast<VAddr>(entry_point), static_cast<u64>(arg),
- static_cast<VAddr>(stack_top), priority, processor_id);
+ return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id);
}
/// Starts the thread for the provided handle
static ResultCode StartThread(Core::System& system, Handle thread_handle) {
LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
+ // Get the thread from its handle.
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
+ const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
- thread_handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle);
+ return ResultInvalidHandle;
}
- ASSERT(thread->GetStatus() == ThreadStatus::Dormant);
+ // Try to start the thread.
+ const auto run_result = thread->Run();
+ if (run_result.IsError()) {
+ LOG_ERROR(Kernel_SVC,
+ "Unable to successfuly start thread (thread handle={:08X}, result={})",
+ thread_handle, run_result.raw);
+ return run_result;
+ }
- return thread->Start();
+ return RESULT_SUCCESS;
}
static ResultCode StartThread32(Core::System& system, Handle thread_handle) {
@@ -1581,9 +1590,9 @@ static ResultCode StartThread32(Core::System& system, Handle thread_handle) {
static void ExitThread(Core::System& system) {
LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
- auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
- system.GlobalScheduler().RemoveThread(SharedFrom(current_thread));
- current_thread->Stop();
+ auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
+ system.GlobalSchedulerContext().RemoveThread(SharedFrom(current_thread));
+ current_thread->Exit();
}
static void ExitThread32(Core::System& system) {
@@ -1592,277 +1601,190 @@ static void ExitThread32(Core::System& system) {
/// Sleep the current thread
static void SleepThread(Core::System& system, s64 nanoseconds) {
- LOG_DEBUG(Kernel_SVC, "called nanoseconds={}", nanoseconds);
-
- enum class SleepType : s64 {
- YieldWithoutLoadBalancing = 0,
- YieldWithLoadBalancing = -1,
- YieldAndWaitForLoadBalancing = -2,
- };
-
- auto& scheduler = system.CurrentScheduler();
- auto* const current_thread = scheduler.GetCurrentThread();
- bool is_redundant = false;
-
- if (nanoseconds <= 0) {
- switch (static_cast<SleepType>(nanoseconds)) {
- case SleepType::YieldWithoutLoadBalancing: {
- auto pair = current_thread->YieldSimple();
- is_redundant = pair.second;
- break;
- }
- case SleepType::YieldWithLoadBalancing: {
- auto pair = current_thread->YieldAndBalanceLoad();
- is_redundant = pair.second;
- break;
- }
- case SleepType::YieldAndWaitForLoadBalancing: {
- auto pair = current_thread->YieldAndWaitForLoadBalancing();
- is_redundant = pair.second;
- break;
- }
- default:
- UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
- }
+ auto& kernel = system.Kernel();
+ const auto yield_type = static_cast<Svc::YieldType>(nanoseconds);
+
+ LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
+
+ // When the input tick is positive, sleep.
+ if (nanoseconds > 0) {
+ // Convert the timeout from nanoseconds to ticks.
+ // NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
+
+ // Sleep.
+ // NOTE: Nintendo does not check the result of this sleep.
+ static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds));
+ } else if (yield_type == Svc::YieldType::WithoutCoreMigration) {
+ KScheduler::YieldWithoutCoreMigration(kernel);
+ } else if (yield_type == Svc::YieldType::WithCoreMigration) {
+ KScheduler::YieldWithCoreMigration(kernel);
+ } else if (yield_type == Svc::YieldType::ToAnyThread) {
+ KScheduler::YieldToAnyThread(kernel);
} else {
- current_thread->Sleep(nanoseconds);
- }
-
- if (is_redundant && !system.Kernel().IsMulticore()) {
- system.Kernel().ExitSVCProfile();
- system.CoreTiming().AddTicks(1000U);
- system.GetCpuManager().PreemptSingleCore();
- system.Kernel().EnterSVCProfile();
+ // Nintendo does nothing at all if an otherwise invalid value is passed.
+ UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
}
static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) {
- const s64 nanoseconds = static_cast<s64>(static_cast<u64>(nanoseconds_low) |
- (static_cast<u64>(nanoseconds_high) << 32));
+ const auto nanoseconds = static_cast<s64>(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32));
SleepThread(system, nanoseconds);
}
/// Wait process wide key atomic
-static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_addr,
- VAddr condition_variable_addr, Handle thread_handle,
- s64 nano_seconds) {
- LOG_TRACE(
- Kernel_SVC,
- "called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
- mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
-
- if (Core::Memory::IsKernelVirtualAddress(mutex_addr)) {
- LOG_ERROR(
- Kernel_SVC,
- "Given mutex address must not be within the kernel address space. address=0x{:016X}",
- mutex_addr);
- return ERR_INVALID_ADDRESS_STATE;
- }
-
- if (!Common::IsWordAligned(mutex_addr)) {
- LOG_ERROR(Kernel_SVC, "Given mutex address must be word-aligned. address=0x{:016X}",
- mutex_addr);
- return ERR_INVALID_ADDRESS;
- }
-
- ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
- auto& kernel = system.Kernel();
- Handle event_handle;
- Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
- auto* const current_process = system.Kernel().CurrentProcess();
- {
- SchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds);
- const auto& handle_table = current_process->GetHandleTable();
- std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
- ASSERT(thread);
-
- current_thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
-
- if (thread->IsPendingTermination()) {
- lock.CancelSleep();
- return ERR_THREAD_TERMINATING;
- }
-
- const auto release_result = current_process->GetMutex().Release(mutex_addr);
- if (release_result.IsError()) {
- lock.CancelSleep();
- return release_result;
- }
-
- if (nano_seconds == 0) {
- lock.CancelSleep();
- return RESULT_TIMEOUT;
- }
-
- current_thread->SetCondVarWaitAddress(condition_variable_addr);
- current_thread->SetMutexWaitAddress(mutex_addr);
- current_thread->SetWaitHandle(thread_handle);
- current_thread->SetStatus(ThreadStatus::WaitCondVar);
- current_process->InsertConditionVariableThread(SharedFrom(current_thread));
- }
-
- if (event_handle != InvalidHandle) {
- auto& time_manager = kernel.TimeManager();
- time_manager.UnscheduleTimeEvent(event_handle);
- }
-
- {
- SchedulerLock lock(kernel);
-
- auto* owner = current_thread->GetLockOwner();
- if (owner != nullptr) {
- owner->RemoveMutexWaiter(SharedFrom(current_thread));
+static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr address, VAddr cv_key,
+ u32 tag, s64 timeout_ns) {
+ LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address,
+ cv_key, tag, timeout_ns);
+
+ // Validate input.
+ if (Memory::IsKernelAddress(address)) {
+ LOG_ERROR(Kernel_SVC, "Attempted to wait on kernel address (address={:08X})", address);
+ return ResultInvalidCurrentMemory;
+ }
+ if (!Common::IsAligned(address, sizeof(s32))) {
+ LOG_ERROR(Kernel_SVC, "Address must be 4 byte aligned (address={:08X})", address);
+ return ResultInvalidAddress;
+ }
+
+ // Convert timeout from nanoseconds to ticks.
+ s64 timeout{};
+ if (timeout_ns > 0) {
+ const s64 offset_tick(timeout_ns);
+ if (offset_tick > 0) {
+ timeout = offset_tick + 2;
+ if (timeout <= 0) {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = std::numeric_limits<s64>::max();
}
-
- current_process->RemoveConditionVariableThread(SharedFrom(current_thread));
+ } else {
+ timeout = timeout_ns;
}
- // Note: Deliberately don't attempt to inherit the lock owner's priority.
- return current_thread->GetSignalingResult();
+ // Wait on the condition variable.
+ return system.Kernel().CurrentProcess()->WaitConditionVariable(
+ address, Common::AlignDown(cv_key, sizeof(u32)), tag, timeout);
}
-static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 mutex_addr,
- u32 condition_variable_addr, Handle thread_handle,
- u32 nanoseconds_low, u32 nanoseconds_high) {
- const s64 nanoseconds =
- static_cast<s64>(nanoseconds_low | (static_cast<u64>(nanoseconds_high) << 32));
- return WaitProcessWideKeyAtomic(system, static_cast<VAddr>(mutex_addr),
- static_cast<VAddr>(condition_variable_addr), thread_handle,
- nanoseconds);
+static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 address, u32 cv_key, u32 tag,
+ u32 timeout_ns_low, u32 timeout_ns_high) {
+ const auto timeout_ns = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32));
+ return WaitProcessWideKeyAtomic(system, address, cv_key, tag, timeout_ns);
}
/// Signal process wide key
-static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_addr, s32 target) {
- LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
- condition_variable_addr, target);
+static void SignalProcessWideKey(Core::System& system, VAddr cv_key, s32 count) {
+ LOG_TRACE(Kernel_SVC, "called, cv_key=0x{:X}, count=0x{:08X}", cv_key, count);
- ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
+ // Signal the condition variable.
+ return system.Kernel().CurrentProcess()->SignalConditionVariable(
+ Common::AlignDown(cv_key, sizeof(u32)), count);
+}
- // Retrieve a list of all threads that are waiting for this condition variable.
- auto& kernel = system.Kernel();
- SchedulerLock lock(kernel);
- auto* const current_process = kernel.CurrentProcess();
- std::vector<std::shared_ptr<Thread>> waiting_threads =
- current_process->GetConditionVariableThreads(condition_variable_addr);
-
- // Only process up to 'target' threads, unless 'target' is less equal 0, in which case process
- // them all.
- std::size_t last = waiting_threads.size();
- if (target > 0) {
- last = std::min(waiting_threads.size(), static_cast<std::size_t>(target));
- }
- for (std::size_t index = 0; index < last; ++index) {
- auto& thread = waiting_threads[index];
-
- ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr);
-
- // liberate Cond Var Thread.
- current_process->RemoveConditionVariableThread(thread);
-
- const std::size_t current_core = system.CurrentCoreIndex();
- auto& monitor = system.Monitor();
-
- // Atomically read the value of the mutex.
- u32 mutex_val = 0;
- u32 update_val = 0;
- const VAddr mutex_address = thread->GetMutexWaitAddress();
- do {
- // If the mutex is not yet acquired, acquire it.
- mutex_val = monitor.ExclusiveRead32(current_core, mutex_address);
-
- if (mutex_val != 0) {
- update_val = mutex_val | Mutex::MutexHasWaitersFlag;
- } else {
- update_val = thread->GetWaitHandle();
- }
- } while (!monitor.ExclusiveWrite32(current_core, mutex_address, update_val));
- monitor.ClearExclusive();
- if (mutex_val == 0) {
- // We were able to acquire the mutex, resume this thread.
- auto* const lock_owner = thread->GetLockOwner();
- if (lock_owner != nullptr) {
- lock_owner->RemoveMutexWaiter(thread);
- }
+static void SignalProcessWideKey32(Core::System& system, u32 cv_key, s32 count) {
+ SignalProcessWideKey(system, cv_key, count);
+}
- thread->SetLockOwner(nullptr);
- thread->SetSynchronizationResults(nullptr, RESULT_SUCCESS);
- thread->ResumeFromWait();
- } else {
- // The mutex is already owned by some other thread, make this thread wait on it.
- const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
- const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- auto owner = handle_table.Get<Thread>(owner_handle);
- ASSERT(owner);
- if (thread->GetStatus() == ThreadStatus::WaitCondVar) {
- thread->SetStatus(ThreadStatus::WaitMutex);
- }
+namespace {
- owner->AddMutexWaiter(thread);
- }
+constexpr bool IsValidSignalType(Svc::SignalType type) {
+ switch (type) {
+ case Svc::SignalType::Signal:
+ case Svc::SignalType::SignalAndIncrementIfEqual:
+ case Svc::SignalType::SignalAndModifyByWaitingCountIfEqual:
+ return true;
+ default:
+ return false;
}
}
-static void SignalProcessWideKey32(Core::System& system, u32 condition_variable_addr, s32 target) {
- SignalProcessWideKey(system, condition_variable_addr, target);
+constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) {
+ switch (type) {
+ case Svc::ArbitrationType::WaitIfLessThan:
+ case Svc::ArbitrationType::DecrementAndWaitIfLessThan:
+ case Svc::ArbitrationType::WaitIfEqual:
+ return true;
+ default:
+ return false;
+ }
}
-// Wait for an address (via Address Arbiter)
-static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, s32 value,
- s64 timeout) {
- LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}", address,
- type, value, timeout);
-
- // If the passed address is a kernel virtual address, return invalid memory state.
- if (Core::Memory::IsKernelVirtualAddress(address)) {
- LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address);
- return ERR_INVALID_ADDRESS_STATE;
- }
+} // namespace
- // If the address is not properly aligned to 4 bytes, return invalid address.
- if (!Common::IsWordAligned(address)) {
- LOG_ERROR(Kernel_SVC, "Address is not word aligned, address={:016X}", address);
- return ERR_INVALID_ADDRESS;
+// Wait for an address (via Address Arbiter)
+static ResultCode WaitForAddress(Core::System& system, VAddr address, Svc::ArbitrationType arb_type,
+ s32 value, s64 timeout_ns) {
+ LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}",
+ address, arb_type, value, timeout_ns);
+
+ // Validate input.
+ if (Memory::IsKernelAddress(address)) {
+ LOG_ERROR(Kernel_SVC, "Attempting to wait on kernel address (address={:08X})", address);
+ return ResultInvalidCurrentMemory;
+ }
+ if (!Common::IsAligned(address, sizeof(s32))) {
+ LOG_ERROR(Kernel_SVC, "Wait address must be 4 byte aligned (address={:08X})", address);
+ return ResultInvalidAddress;
+ }
+ if (!IsValidArbitrationType(arb_type)) {
+ LOG_ERROR(Kernel_SVC, "Invalid arbitration type specified (type={})", arb_type);
+ return ResultInvalidEnumValue;
+ }
+
+ // Convert timeout from nanoseconds to ticks.
+ s64 timeout{};
+ if (timeout_ns > 0) {
+ const s64 offset_tick(timeout_ns);
+ if (offset_tick > 0) {
+ timeout = offset_tick + 2;
+ if (timeout <= 0) {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = std::numeric_limits<s64>::max();
+ }
+ } else {
+ timeout = timeout_ns;
}
- const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type);
- auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter();
- const ResultCode result =
- address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
- return result;
+ return system.Kernel().CurrentProcess()->WaitAddressArbiter(address, arb_type, value, timeout);
}
-static ResultCode WaitForAddress32(Core::System& system, u32 address, u32 type, s32 value,
- u32 timeout_low, u32 timeout_high) {
- s64 timeout = static_cast<s64>(timeout_low | (static_cast<u64>(timeout_high) << 32));
- return WaitForAddress(system, static_cast<VAddr>(address), type, value, timeout);
+static ResultCode WaitForAddress32(Core::System& system, u32 address, Svc::ArbitrationType arb_type,
+ s32 value, u32 timeout_ns_low, u32 timeout_ns_high) {
+ const auto timeout = static_cast<s64>(timeout_ns_low | (u64{timeout_ns_high} << 32));
+ return WaitForAddress(system, address, arb_type, value, timeout);
}
// Signals to an address (via Address Arbiter)
-static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value,
- s32 num_to_wake) {
- LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}",
- address, type, value, num_to_wake);
+static ResultCode SignalToAddress(Core::System& system, VAddr address, Svc::SignalType signal_type,
+ s32 value, s32 count) {
+ LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}",
+ address, signal_type, value, count);
- // If the passed address is a kernel virtual address, return invalid memory state.
- if (Core::Memory::IsKernelVirtualAddress(address)) {
- LOG_ERROR(Kernel_SVC, "Address is a kernel virtual address, address={:016X}", address);
- return ERR_INVALID_ADDRESS_STATE;
+ // Validate input.
+ if (Memory::IsKernelAddress(address)) {
+ LOG_ERROR(Kernel_SVC, "Attempting to signal to a kernel address (address={:08X})", address);
+ return ResultInvalidCurrentMemory;
}
-
- // If the address is not properly aligned to 4 bytes, return invalid address.
- if (!Common::IsWordAligned(address)) {
- LOG_ERROR(Kernel_SVC, "Address is not word aligned, address={:016X}", address);
- return ERR_INVALID_ADDRESS;
+ if (!Common::IsAligned(address, sizeof(s32))) {
+ LOG_ERROR(Kernel_SVC, "Signaled address must be 4 byte aligned (address={:08X})", address);
+ return ResultInvalidAddress;
+ }
+ if (!IsValidSignalType(signal_type)) {
+ LOG_ERROR(Kernel_SVC, "Invalid signal type specified (type={})", signal_type);
+ return ResultInvalidEnumValue;
}
- const auto signal_type = static_cast<AddressArbiter::SignalType>(type);
- auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter();
- return address_arbiter.SignalToAddress(address, signal_type, value, num_to_wake);
+ return system.Kernel().CurrentProcess()->SignalAddressArbiter(address, signal_type, value,
+ count);
}
-static ResultCode SignalToAddress32(Core::System& system, u32 address, u32 type, s32 value,
- s32 num_to_wake) {
- return SignalToAddress(system, static_cast<VAddr>(address), type, value, num_to_wake);
+static ResultCode SignalToAddress32(Core::System& system, u32 address, Svc::SignalType signal_type,
+ s32 value, s32 count) {
+ return SignalToAddress(system, address, signal_type, value, count);
}
static void KernelDebug([[maybe_unused]] Core::System& system,
@@ -1893,7 +1815,7 @@ static u64 GetSystemTick(Core::System& system) {
}
static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) {
- u64 time = GetSystemTick(system);
+ const auto time = GetSystemTick(system);
*time_low = static_cast<u32>(time);
*time_high = static_cast<u32>(time >> 32);
}
@@ -1914,20 +1836,28 @@ static ResultCode CloseHandle32(Core::System& system, Handle handle) {
static ResultCode ResetSignal(Core::System& system, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle);
+ // Get the current handle table.
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- auto event = handle_table.Get<ReadableEvent>(handle);
- if (event) {
- return event->Reset();
+ // Try to reset as readable event.
+ {
+ auto readable_event = handle_table.Get<KReadableEvent>(handle);
+ if (readable_event) {
+ return readable_event->Reset();
+ }
}
- auto process = handle_table.Get<Process>(handle);
- if (process) {
- return process->ClearSignalState();
+ // Try to reset as process.
+ {
+ auto process = handle_table.Get<Process>(handle);
+ if (process) {
+ return process->Reset();
+ }
}
- LOG_ERROR(Kernel_SVC, "Invalid handle (0x{:08X})", handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "invalid handle (0x{:08X})", handle);
+
+ return ResultInvalidHandle;
}
static ResultCode ResetSignal32(Core::System& system, Handle handle) {
@@ -1943,18 +1873,18 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
if (!Common::Is4KBAligned(addr)) {
LOG_ERROR(Kernel_SVC, "Address ({:016X}) is not page aligned!", addr);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (!Common::Is4KBAligned(size) || size == 0) {
LOG_ERROR(Kernel_SVC, "Size ({:016X}) is not page aligned or equal to zero!", size);
- return ERR_INVALID_ADDRESS;
+ return ResultInvalidAddress;
}
if (!IsValidAddressRange(addr, size)) {
LOG_ERROR(Kernel_SVC, "Address and size cause overflow! (address={:016X}, size={:016X})",
addr, size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
const auto perms{static_cast<Memory::MemoryPermission>(permissions)};
@@ -1962,10 +1892,17 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
perms == Memory::MemoryPermission::Write) {
LOG_ERROR(Kernel_SVC, "Invalid memory permissions for transfer memory! (perms={:08X})",
permissions);
- return ERR_INVALID_MEMORY_PERMISSIONS;
+ return ResultInvalidMemoryPermissions;
}
auto& kernel = system.Kernel();
+ // Reserve a new transfer memory from the process resource limit.
+ KScopedResourceReservation trmem_reservation(kernel.CurrentProcess(),
+ LimitableResource::TransferMemory);
+ if (!trmem_reservation.Succeeded()) {
+ LOG_ERROR(Kernel_SVC, "Could not reserve a new transfer memory");
+ return ResultResourceLimitedExceeded;
+ }
auto transfer_mem_handle = TransferMemory::Create(kernel, system.Memory(), addr, size, perms);
if (const auto reserve_result{transfer_mem_handle->Reserve()}; reserve_result.IsError()) {
@@ -1977,6 +1914,7 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
if (result.Failed()) {
return result.Code();
}
+ trmem_reservation.Commit();
*handle = *result;
return RESULT_SUCCESS;
@@ -1984,176 +1922,207 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
static ResultCode CreateTransferMemory32(Core::System& system, Handle* handle, u32 addr, u32 size,
u32 permissions) {
- return CreateTransferMemory(system, handle, static_cast<VAddr>(addr),
- static_cast<std::size_t>(size), permissions);
+ return CreateTransferMemory(system, handle, addr, size, permissions);
}
-static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core,
- u64* mask) {
+static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, s32* out_core_id,
+ u64* out_affinity_mask) {
LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
+ // Get the thread from its handle.
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
+ const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
- thread_handle);
- *core = 0;
- *mask = 0;
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid thread handle specified (handle={:08X})", thread_handle);
+ return ResultInvalidHandle;
}
- *core = thread->GetIdealCore();
- *mask = thread->GetAffinityMask();
+ // Get the core mask.
+ const auto result = thread->GetCoreMask(out_core_id, out_affinity_mask);
+ if (result.IsError()) {
+ LOG_ERROR(Kernel_SVC, "Unable to successfully retrieve core mask (result={})", result.raw);
+ return result;
+ }
return RESULT_SUCCESS;
}
-static ResultCode GetThreadCoreMask32(Core::System& system, Handle thread_handle, u32* core,
- u32* mask_low, u32* mask_high) {
- u64 mask{};
- const auto result = GetThreadCoreMask(system, thread_handle, core, &mask);
- *mask_high = static_cast<u32>(mask >> 32);
- *mask_low = static_cast<u32>(mask);
+static ResultCode GetThreadCoreMask32(Core::System& system, Handle thread_handle, s32* out_core_id,
+ u32* out_affinity_mask_low, u32* out_affinity_mask_high) {
+ u64 out_affinity_mask{};
+ const auto result = GetThreadCoreMask(system, thread_handle, out_core_id, &out_affinity_mask);
+ *out_affinity_mask_high = static_cast<u32>(out_affinity_mask >> 32);
+ *out_affinity_mask_low = static_cast<u32>(out_affinity_mask);
return result;
}
-static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, u32 core,
+static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id,
u64 affinity_mask) {
- LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, core=0x{:X}, affinity_mask=0x{:016X}",
- thread_handle, core, affinity_mask);
-
- const auto* const current_process = system.Kernel().CurrentProcess();
+ LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, core_id=0x{:X}, affinity_mask=0x{:016X}",
+ thread_handle, core_id, affinity_mask);
- if (core == static_cast<u32>(THREADPROCESSORID_IDEAL)) {
- const u8 ideal_cpu_core = current_process->GetIdealCore();
+ const auto& current_process = *system.Kernel().CurrentProcess();
- ASSERT(ideal_cpu_core != static_cast<u8>(THREADPROCESSORID_IDEAL));
-
- // Set the target CPU to the ideal core specified by the process.
- core = ideal_cpu_core;
- affinity_mask = 1ULL << core;
+ // Determine the core id/affinity mask.
+ if (core_id == Svc::IdealCoreUseProcessValue) {
+ core_id = current_process.GetIdealCoreId();
+ affinity_mask = (1ULL << core_id);
} else {
- const u64 core_mask = current_process->GetCoreMask();
-
- if ((core_mask | affinity_mask) != core_mask) {
- LOG_ERROR(
- Kernel_SVC,
- "Invalid processor ID specified (core_mask=0x{:08X}, affinity_mask=0x{:016X})",
- core_mask, affinity_mask);
- return ERR_INVALID_PROCESSOR_ID;
+ // Validate the affinity mask.
+ const u64 process_core_mask = current_process.GetCoreMask();
+ if ((affinity_mask | process_core_mask) != process_core_mask) {
+ LOG_ERROR(Kernel_SVC,
+ "Affinity mask does match the process core mask (affinity mask={:016X}, core "
+ "mask={:016X})",
+ affinity_mask, process_core_mask);
+ return ResultInvalidCoreId;
}
-
if (affinity_mask == 0) {
- LOG_ERROR(Kernel_SVC, "Specfified affinity mask is zero.");
- return ERR_INVALID_COMBINATION;
+ LOG_ERROR(Kernel_SVC, "Affinity mask is zero.");
+ return ResultInvalidCombination;
}
- if (core < Core::Hardware::NUM_CPU_CORES) {
- if ((affinity_mask & (1ULL << core)) == 0) {
- LOG_ERROR(Kernel_SVC,
- "Core is not enabled for the current mask, core={}, mask={:016X}", core,
- affinity_mask);
- return ERR_INVALID_COMBINATION;
+ // Validate the core id.
+ if (IsValidCoreId(core_id)) {
+ if (((1ULL << core_id) & affinity_mask) == 0) {
+ LOG_ERROR(Kernel_SVC, "Invalid core ID (ID={})", core_id);
+ return ResultInvalidCombination;
+ }
+ } else {
+ if (core_id != IdealCoreNoUpdate && core_id != IdealCoreDontCare) {
+ LOG_ERROR(Kernel_SVC, "Invalid core ID (ID={})", core_id);
+ return ResultInvalidCoreId;
}
- } else if (core != static_cast<u32>(THREADPROCESSORID_DONT_CARE) &&
- core != static_cast<u32>(THREADPROCESSORID_DONT_UPDATE)) {
- LOG_ERROR(Kernel_SVC, "Invalid processor ID specified (core={}).", core);
- return ERR_INVALID_PROCESSOR_ID;
}
}
- const auto& handle_table = current_process->GetHandleTable();
- const std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
+ // Get the thread from its handle.
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
if (!thread) {
- LOG_ERROR(Kernel_SVC, "Thread handle does not exist, thread_handle=0x{:08X}",
- thread_handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Invalid thread handle (handle={:08X})", thread_handle);
+ return ResultInvalidHandle;
}
- return thread->SetCoreAndAffinityMask(core, affinity_mask);
+ // Set the core mask.
+ const auto set_result = thread->SetCoreMask(core_id, affinity_mask);
+ if (set_result.IsError()) {
+ LOG_ERROR(Kernel_SVC, "Unable to successfully set core mask (result={})", set_result.raw);
+ return set_result;
+ }
+ return RESULT_SUCCESS;
}
-static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, u32 core,
+static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, s32 core_id,
u32 affinity_mask_low, u32 affinity_mask_high) {
- const u64 affinity_mask =
- static_cast<u64>(affinity_mask_low) | (static_cast<u64>(affinity_mask_high) << 32);
- return SetThreadCoreMask(system, thread_handle, core, affinity_mask);
+ const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32);
+ return SetThreadCoreMask(system, thread_handle, core_id, affinity_mask);
}
-static ResultCode CreateEvent(Core::System& system, Handle* write_handle, Handle* read_handle) {
- LOG_DEBUG(Kernel_SVC, "called");
+static ResultCode SignalEvent(Core::System& system, Handle event_handle) {
+ LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle);
auto& kernel = system.Kernel();
- const auto [readable_event, writable_event] =
- WritableEvent::CreateEventPair(kernel, "CreateEvent");
+ // Get the current handle table.
+ const HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable();
- HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable();
-
- const auto write_create_result = handle_table.Create(writable_event);
- if (write_create_result.Failed()) {
- return write_create_result.Code();
+ // Reserve a new event from the process resource limit.
+ KScopedResourceReservation event_reservation(kernel.CurrentProcess(),
+ LimitableResource::Events);
+ if (!event_reservation.Succeeded()) {
+ LOG_ERROR(Kernel, "Could not reserve a new event");
+ return ResultResourceLimitedExceeded;
}
- *write_handle = *write_create_result;
- const auto read_create_result = handle_table.Create(readable_event);
- if (read_create_result.Failed()) {
- handle_table.Close(*write_create_result);
- return read_create_result.Code();
+ // Get the writable event.
+ auto writable_event = handle_table.Get<KWritableEvent>(event_handle);
+ if (!writable_event) {
+ LOG_ERROR(Kernel_SVC, "Invalid event handle provided (handle={:08X})", event_handle);
+ return ResultInvalidHandle;
}
- *read_handle = *read_create_result;
- LOG_DEBUG(Kernel_SVC,
- "successful. Writable event handle=0x{:08X}, Readable event handle=0x{:08X}",
- *write_create_result, *read_create_result);
- return RESULT_SUCCESS;
+ // Commit the successfuly reservation.
+ event_reservation.Commit();
+
+ return writable_event->Signal();
}
-static ResultCode CreateEvent32(Core::System& system, Handle* write_handle, Handle* read_handle) {
- return CreateEvent(system, write_handle, read_handle);
+static ResultCode SignalEvent32(Core::System& system, Handle event_handle) {
+ return SignalEvent(system, event_handle);
}
-static ResultCode ClearEvent(Core::System& system, Handle handle) {
- LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
+static ResultCode ClearEvent(Core::System& system, Handle event_handle) {
+ LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle);
+ // Get the current handle table.
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- auto writable_event = handle_table.Get<WritableEvent>(handle);
- if (writable_event) {
- writable_event->Clear();
- return RESULT_SUCCESS;
+ // Try to clear the writable event.
+ {
+ auto writable_event = handle_table.Get<KWritableEvent>(event_handle);
+ if (writable_event) {
+ return writable_event->Clear();
+ }
}
- auto readable_event = handle_table.Get<ReadableEvent>(handle);
- if (readable_event) {
- readable_event->Clear();
- return RESULT_SUCCESS;
+ // Try to clear the readable event.
+ {
+ auto readable_event = handle_table.Get<KReadableEvent>(event_handle);
+ if (readable_event) {
+ return readable_event->Clear();
+ }
}
- LOG_ERROR(Kernel_SVC, "Event handle does not exist, handle=0x{:08X}", handle);
- return ERR_INVALID_HANDLE;
+ LOG_ERROR(Kernel_SVC, "Event handle does not exist, event_handle=0x{:08X}", event_handle);
+
+ return ResultInvalidHandle;
}
-static ResultCode ClearEvent32(Core::System& system, Handle handle) {
- return ClearEvent(system, handle);
+static ResultCode ClearEvent32(Core::System& system, Handle event_handle) {
+ return ClearEvent(system, event_handle);
}
-static ResultCode SignalEvent(Core::System& system, Handle handle) {
- LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle);
+static ResultCode CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) {
+ LOG_DEBUG(Kernel_SVC, "called");
- HandleTable& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
- auto writable_event = handle_table.Get<WritableEvent>(handle);
+ // Get the kernel reference and handle table.
+ auto& kernel = system.Kernel();
+ HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable();
- if (!writable_event) {
- LOG_ERROR(Kernel_SVC, "Non-existent writable event handle used (0x{:08X})", handle);
- return ERR_INVALID_HANDLE;
+ // Create a new event.
+ const auto event = KEvent::Create(kernel, "CreateEvent");
+ if (!event) {
+ LOG_ERROR(Kernel_SVC, "Unable to create new events. Event creation limit reached.");
+ return ResultOutOfResource;
+ }
+
+ // Initialize the event.
+ event->Initialize();
+
+ // Add the writable event to the handle table.
+ const auto write_create_result = handle_table.Create(event->GetWritableEvent());
+ if (write_create_result.Failed()) {
+ return write_create_result.Code();
+ }
+ *out_write = *write_create_result;
+
+ // Add the writable event to the handle table.
+ auto handle_guard = SCOPE_GUARD({ handle_table.Close(*write_create_result); });
+
+ // Add the readable event to the handle table.
+ const auto read_create_result = handle_table.Create(event->GetReadableEvent());
+ if (read_create_result.Failed()) {
+ return read_create_result.Code();
}
+ *out_read = *read_create_result;
- writable_event->Signal();
+ // We succeeded.
+ handle_guard.Cancel();
return RESULT_SUCCESS;
}
-static ResultCode SignalEvent32(Core::System& system, Handle handle) {
- return SignalEvent(system, handle);
+static ResultCode CreateEvent32(Core::System& system, Handle* out_write, Handle* out_read) {
+ return CreateEvent(system, out_write, out_read);
}
static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) {
@@ -2169,13 +2138,13 @@ static ResultCode GetProcessInfo(Core::System& system, u64* out, Handle process_
if (!process) {
LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}",
process_handle);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
const auto info_type = static_cast<InfoType>(type);
if (info_type != InfoType::Status) {
LOG_ERROR(Kernel_SVC, "Expected info_type to be Status but got {} instead", type);
- return ERR_INVALID_ENUM_VALUE;
+ return ResultInvalidEnumValue;
}
*out = static_cast<u64>(process->GetStatus());
@@ -2187,7 +2156,7 @@ static ResultCode CreateResourceLimit(Core::System& system, Handle* out_handle)
LOG_DEBUG(Kernel_SVC, "called");
auto& kernel = system.Kernel();
- auto resource_limit = ResourceLimit::Create(kernel);
+ auto resource_limit = std::make_shared<KResourceLimit>(kernel, system);
auto* const current_process = kernel.CurrentProcess();
ASSERT(current_process != nullptr);
@@ -2234,30 +2203,30 @@ static ResultCode SetResourceLimitLimitValue(Core::System& system, Handle resour
LOG_DEBUG(Kernel_SVC, "called. Handle={:08X}, Resource type={}, Value={}", resource_limit,
resource_type, value);
- const auto type = static_cast<ResourceType>(resource_type);
+ const auto type = static_cast<LimitableResource>(resource_type);
if (!IsValidResourceType(type)) {
LOG_ERROR(Kernel_SVC, "Invalid resource limit type: '{}'", resource_type);
- return ERR_INVALID_ENUM_VALUE;
+ return ResultInvalidEnumValue;
}
auto* const current_process = system.Kernel().CurrentProcess();
ASSERT(current_process != nullptr);
auto resource_limit_object =
- current_process->GetHandleTable().Get<ResourceLimit>(resource_limit);
+ current_process->GetHandleTable().Get<KResourceLimit>(resource_limit);
if (!resource_limit_object) {
LOG_ERROR(Kernel_SVC, "Handle to non-existent resource limit instance used. Handle={:08X}",
resource_limit);
- return ERR_INVALID_HANDLE;
+ return ResultInvalidHandle;
}
const auto set_result = resource_limit_object->SetLimitValue(type, static_cast<s64>(value));
if (set_result.IsError()) {
- LOG_ERROR(
- Kernel_SVC,
- "Attempted to lower resource limit ({}) for category '{}' below its current value ({})",
- resource_limit_object->GetMaxResourceValue(type), resource_type,
- resource_limit_object->GetCurrentResourceValue(type));
+ LOG_ERROR(Kernel_SVC,
+ "Attempted to lower resource limit ({}) for category '{}' below its current "
+ "value ({})",
+ resource_limit_object->GetLimitValue(type), resource_type,
+ resource_limit_object->GetCurrentValue(type));
return set_result;
}
@@ -2274,7 +2243,7 @@ static ResultCode GetProcessList(Core::System& system, u32* out_num_processes,
LOG_ERROR(Kernel_SVC,
"Supplied size outside [0, 0x0FFFFFFF] range. out_process_ids_size={}",
out_process_ids_size);
- return ERR_OUT_OF_RANGE;
+ return ResultOutOfRange;
}
const auto& kernel = system.Kernel();
@@ -2284,7 +2253,7 @@ static ResultCode GetProcessList(Core::System& system, u32* out_num_processes,
out_process_ids, total_copy_size)) {
LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}",
out_process_ids, out_process_ids + total_copy_size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
auto& memory = system.Memory();
@@ -2313,7 +2282,7 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
if ((out_thread_ids_size & 0xF0000000) != 0) {
LOG_ERROR(Kernel_SVC, "Supplied size outside [0, 0x0FFFFFFF] range. size={}",
out_thread_ids_size);
- return ERR_OUT_OF_RANGE;
+ return ResultOutOfRange;
}
const auto* const current_process = system.Kernel().CurrentProcess();
@@ -2323,7 +2292,7 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
!current_process->PageTable().IsInsideAddressSpace(out_thread_ids, total_copy_size)) {
LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}",
out_thread_ids, out_thread_ids + total_copy_size);
- return ERR_INVALID_ADDRESS_STATE;
+ return ResultInvalidCurrentMemory;
}
auto& memory = system.Memory();
@@ -2341,9 +2310,10 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
return RESULT_SUCCESS;
}
-static ResultCode FlushProcessDataCache32(Core::System& system, Handle handle, u32 address,
- u32 size) {
- // Note(Blinkhawk): For emulation purposes of the data cache this is mostly a nope
+static ResultCode FlushProcessDataCache32([[maybe_unused]] Core::System& system,
+ [[maybe_unused]] Handle handle,
+ [[maybe_unused]] u32 address, [[maybe_unused]] u32 size) {
+ // Note(Blinkhawk): For emulation purposes of the data cache this is mostly a no-op,
// as all emulation is done in the same cache level in host architecture, thus data cache
// does not need flushing.
LOG_DEBUG(Kernel_SVC, "called");
@@ -2639,6 +2609,9 @@ void Call(Core::System& system, u32 immediate) {
auto& kernel = system.Kernel();
kernel.EnterSVCProfile();
+ auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
+ thread->SetIsCallingSvc();
+
const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate)
: GetSVCInfo32(immediate);
if (info) {
@@ -2652,6 +2625,12 @@ void Call(Core::System& system, u32 immediate) {
}
kernel.ExitSVCProfile();
+
+ if (!thread->IsCallingSvc()) {
+ auto* host_context = thread->GetHostContext().get();
+ host_context->Rewind();
+ }
+
system.EnterDynarmicProfile();
}
diff --git a/src/core/hle/kernel/svc_common.h b/src/core/hle/kernel/svc_common.h
new file mode 100644
index 000000000..4af049551
--- /dev/null
+++ b/src/core/hle/kernel/svc_common.h
@@ -0,0 +1,14 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Kernel::Svc {
+
+constexpr s32 ArgumentHandleCountMax = 0x40;
+constexpr u32 HandleWaitMask{1u << 30};
+
+} // namespace Kernel::Svc
diff --git a/src/core/hle/kernel/svc_results.h b/src/core/hle/kernel/svc_results.h
new file mode 100644
index 000000000..a26d9f2c9
--- /dev/null
+++ b/src/core/hle/kernel/svc_results.h
@@ -0,0 +1,41 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+// Confirmed Switch kernel error codes
+
+constexpr ResultCode ResultMaxConnectionsReached{ErrorModule::Kernel, 7};
+constexpr ResultCode ResultInvalidCapabilityDescriptor{ErrorModule::Kernel, 14};
+constexpr ResultCode ResultNoSynchronizationObject{ErrorModule::Kernel, 57};
+constexpr ResultCode ResultTerminationRequested{ErrorModule::Kernel, 59};
+constexpr ResultCode ResultInvalidSize{ErrorModule::Kernel, 101};
+constexpr ResultCode ResultInvalidAddress{ErrorModule::Kernel, 102};
+constexpr ResultCode ResultOutOfResource{ErrorModule::Kernel, 103};
+constexpr ResultCode ResultOutOfMemory{ErrorModule::Kernel, 104};
+constexpr ResultCode ResultHandleTableFull{ErrorModule::Kernel, 105};
+constexpr ResultCode ResultInvalidCurrentMemory{ErrorModule::Kernel, 106};
+constexpr ResultCode ResultInvalidMemoryPermissions{ErrorModule::Kernel, 108};
+constexpr ResultCode ResultInvalidMemoryRange{ErrorModule::Kernel, 110};
+constexpr ResultCode ResultInvalidPriority{ErrorModule::Kernel, 112};
+constexpr ResultCode ResultInvalidCoreId{ErrorModule::Kernel, 113};
+constexpr ResultCode ResultInvalidHandle{ErrorModule::Kernel, 114};
+constexpr ResultCode ResultInvalidPointer{ErrorModule::Kernel, 115};
+constexpr ResultCode ResultInvalidCombination{ErrorModule::Kernel, 116};
+constexpr ResultCode ResultTimedOut{ErrorModule::Kernel, 117};
+constexpr ResultCode ResultCancelled{ErrorModule::Kernel, 118};
+constexpr ResultCode ResultOutOfRange{ErrorModule::Kernel, 119};
+constexpr ResultCode ResultInvalidEnumValue{ErrorModule::Kernel, 120};
+constexpr ResultCode ResultNotFound{ErrorModule::Kernel, 121};
+constexpr ResultCode ResultBusy{ErrorModule::Kernel, 122};
+constexpr ResultCode ResultSessionClosedByRemote{ErrorModule::Kernel, 123};
+constexpr ResultCode ResultInvalidState{ErrorModule::Kernel, 125};
+constexpr ResultCode ResultReservedValue{ErrorModule::Kernel, 126};
+constexpr ResultCode ResultResourceLimitedExceeded{ErrorModule::Kernel, 132};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h
index 986724beb..ec463b97c 100644
--- a/src/core/hle/kernel/svc_types.h
+++ b/src/core/hle/kernel/svc_types.h
@@ -23,8 +23,8 @@ enum class MemoryState : u32 {
Ipc = 0x0A,
Stack = 0x0B,
ThreadLocal = 0x0C,
- Transfered = 0x0D,
- SharedTransfered = 0x0E,
+ Transferred = 0x0D,
+ SharedTransferred = 0x0E,
SharedCode = 0x0F,
Inaccessible = 0x10,
NonSecureIpc = 0x11,
@@ -65,4 +65,34 @@ struct MemoryInfo {
u32 padding{};
};
+enum class SignalType : u32 {
+ Signal = 0,
+ SignalAndIncrementIfEqual = 1,
+ SignalAndModifyByWaitingCountIfEqual = 2,
+};
+
+enum class ArbitrationType : u32 {
+ WaitIfLessThan = 0,
+ DecrementAndWaitIfLessThan = 1,
+ WaitIfEqual = 2,
+};
+
+enum class YieldType : s64 {
+ WithoutCoreMigration = 0,
+ WithCoreMigration = -1,
+ ToAnyThread = -2,
+};
+
+enum class ThreadActivity : u32 {
+ Runnable = 0,
+ Paused = 1,
+};
+
+constexpr inline s32 IdealCoreDontCare = -1;
+constexpr inline s32 IdealCoreUseProcessValue = -2;
+constexpr inline s32 IdealCoreNoUpdate = -3;
+
+constexpr inline s32 LowestThreadPriority = 63;
+constexpr inline s32 HighestThreadPriority = 0;
+
} // namespace Kernel::Svc
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 0b6dd9df0..96afd544b 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -7,6 +7,7 @@
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
+#include "core/hle/kernel/svc_types.h"
#include "core/hle/result.h"
namespace Kernel {
@@ -57,6 +58,14 @@ void SvcWrap64(Core::System& system) {
func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1))).raw);
}
+// Used by SetThreadActivity
+template <ResultCode func(Core::System&, Handle, Svc::ThreadActivity)>
+void SvcWrap64(Core::System& system) {
+ FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)),
+ static_cast<Svc::ThreadActivity>(Param(system, 1)))
+ .raw);
+}
+
template <ResultCode func(Core::System&, u32, u64, u64, u64)>
void SvcWrap64(Core::System& system) {
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)), Param(system, 1),
@@ -157,9 +166,18 @@ void SvcWrap64(Core::System& system) {
.raw);
}
-template <ResultCode func(Core::System&, u32, u32*, u64*)>
+// Used by SetThreadCoreMask
+template <ResultCode func(Core::System&, Handle, s32, u64)>
void SvcWrap64(Core::System& system) {
- u32 param_1 = 0;
+ FuncReturn(system, func(system, static_cast<u32>(Param(system, 0)),
+ static_cast<s32>(Param(system, 1)), Param(system, 2))
+ .raw);
+}
+
+// Used by GetThreadCoreMask
+template <ResultCode func(Core::System&, Handle, s32*, u64*)>
+void SvcWrap64(Core::System& system) {
+ s32 param_1 = 0;
u64 param_2 = 0;
const ResultCode retval = func(system, static_cast<u32>(Param(system, 2)), &param_1, &param_2);
@@ -215,9 +233,10 @@ void SvcWrap64(Core::System& system) {
func(system, static_cast<u32>(Param(system, 0)), Param(system, 1), Param(system, 2)).raw);
}
-template <ResultCode func(Core::System&, u32*, u64, u64, s64)>
+// Used by WaitSynchronization
+template <ResultCode func(Core::System&, s32*, u64, u64, s64)>
void SvcWrap64(Core::System& system) {
- u32 param_1 = 0;
+ s32 param_1 = 0;
const u32 retval = func(system, &param_1, Param(system, 1), static_cast<u32>(Param(system, 2)),
static_cast<s64>(Param(system, 3)))
.raw;
@@ -276,18 +295,22 @@ void SvcWrap64(Core::System& system) {
FuncReturn(system, retval);
}
-template <ResultCode func(Core::System&, u64, u32, s32, s64)>
+// Used by WaitForAddress
+template <ResultCode func(Core::System&, u64, Svc::ArbitrationType, s32, s64)>
void SvcWrap64(Core::System& system) {
- FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1)),
- static_cast<s32>(Param(system, 2)), static_cast<s64>(Param(system, 3)))
- .raw);
+ FuncReturn(system,
+ func(system, Param(system, 0), static_cast<Svc::ArbitrationType>(Param(system, 1)),
+ static_cast<s32>(Param(system, 2)), static_cast<s64>(Param(system, 3)))
+ .raw);
}
-template <ResultCode func(Core::System&, u64, u32, s32, s32)>
+// Used by SignalToAddress
+template <ResultCode func(Core::System&, u64, Svc::SignalType, s32, s32)>
void SvcWrap64(Core::System& system) {
- FuncReturn(system, func(system, Param(system, 0), static_cast<u32>(Param(system, 1)),
- static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3)))
- .raw);
+ FuncReturn(system,
+ func(system, Param(system, 0), static_cast<Svc::SignalType>(Param(system, 1)),
+ static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3)))
+ .raw);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -467,12 +490,35 @@ void SvcWrap32(Core::System& system) {
FuncReturn(system, retval);
}
+// Used by GetThreadCoreMask32
+template <ResultCode func(Core::System&, Handle, s32*, u32*, u32*)>
+void SvcWrap32(Core::System& system) {
+ s32 param_1 = 0;
+ u32 param_2 = 0;
+ u32 param_3 = 0;
+
+ const u32 retval = func(system, Param32(system, 2), &param_1, &param_2, &param_3).raw;
+ system.CurrentArmInterface().SetReg(1, param_1);
+ system.CurrentArmInterface().SetReg(2, param_2);
+ system.CurrentArmInterface().SetReg(3, param_3);
+ FuncReturn(system, retval);
+}
+
// Used by SignalProcessWideKey32
template <void func(Core::System&, u32, s32)>
void SvcWrap32(Core::System& system) {
func(system, static_cast<u32>(Param(system, 0)), static_cast<s32>(Param(system, 1)));
}
+// Used by SetThreadActivity32
+template <ResultCode func(Core::System&, Handle, Svc::ThreadActivity)>
+void SvcWrap32(Core::System& system) {
+ const u32 retval = func(system, static_cast<Handle>(Param(system, 0)),
+ static_cast<Svc::ThreadActivity>(Param(system, 1)))
+ .raw;
+ FuncReturn(system, retval);
+}
+
// Used by SetThreadPriority32
template <ResultCode func(Core::System&, Handle, u32)>
void SvcWrap32(Core::System& system) {
@@ -481,7 +527,7 @@ void SvcWrap32(Core::System& system) {
FuncReturn(system, retval);
}
-// Used by SetThreadCoreMask32
+// Used by SetMemoryAttribute32
template <ResultCode func(Core::System&, Handle, u32, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval =
@@ -491,6 +537,16 @@ void SvcWrap32(Core::System& system) {
FuncReturn(system, retval);
}
+// Used by SetThreadCoreMask32
+template <ResultCode func(Core::System&, Handle, s32, u32, u32)>
+void SvcWrap32(Core::System& system) {
+ const u32 retval =
+ func(system, static_cast<Handle>(Param(system, 0)), static_cast<s32>(Param(system, 1)),
+ static_cast<u32>(Param(system, 2)), static_cast<u32>(Param(system, 3)))
+ .raw;
+ FuncReturn(system, retval);
+}
+
// Used by WaitProcessWideKeyAtomic32
template <ResultCode func(Core::System&, u32, u32, Handle, u32, u32)>
void SvcWrap32(Core::System& system) {
@@ -503,22 +559,23 @@ void SvcWrap32(Core::System& system) {
}
// Used by WaitForAddress32
-template <ResultCode func(Core::System&, u32, u32, s32, u32, u32)>
+template <ResultCode func(Core::System&, u32, Svc::ArbitrationType, s32, u32, u32)>
void SvcWrap32(Core::System& system) {
const u32 retval = func(system, static_cast<u32>(Param(system, 0)),
- static_cast<u32>(Param(system, 1)), static_cast<s32>(Param(system, 2)),
- static_cast<u32>(Param(system, 3)), static_cast<u32>(Param(system, 4)))
+ static_cast<Svc::ArbitrationType>(Param(system, 1)),
+ static_cast<s32>(Param(system, 2)), static_cast<u32>(Param(system, 3)),
+ static_cast<u32>(Param(system, 4)))
.raw;
FuncReturn(system, retval);
}
// Used by SignalToAddress32
-template <ResultCode func(Core::System&, u32, u32, s32, s32)>
+template <ResultCode func(Core::System&, u32, Svc::SignalType, s32, s32)>
void SvcWrap32(Core::System& system) {
- const u32 retval =
- func(system, static_cast<u32>(Param(system, 0)), static_cast<u32>(Param(system, 1)),
- static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3)))
- .raw;
+ const u32 retval = func(system, static_cast<u32>(Param(system, 0)),
+ static_cast<Svc::SignalType>(Param(system, 1)),
+ static_cast<s32>(Param(system, 2)), static_cast<s32>(Param(system, 3)))
+ .raw;
FuncReturn(system, retval);
}
@@ -539,9 +596,9 @@ void SvcWrap32(Core::System& system) {
}
// Used by WaitSynchronization32
-template <ResultCode func(Core::System&, u32, u32, s32, u32, Handle*)>
+template <ResultCode func(Core::System&, u32, u32, s32, u32, s32*)>
void SvcWrap32(Core::System& system) {
- u32 param_1 = 0;
+ s32 param_1 = 0;
const u32 retval = func(system, Param32(system, 0), Param32(system, 1), Param32(system, 2),
Param32(system, 3), &param_1)
.raw;
diff --git a/src/core/hle/kernel/synchronization.cpp b/src/core/hle/kernel/synchronization.cpp
deleted file mode 100644
index 8b875d853..000000000
--- a/src/core/hle/kernel/synchronization.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "core/core.h"
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/synchronization.h"
-#include "core/hle/kernel/synchronization_object.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/time_manager.h"
-
-namespace Kernel {
-
-Synchronization::Synchronization(Core::System& system) : system{system} {}
-
-void Synchronization::SignalObject(SynchronizationObject& obj) const {
- auto& kernel = system.Kernel();
- SchedulerLock lock(kernel);
- if (obj.IsSignaled()) {
- for (auto thread : obj.GetWaitingThreads()) {
- if (thread->GetSchedulingStatus() == ThreadSchedStatus::Paused) {
- if (thread->GetStatus() != ThreadStatus::WaitHLEEvent) {
- ASSERT(thread->GetStatus() == ThreadStatus::WaitSynch);
- ASSERT(thread->IsWaitingSync());
- }
- thread->SetSynchronizationResults(&obj, RESULT_SUCCESS);
- thread->ResumeFromWait();
- }
- }
- obj.ClearWaitingThreads();
- }
-}
-
-std::pair<ResultCode, Handle> Synchronization::WaitFor(
- std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) {
- auto& kernel = system.Kernel();
- auto* const thread = system.CurrentScheduler().GetCurrentThread();
- Handle event_handle = InvalidHandle;
- {
- SchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds);
- const auto itr =
- std::find_if(sync_objects.begin(), sync_objects.end(),
- [thread](const std::shared_ptr<SynchronizationObject>& object) {
- return object->IsSignaled();
- });
-
- if (itr != sync_objects.end()) {
- // We found a ready object, acquire it and set the result value
- SynchronizationObject* object = itr->get();
- object->Acquire(thread);
- const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
- lock.CancelSleep();
- return {RESULT_SUCCESS, index};
- }
-
- if (nano_seconds == 0) {
- lock.CancelSleep();
- return {RESULT_TIMEOUT, InvalidHandle};
- }
-
- if (thread->IsPendingTermination()) {
- lock.CancelSleep();
- return {ERR_THREAD_TERMINATING, InvalidHandle};
- }
-
- if (thread->IsSyncCancelled()) {
- thread->SetSyncCancelled(false);
- lock.CancelSleep();
- return {ERR_SYNCHRONIZATION_CANCELED, InvalidHandle};
- }
-
- for (auto& object : sync_objects) {
- object->AddWaitingThread(SharedFrom(thread));
- }
-
- thread->SetSynchronizationObjects(&sync_objects);
- thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
- thread->SetStatus(ThreadStatus::WaitSynch);
- thread->SetWaitingSync(true);
- }
- thread->SetWaitingSync(false);
-
- if (event_handle != InvalidHandle) {
- auto& time_manager = kernel.TimeManager();
- time_manager.UnscheduleTimeEvent(event_handle);
- }
-
- {
- SchedulerLock lock(kernel);
- ResultCode signaling_result = thread->GetSignalingResult();
- SynchronizationObject* signaling_object = thread->GetSignalingObject();
- thread->SetSynchronizationObjects(nullptr);
- auto shared_thread = SharedFrom(thread);
- for (auto& obj : sync_objects) {
- obj->RemoveWaitingThread(shared_thread);
- }
- if (signaling_object != nullptr) {
- const auto itr = std::find_if(
- sync_objects.begin(), sync_objects.end(),
- [signaling_object](const std::shared_ptr<SynchronizationObject>& object) {
- return object.get() == signaling_object;
- });
- ASSERT(itr != sync_objects.end());
- signaling_object->Acquire(thread);
- const u32 index = static_cast<s32>(std::distance(sync_objects.begin(), itr));
- return {signaling_result, index};
- }
- return {signaling_result, -1};
- }
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/synchronization.h b/src/core/hle/kernel/synchronization.h
deleted file mode 100644
index 379f4b1d3..000000000
--- a/src/core/hle/kernel/synchronization.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "core/hle/kernel/object.h"
-#include "core/hle/result.h"
-
-namespace Core {
-class System;
-} // namespace Core
-
-namespace Kernel {
-
-class SynchronizationObject;
-
-/**
- * The 'Synchronization' class is an interface for handling synchronization methods
- * used by Synchronization objects and synchronization SVCs. This centralizes processing of
- * such
- */
-class Synchronization {
-public:
- explicit Synchronization(Core::System& system);
-
- /// Signals a synchronization object, waking up all its waiting threads
- void SignalObject(SynchronizationObject& obj) const;
-
- /// Tries to see if waiting for any of the sync_objects is necessary, if not
- /// it returns Success and the handle index of the signaled sync object. In
- /// case not, the current thread will be locked and wait for nano_seconds or
- /// for a synchronization object to signal.
- std::pair<ResultCode, Handle> WaitFor(
- std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds);
-
-private:
- Core::System& system;
-};
-} // namespace Kernel
diff --git a/src/core/hle/kernel/synchronization_object.cpp b/src/core/hle/kernel/synchronization_object.cpp
deleted file mode 100644
index ba4d39157..000000000
--- a/src/core/hle/kernel/synchronization_object.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/synchronization.h"
-#include "core/hle/kernel/synchronization_object.h"
-#include "core/hle/kernel/thread.h"
-
-namespace Kernel {
-
-SynchronizationObject::SynchronizationObject(KernelCore& kernel) : Object{kernel} {}
-SynchronizationObject::~SynchronizationObject() = default;
-
-void SynchronizationObject::Signal() {
- kernel.Synchronization().SignalObject(*this);
-}
-
-void SynchronizationObject::AddWaitingThread(std::shared_ptr<Thread> thread) {
- auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
- if (itr == waiting_threads.end())
- waiting_threads.push_back(std::move(thread));
-}
-
-void SynchronizationObject::RemoveWaitingThread(std::shared_ptr<Thread> thread) {
- auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
- // If a thread passed multiple handles to the same object,
- // the kernel might attempt to remove the thread from the object's
- // waiting threads list multiple times.
- if (itr != waiting_threads.end())
- waiting_threads.erase(itr);
-}
-
-void SynchronizationObject::ClearWaitingThreads() {
- waiting_threads.clear();
-}
-
-const std::vector<std::shared_ptr<Thread>>& SynchronizationObject::GetWaitingThreads() const {
- return waiting_threads;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/synchronization_object.h b/src/core/hle/kernel/synchronization_object.h
deleted file mode 100644
index f89b24204..000000000
--- a/src/core/hle/kernel/synchronization_object.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "core/hle/kernel/object.h"
-
-namespace Kernel {
-
-class KernelCore;
-class Synchronization;
-class Thread;
-
-/// Class that represents a Kernel object that a thread can be waiting on
-class SynchronizationObject : public Object {
-public:
- explicit SynchronizationObject(KernelCore& kernel);
- ~SynchronizationObject() override;
-
- /**
- * Check if the specified thread should wait until the object is available
- * @param thread The thread about which we're deciding.
- * @return True if the current thread should wait due to this object being unavailable
- */
- virtual bool ShouldWait(const Thread* thread) const = 0;
-
- /// Acquire/lock the object for the specified thread if it is available
- virtual void Acquire(Thread* thread) = 0;
-
- /// Signal this object
- virtual void Signal();
-
- virtual bool IsSignaled() const {
- return is_signaled;
- }
-
- /**
- * Add a thread to wait on this object
- * @param thread Pointer to thread to add
- */
- void AddWaitingThread(std::shared_ptr<Thread> thread);
-
- /**
- * Removes a thread from waiting on this object (e.g. if it was resumed already)
- * @param thread Pointer to thread to remove
- */
- void RemoveWaitingThread(std::shared_ptr<Thread> thread);
-
- /// Get a const reference to the waiting threads list for debug use
- const std::vector<std::shared_ptr<Thread>>& GetWaitingThreads() const;
-
- void ClearWaitingThreads();
-
-protected:
- bool is_signaled{}; // Tells if this sync object is signalled;
-
-private:
- /// Threads waiting for this object to become available
- std::vector<std::shared_ptr<Thread>> waiting_threads;
-};
-
-// Specialization of DynamicObjectCast for SynchronizationObjects
-template <>
-inline std::shared_ptr<SynchronizationObject> DynamicObjectCast<SynchronizationObject>(
- std::shared_ptr<Object> object) {
- if (object != nullptr && object->IsWaitable()) {
- return std::static_pointer_cast<SynchronizationObject>(object);
- }
- return nullptr;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
deleted file mode 100644
index d132aba34..000000000
--- a/src/core/hle/kernel/thread.cpp
+++ /dev/null
@@ -1,540 +0,0 @@
-// Copyright 2014 Citra Emulator Project / PPSSPP Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cinttypes>
-#include <optional>
-#include <vector>
-
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "common/fiber.h"
-#include "common/logging/log.h"
-#include "common/thread_queue_list.h"
-#include "core/arm/arm_interface.h"
-#include "core/arm/unicorn/arm_unicorn.h"
-#include "core/core.h"
-#include "core/cpu_manager.h"
-#include "core/hardware_properties.h"
-#include "core/hle/kernel/errors.h"
-#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/time_manager.h"
-#include "core/hle/result.h"
-#include "core/memory.h"
-
-#ifdef ARCHITECTURE_x86_64
-#include "core/arm/dynarmic/arm_dynarmic_32.h"
-#include "core/arm/dynarmic/arm_dynarmic_64.h"
-#endif
-
-namespace Kernel {
-
-bool Thread::ShouldWait(const Thread* thread) const {
- return status != ThreadStatus::Dead;
-}
-
-bool Thread::IsSignaled() const {
- return status == ThreadStatus::Dead;
-}
-
-void Thread::Acquire(Thread* thread) {
- ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
-}
-
-Thread::Thread(KernelCore& kernel) : SynchronizationObject{kernel} {}
-Thread::~Thread() = default;
-
-void Thread::Stop() {
- {
- SchedulerLock lock(kernel);
- SetStatus(ThreadStatus::Dead);
- Signal();
- kernel.GlobalHandleTable().Close(global_handle);
-
- if (owner_process) {
- owner_process->UnregisterThread(this);
-
- // Mark the TLS slot in the thread's page as free.
- owner_process->FreeTLSRegion(tls_address);
- }
- arm_interface.reset();
- has_exited = true;
- }
- global_handle = 0;
-}
-
-void Thread::ResumeFromWait() {
- SchedulerLock lock(kernel);
- switch (status) {
- case ThreadStatus::Paused:
- case ThreadStatus::WaitSynch:
- case ThreadStatus::WaitHLEEvent:
- case ThreadStatus::WaitSleep:
- case ThreadStatus::WaitIPC:
- case ThreadStatus::WaitMutex:
- case ThreadStatus::WaitCondVar:
- case ThreadStatus::WaitArb:
- case ThreadStatus::Dormant:
- break;
-
- case ThreadStatus::Ready:
- // The thread's wakeup callback must have already been cleared when the thread was first
- // awoken.
- ASSERT(hle_callback == nullptr);
- // If the thread is waiting on multiple wait objects, it might be awoken more than once
- // before actually resuming. We can ignore subsequent wakeups if the thread status has
- // already been set to ThreadStatus::Ready.
- return;
-
- case ThreadStatus::Running:
- DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId());
- return;
- case ThreadStatus::Dead:
- // This should never happen, as threads must complete before being stopped.
- DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.",
- GetObjectId());
- return;
- }
-
- SetStatus(ThreadStatus::Ready);
-}
-
-void Thread::OnWakeUp() {
- SchedulerLock lock(kernel);
-
- SetStatus(ThreadStatus::Ready);
-}
-
-ResultCode Thread::Start() {
- SchedulerLock lock(kernel);
- SetStatus(ThreadStatus::Ready);
- return RESULT_SUCCESS;
-}
-
-void Thread::CancelWait() {
- SchedulerLock lock(kernel);
- if (GetSchedulingStatus() != ThreadSchedStatus::Paused || !is_waiting_on_sync) {
- is_sync_cancelled = true;
- return;
- }
- // TODO(Blinkhawk): Implement cancel of server session
- is_sync_cancelled = false;
- SetSynchronizationResults(nullptr, ERR_SYNCHRONIZATION_CANCELED);
- SetStatus(ThreadStatus::Ready);
-}
-
-static void ResetThreadContext32(Core::ARM_Interface::ThreadContext32& context, u32 stack_top,
- u32 entry_point, u32 arg) {
- context = {};
- context.cpu_registers[0] = arg;
- context.cpu_registers[15] = entry_point;
- context.cpu_registers[13] = stack_top;
-}
-
-static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context, VAddr stack_top,
- VAddr entry_point, u64 arg) {
- context = {};
- context.cpu_registers[0] = arg;
- context.pc = entry_point;
- context.sp = stack_top;
- // TODO(merry): Perform a hardware test to determine the below value.
- context.fpcr = 0;
-}
-
-std::shared_ptr<Common::Fiber>& Thread::GetHostContext() {
- return host_context;
-}
-
-ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadType type_flags,
- std::string name, VAddr entry_point, u32 priority,
- u64 arg, s32 processor_id, VAddr stack_top,
- Process* owner_process) {
- std::function<void(void*)> init_func = Core::CpuManager::GetGuestThreadStartFunc();
- void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
- return Create(system, type_flags, name, entry_point, priority, arg, processor_id, stack_top,
- owner_process, std::move(init_func), init_func_parameter);
-}
-
-ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadType type_flags,
- std::string name, VAddr entry_point, u32 priority,
- u64 arg, s32 processor_id, VAddr stack_top,
- Process* owner_process,
- std::function<void(void*)>&& thread_start_func,
- void* thread_start_parameter) {
- auto& kernel = system.Kernel();
- // Check if priority is in ranged. Lowest priority -> highest priority id.
- if (priority > THREADPRIO_LOWEST && ((type_flags & THREADTYPE_IDLE) == 0)) {
- LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
- return ERR_INVALID_THREAD_PRIORITY;
- }
-
- if (processor_id > THREADPROCESSORID_MAX) {
- LOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id);
- return ERR_INVALID_PROCESSOR_ID;
- }
-
- if (owner_process) {
- if (!system.Memory().IsValidVirtualAddress(*owner_process, entry_point)) {
- LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
- // TODO (bunnei): Find the correct error code to use here
- return RESULT_UNKNOWN;
- }
- }
-
- std::shared_ptr<Thread> thread = std::make_shared<Thread>(kernel);
-
- thread->thread_id = kernel.CreateNewThreadID();
- thread->status = ThreadStatus::Dormant;
- thread->entry_point = entry_point;
- thread->stack_top = stack_top;
- thread->tpidr_el0 = 0;
- thread->nominal_priority = thread->current_priority = priority;
- thread->last_running_ticks = 0;
- thread->processor_id = processor_id;
- thread->ideal_core = processor_id;
- thread->affinity_mask = 1ULL << processor_id;
- thread->wait_objects = nullptr;
- thread->mutex_wait_address = 0;
- thread->condvar_wait_address = 0;
- thread->wait_handle = 0;
- thread->name = std::move(name);
- thread->global_handle = kernel.GlobalHandleTable().Create(thread).Unwrap();
- thread->owner_process = owner_process;
- thread->type = type_flags;
- if ((type_flags & THREADTYPE_IDLE) == 0) {
- auto& scheduler = kernel.GlobalScheduler();
- scheduler.AddThread(thread);
- }
- if (owner_process) {
- thread->tls_address = thread->owner_process->CreateTLSRegion();
- thread->owner_process->RegisterThread(thread.get());
- } else {
- thread->tls_address = 0;
- }
- // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
- // to initialize the context
- thread->arm_interface.reset();
- if ((type_flags & THREADTYPE_HLE) == 0) {
-#ifdef ARCHITECTURE_x86_64
- if (owner_process && !owner_process->Is64BitProcess()) {
- thread->arm_interface = std::make_unique<Core::ARM_Dynarmic_32>(
- system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(),
- processor_id);
- } else {
- thread->arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
- system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(),
- processor_id);
- }
-
-#else
- if (owner_process && !owner_process->Is64BitProcess()) {
- thread->arm_interface = std::make_shared<Core::ARM_Unicorn>(
- system, kernel.Interrupts(), kernel.IsMulticore(), ARM_Unicorn::Arch::AArch32,
- processor_id);
- } else {
- thread->arm_interface = std::make_shared<Core::ARM_Unicorn>(
- system, kernel.Interrupts(), kernel.IsMulticore(), ARM_Unicorn::Arch::AArch64,
- processor_id);
- }
- LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
-#endif
- ResetThreadContext32(thread->context_32, static_cast<u32>(stack_top),
- static_cast<u32>(entry_point), static_cast<u32>(arg));
- ResetThreadContext64(thread->context_64, stack_top, entry_point, arg);
- }
- thread->host_context =
- std::make_shared<Common::Fiber>(std::move(thread_start_func), thread_start_parameter);
-
- return MakeResult<std::shared_ptr<Thread>>(std::move(thread));
-}
-
-void Thread::SetPriority(u32 priority) {
- SchedulerLock lock(kernel);
- ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
- "Invalid priority value.");
- nominal_priority = priority;
- UpdatePriority();
-}
-
-void Thread::SetSynchronizationResults(SynchronizationObject* object, ResultCode result) {
- signaling_object = object;
- signaling_result = result;
-}
-
-s32 Thread::GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const {
- ASSERT_MSG(!wait_objects->empty(), "Thread is not waiting for anything");
- const auto match = std::find(wait_objects->rbegin(), wait_objects->rend(), object);
- return static_cast<s32>(std::distance(match, wait_objects->rend()) - 1);
-}
-
-VAddr Thread::GetCommandBufferAddress() const {
- // Offset from the start of TLS at which the IPC command buffer begins.
- constexpr u64 command_header_offset = 0x80;
- return GetTLSAddress() + command_header_offset;
-}
-
-Core::ARM_Interface& Thread::ArmInterface() {
- return *arm_interface;
-}
-
-const Core::ARM_Interface& Thread::ArmInterface() const {
- return *arm_interface;
-}
-
-void Thread::SetStatus(ThreadStatus new_status) {
- if (new_status == status) {
- return;
- }
-
- switch (new_status) {
- case ThreadStatus::Ready:
- case ThreadStatus::Running:
- SetSchedulingStatus(ThreadSchedStatus::Runnable);
- break;
- case ThreadStatus::Dormant:
- SetSchedulingStatus(ThreadSchedStatus::None);
- break;
- case ThreadStatus::Dead:
- SetSchedulingStatus(ThreadSchedStatus::Exited);
- break;
- default:
- SetSchedulingStatus(ThreadSchedStatus::Paused);
- break;
- }
-
- status = new_status;
-}
-
-void Thread::AddMutexWaiter(std::shared_ptr<Thread> thread) {
- if (thread->lock_owner.get() == this) {
- // If the thread is already waiting for this thread to release the mutex, ensure that the
- // waiters list is consistent and return without doing anything.
- const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(iter != wait_mutex_threads.end());
- return;
- }
-
- // A thread can't wait on two different mutexes at the same time.
- ASSERT(thread->lock_owner == nullptr);
-
- // Ensure that the thread is not already in the list of mutex waiters
- const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(iter == wait_mutex_threads.end());
-
- // Keep the list in an ordered fashion
- const auto insertion_point = std::find_if(
- wait_mutex_threads.begin(), wait_mutex_threads.end(),
- [&thread](const auto& entry) { return entry->GetPriority() > thread->GetPriority(); });
- wait_mutex_threads.insert(insertion_point, thread);
- thread->lock_owner = SharedFrom(this);
-
- UpdatePriority();
-}
-
-void Thread::RemoveMutexWaiter(std::shared_ptr<Thread> thread) {
- ASSERT(thread->lock_owner.get() == this);
-
- // Ensure that the thread is in the list of mutex waiters
- const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(iter != wait_mutex_threads.end());
-
- wait_mutex_threads.erase(iter);
-
- thread->lock_owner = nullptr;
- UpdatePriority();
-}
-
-void Thread::UpdatePriority() {
- // If any of the threads waiting on the mutex have a higher priority
- // (taking into account priority inheritance), then this thread inherits
- // that thread's priority.
- u32 new_priority = nominal_priority;
- if (!wait_mutex_threads.empty()) {
- if (wait_mutex_threads.front()->current_priority < new_priority) {
- new_priority = wait_mutex_threads.front()->current_priority;
- }
- }
-
- if (new_priority == current_priority) {
- return;
- }
-
- if (GetStatus() == ThreadStatus::WaitCondVar) {
- owner_process->RemoveConditionVariableThread(SharedFrom(this));
- }
-
- SetCurrentPriority(new_priority);
-
- if (GetStatus() == ThreadStatus::WaitCondVar) {
- owner_process->InsertConditionVariableThread(SharedFrom(this));
- }
-
- if (!lock_owner) {
- return;
- }
-
- // Ensure that the thread is within the correct location in the waiting list.
- auto old_owner = lock_owner;
- lock_owner->RemoveMutexWaiter(SharedFrom(this));
- old_owner->AddMutexWaiter(SharedFrom(this));
-
- // Recursively update the priority of the thread that depends on the priority of this one.
- lock_owner->UpdatePriority();
-}
-
-bool Thread::AllSynchronizationObjectsReady() const {
- return std::none_of(wait_objects->begin(), wait_objects->end(),
- [this](const std::shared_ptr<SynchronizationObject>& object) {
- return object->ShouldWait(this);
- });
-}
-
-bool Thread::InvokeHLECallback(std::shared_ptr<Thread> thread) {
- ASSERT(hle_callback);
- return hle_callback(std::move(thread));
-}
-
-ResultCode Thread::SetActivity(ThreadActivity value) {
- SchedulerLock lock(kernel);
-
- auto sched_status = GetSchedulingStatus();
-
- if (sched_status != ThreadSchedStatus::Runnable && sched_status != ThreadSchedStatus::Paused) {
- return ERR_INVALID_STATE;
- }
-
- if (IsPendingTermination()) {
- return RESULT_SUCCESS;
- }
-
- if (value == ThreadActivity::Paused) {
- if ((pausing_state & static_cast<u32>(ThreadSchedFlags::ThreadPauseFlag)) != 0) {
- return ERR_INVALID_STATE;
- }
- AddSchedulingFlag(ThreadSchedFlags::ThreadPauseFlag);
- } else {
- if ((pausing_state & static_cast<u32>(ThreadSchedFlags::ThreadPauseFlag)) == 0) {
- return ERR_INVALID_STATE;
- }
- RemoveSchedulingFlag(ThreadSchedFlags::ThreadPauseFlag);
- }
- return RESULT_SUCCESS;
-}
-
-ResultCode Thread::Sleep(s64 nanoseconds) {
- Handle event_handle{};
- {
- SchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds);
- SetStatus(ThreadStatus::WaitSleep);
- }
-
- if (event_handle != InvalidHandle) {
- auto& time_manager = kernel.TimeManager();
- time_manager.UnscheduleTimeEvent(event_handle);
- }
- return RESULT_SUCCESS;
-}
-
-std::pair<ResultCode, bool> Thread::YieldSimple() {
- bool is_redundant = false;
- {
- SchedulerLock lock(kernel);
- is_redundant = kernel.GlobalScheduler().YieldThread(this);
- }
- return {RESULT_SUCCESS, is_redundant};
-}
-
-std::pair<ResultCode, bool> Thread::YieldAndBalanceLoad() {
- bool is_redundant = false;
- {
- SchedulerLock lock(kernel);
- is_redundant = kernel.GlobalScheduler().YieldThreadAndBalanceLoad(this);
- }
- return {RESULT_SUCCESS, is_redundant};
-}
-
-std::pair<ResultCode, bool> Thread::YieldAndWaitForLoadBalancing() {
- bool is_redundant = false;
- {
- SchedulerLock lock(kernel);
- is_redundant = kernel.GlobalScheduler().YieldThreadAndWaitForLoadBalancing(this);
- }
- return {RESULT_SUCCESS, is_redundant};
-}
-
-void Thread::AddSchedulingFlag(ThreadSchedFlags flag) {
- const u32 old_state = scheduling_state;
- pausing_state |= static_cast<u32>(flag);
- const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
- scheduling_state = base_scheduling | pausing_state;
- kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
-}
-
-void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) {
- const u32 old_state = scheduling_state;
- pausing_state &= ~static_cast<u32>(flag);
- const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
- scheduling_state = base_scheduling | pausing_state;
- kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
-}
-
-void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) {
- const u32 old_state = scheduling_state;
- scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) |
- static_cast<u32>(new_status);
- kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
-}
-
-void Thread::SetCurrentPriority(u32 new_priority) {
- const u32 old_priority = std::exchange(current_priority, new_priority);
- kernel.GlobalScheduler().AdjustSchedulingOnPriority(this, old_priority);
-}
-
-ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
- SchedulerLock lock(kernel);
- const auto HighestSetCore = [](u64 mask, u32 max_cores) {
- for (s32 core = static_cast<s32>(max_cores - 1); core >= 0; core--) {
- if (((mask >> core) & 1) != 0) {
- return core;
- }
- }
- return -1;
- };
-
- const bool use_override = affinity_override_count != 0;
- if (new_core == THREADPROCESSORID_DONT_UPDATE) {
- new_core = use_override ? ideal_core_override : ideal_core;
- if ((new_affinity_mask & (1ULL << new_core)) == 0) {
- LOG_ERROR(Kernel, "New affinity mask is incorrect! new_core={}, new_affinity_mask={}",
- new_core, new_affinity_mask);
- return ERR_INVALID_COMBINATION;
- }
- }
- if (use_override) {
- ideal_core_override = new_core;
- affinity_mask_override = new_affinity_mask;
- } else {
- const u64 old_affinity_mask = std::exchange(affinity_mask, new_affinity_mask);
- ideal_core = new_core;
- if (old_affinity_mask != new_affinity_mask) {
- const s32 old_core = processor_id;
- if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) {
- if (static_cast<s32>(ideal_core) < 0) {
- processor_id = HighestSetCore(affinity_mask, Core::Hardware::NUM_CPU_CORES);
- } else {
- processor_id = ideal_core;
- }
- }
- kernel.GlobalScheduler().AdjustSchedulingOnAffinity(this, old_affinity_mask, old_core);
- }
- }
- return RESULT_SUCCESS;
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
deleted file mode 100644
index 8daf79fac..000000000
--- a/src/core/hle/kernel/thread.h
+++ /dev/null
@@ -1,681 +0,0 @@
-// Copyright 2014 Citra Emulator Project / PPSSPP Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <functional>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "common/common_types.h"
-#include "common/spin_lock.h"
-#include "core/arm/arm_interface.h"
-#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/synchronization_object.h"
-#include "core/hle/result.h"
-
-namespace Common {
-class Fiber;
-}
-
-namespace Core {
-class ARM_Interface;
-class System;
-} // namespace Core
-
-namespace Kernel {
-
-class GlobalScheduler;
-class KernelCore;
-class Process;
-class Scheduler;
-
-enum ThreadPriority : u32 {
- THREADPRIO_HIGHEST = 0, ///< Highest thread priority
- THREADPRIO_MAX_CORE_MIGRATION = 2, ///< Highest priority for a core migration
- THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
- THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
- THREADPRIO_LOWEST = 63, ///< Lowest thread priority
- THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
-};
-
-enum ThreadType : u32 {
- THREADTYPE_USER = 0x1,
- THREADTYPE_KERNEL = 0x2,
- THREADTYPE_HLE = 0x4,
- THREADTYPE_IDLE = 0x8,
- THREADTYPE_SUSPEND = 0x10,
-};
-
-enum ThreadProcessorId : s32 {
- /// Indicates that no particular processor core is preferred.
- THREADPROCESSORID_DONT_CARE = -1,
-
- /// Run thread on the ideal core specified by the process.
- THREADPROCESSORID_IDEAL = -2,
-
- /// Indicates that the preferred processor ID shouldn't be updated in
- /// a core mask setting operation.
- THREADPROCESSORID_DONT_UPDATE = -3,
-
- THREADPROCESSORID_0 = 0, ///< Run thread on core 0
- THREADPROCESSORID_1 = 1, ///< Run thread on core 1
- THREADPROCESSORID_2 = 2, ///< Run thread on core 2
- THREADPROCESSORID_3 = 3, ///< Run thread on core 3
- THREADPROCESSORID_MAX = 4, ///< Processor ID must be less than this
-
- /// Allowed CPU mask
- THREADPROCESSORID_DEFAULT_MASK = (1 << THREADPROCESSORID_0) | (1 << THREADPROCESSORID_1) |
- (1 << THREADPROCESSORID_2) | (1 << THREADPROCESSORID_3)
-};
-
-enum class ThreadStatus {
- Running, ///< Currently running
- Ready, ///< Ready to run
- Paused, ///< Paused by SetThreadActivity or debug
- WaitHLEEvent, ///< Waiting for hle event to finish
- WaitSleep, ///< Waiting due to a SleepThread SVC
- WaitIPC, ///< Waiting for the reply from an IPC request
- WaitSynch, ///< Waiting due to WaitSynchronization
- WaitMutex, ///< Waiting due to an ArbitrateLock svc
- WaitCondVar, ///< Waiting due to an WaitProcessWideKey svc
- WaitArb, ///< Waiting due to a SignalToAddress/WaitForAddress svc
- Dormant, ///< Created but not yet made ready
- Dead ///< Run to completion, or forcefully terminated
-};
-
-enum class ThreadWakeupReason {
- Signal, // The thread was woken up by WakeupAllWaitingThreads due to an object signal.
- Timeout // The thread was woken up due to a wait timeout.
-};
-
-enum class ThreadActivity : u32 {
- Normal = 0,
- Paused = 1,
-};
-
-enum class ThreadSchedStatus : u32 {
- None = 0,
- Paused = 1,
- Runnable = 2,
- Exited = 3,
-};
-
-enum class ThreadSchedFlags : u32 {
- ProcessPauseFlag = 1 << 4,
- ThreadPauseFlag = 1 << 5,
- ProcessDebugPauseFlag = 1 << 6,
- KernelInitPauseFlag = 1 << 8,
-};
-
-enum class ThreadSchedMasks : u32 {
- LowMask = 0x000f,
- HighMask = 0xfff0,
- ForcePauseMask = 0x0070,
-};
-
-class Thread final : public SynchronizationObject {
-public:
- explicit Thread(KernelCore& kernel);
- ~Thread() override;
-
- using MutexWaitingThreads = std::vector<std::shared_ptr<Thread>>;
-
- using ThreadContext32 = Core::ARM_Interface::ThreadContext32;
- using ThreadContext64 = Core::ARM_Interface::ThreadContext64;
-
- using ThreadSynchronizationObjects = std::vector<std::shared_ptr<SynchronizationObject>>;
-
- using HLECallback = std::function<bool(std::shared_ptr<Thread> thread)>;
-
- /**
- * Creates and returns a new thread. The new thread is immediately scheduled
- * @param system The instance of the whole system
- * @param name The friendly name desired for the thread
- * @param entry_point The address at which the thread should start execution
- * @param priority The thread's priority
- * @param arg User data to pass to the thread
- * @param processor_id The ID(s) of the processors on which the thread is desired to be run
- * @param stack_top The address of the thread's stack top
- * @param owner_process The parent process for the thread, if null, it's a kernel thread
- * @return A shared pointer to the newly created thread
- */
- static ResultVal<std::shared_ptr<Thread>> Create(Core::System& system, ThreadType type_flags,
- std::string name, VAddr entry_point,
- u32 priority, u64 arg, s32 processor_id,
- VAddr stack_top, Process* owner_process);
-
- /**
- * Creates and returns a new thread. The new thread is immediately scheduled
- * @param system The instance of the whole system
- * @param name The friendly name desired for the thread
- * @param entry_point The address at which the thread should start execution
- * @param priority The thread's priority
- * @param arg User data to pass to the thread
- * @param processor_id The ID(s) of the processors on which the thread is desired to be run
- * @param stack_top The address of the thread's stack top
- * @param owner_process The parent process for the thread, if null, it's a kernel thread
- * @param thread_start_func The function where the host context will start.
- * @param thread_start_parameter The parameter which will passed to host context on init
- * @return A shared pointer to the newly created thread
- */
- static ResultVal<std::shared_ptr<Thread>> Create(Core::System& system, ThreadType type_flags,
- std::string name, VAddr entry_point,
- u32 priority, u64 arg, s32 processor_id,
- VAddr stack_top, Process* owner_process,
- std::function<void(void*)>&& thread_start_func,
- void* thread_start_parameter);
-
- std::string GetName() const override {
- return name;
- }
-
- void SetName(std::string new_name) {
- name = std::move(new_name);
- }
-
- std::string GetTypeName() const override {
- return "Thread";
- }
-
- static constexpr HandleType HANDLE_TYPE = HandleType::Thread;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
-
- bool ShouldWait(const Thread* thread) const override;
- void Acquire(Thread* thread) override;
- bool IsSignaled() const override;
-
- /**
- * Gets the thread's current priority
- * @return The current thread's priority
- */
- u32 GetPriority() const {
- return current_priority;
- }
-
- /**
- * Gets the thread's nominal priority.
- * @return The current thread's nominal priority.
- */
- u32 GetNominalPriority() const {
- return nominal_priority;
- }
-
- /**
- * Sets the thread's current priority
- * @param priority The new priority
- */
- void SetPriority(u32 priority);
-
- /// Adds a thread to the list of threads that are waiting for a lock held by this thread.
- void AddMutexWaiter(std::shared_ptr<Thread> thread);
-
- /// Removes a thread from the list of threads that are waiting for a lock held by this thread.
- void RemoveMutexWaiter(std::shared_ptr<Thread> thread);
-
- /// Recalculates the current priority taking into account priority inheritance.
- void UpdatePriority();
-
- /// Changes the core that the thread is running or scheduled to run on.
- ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask);
-
- /**
- * Gets the thread's thread ID
- * @return The thread's ID
- */
- u64 GetThreadID() const {
- return thread_id;
- }
-
- /// Resumes a thread from waiting
- void ResumeFromWait();
-
- void OnWakeUp();
-
- ResultCode Start();
-
- /// Cancels a waiting operation that this thread may or may not be within.
- ///
- /// When the thread is within a waiting state, this will set the thread's
- /// waiting result to signal a canceled wait. The function will then resume
- /// this thread.
- ///
- void CancelWait();
-
- void SetSynchronizationResults(SynchronizationObject* object, ResultCode result);
-
- Core::ARM_Interface& ArmInterface();
-
- const Core::ARM_Interface& ArmInterface() const;
-
- SynchronizationObject* GetSignalingObject() const {
- return signaling_object;
- }
-
- ResultCode GetSignalingResult() const {
- return signaling_result;
- }
-
- /**
- * Retrieves the index that this particular object occupies in the list of objects
- * that the thread passed to WaitSynchronization, starting the search from the last element.
- *
- * It is used to set the output index of WaitSynchronization when the thread is awakened.
- *
- * When a thread wakes up due to an object signal, the kernel will use the index of the last
- * matching object in the wait objects list in case of having multiple instances of the same
- * object in the list.
- *
- * @param object Object to query the index of.
- */
- s32 GetSynchronizationObjectIndex(std::shared_ptr<SynchronizationObject> object) const;
-
- /**
- * Stops a thread, invalidating it from further use
- */
- void Stop();
-
- /*
- * Returns the Thread Local Storage address of the current thread
- * @returns VAddr of the thread's TLS
- */
- VAddr GetTLSAddress() const {
- return tls_address;
- }
-
- /*
- * Returns the value of the TPIDR_EL0 Read/Write system register for this thread.
- * @returns The value of the TPIDR_EL0 register.
- */
- u64 GetTPIDR_EL0() const {
- return tpidr_el0;
- }
-
- /// Sets the value of the TPIDR_EL0 Read/Write system register for this thread.
- void SetTPIDR_EL0(u64 value) {
- tpidr_el0 = value;
- }
-
- /*
- * Returns the address of the current thread's command buffer, located in the TLS.
- * @returns VAddr of the thread's command buffer.
- */
- VAddr GetCommandBufferAddress() const;
-
- ThreadContext32& GetContext32() {
- return context_32;
- }
-
- const ThreadContext32& GetContext32() const {
- return context_32;
- }
-
- ThreadContext64& GetContext64() {
- return context_64;
- }
-
- const ThreadContext64& GetContext64() const {
- return context_64;
- }
-
- bool IsHLEThread() const {
- return (type & THREADTYPE_HLE) != 0;
- }
-
- bool IsSuspendThread() const {
- return (type & THREADTYPE_SUSPEND) != 0;
- }
-
- bool IsIdleThread() const {
- return (type & THREADTYPE_IDLE) != 0;
- }
-
- bool WasRunning() const {
- return was_running;
- }
-
- void SetWasRunning(bool value) {
- was_running = value;
- }
-
- std::shared_ptr<Common::Fiber>& GetHostContext();
-
- ThreadStatus GetStatus() const {
- return status;
- }
-
- void SetStatus(ThreadStatus new_status);
-
- u64 GetLastRunningTicks() const {
- return last_running_ticks;
- }
-
- u64 GetTotalCPUTimeTicks() const {
- return total_cpu_time_ticks;
- }
-
- void UpdateCPUTimeTicks(u64 ticks) {
- total_cpu_time_ticks += ticks;
- }
-
- s32 GetProcessorID() const {
- return processor_id;
- }
-
- void SetProcessorID(s32 new_core) {
- processor_id = new_core;
- }
-
- Process* GetOwnerProcess() {
- return owner_process;
- }
-
- const Process* GetOwnerProcess() const {
- return owner_process;
- }
-
- const ThreadSynchronizationObjects& GetSynchronizationObjects() const {
- return *wait_objects;
- }
-
- void SetSynchronizationObjects(ThreadSynchronizationObjects* objects) {
- wait_objects = objects;
- }
-
- void ClearSynchronizationObjects() {
- for (const auto& waiting_object : *wait_objects) {
- waiting_object->RemoveWaitingThread(SharedFrom(this));
- }
- wait_objects->clear();
- }
-
- /// Determines whether all the objects this thread is waiting on are ready.
- bool AllSynchronizationObjectsReady() const;
-
- const MutexWaitingThreads& GetMutexWaitingThreads() const {
- return wait_mutex_threads;
- }
-
- Thread* GetLockOwner() const {
- return lock_owner.get();
- }
-
- void SetLockOwner(std::shared_ptr<Thread> owner) {
- lock_owner = std::move(owner);
- }
-
- VAddr GetCondVarWaitAddress() const {
- return condvar_wait_address;
- }
-
- void SetCondVarWaitAddress(VAddr address) {
- condvar_wait_address = address;
- }
-
- VAddr GetMutexWaitAddress() const {
- return mutex_wait_address;
- }
-
- void SetMutexWaitAddress(VAddr address) {
- mutex_wait_address = address;
- }
-
- Handle GetWaitHandle() const {
- return wait_handle;
- }
-
- void SetWaitHandle(Handle handle) {
- wait_handle = handle;
- }
-
- VAddr GetArbiterWaitAddress() const {
- return arb_wait_address;
- }
-
- void SetArbiterWaitAddress(VAddr address) {
- arb_wait_address = address;
- }
-
- bool HasHLECallback() const {
- return hle_callback != nullptr;
- }
-
- void SetHLECallback(HLECallback callback) {
- hle_callback = std::move(callback);
- }
-
- void SetHLETimeEvent(Handle time_event) {
- hle_time_event = time_event;
- }
-
- void SetHLESyncObject(SynchronizationObject* object) {
- hle_object = object;
- }
-
- Handle GetHLETimeEvent() const {
- return hle_time_event;
- }
-
- SynchronizationObject* GetHLESyncObject() const {
- return hle_object;
- }
-
- void InvalidateHLECallback() {
- SetHLECallback(nullptr);
- }
-
- bool InvokeHLECallback(std::shared_ptr<Thread> thread);
-
- u32 GetIdealCore() const {
- return ideal_core;
- }
-
- u64 GetAffinityMask() const {
- return affinity_mask;
- }
-
- ResultCode SetActivity(ThreadActivity value);
-
- /// Sleeps this thread for the given amount of nanoseconds.
- ResultCode Sleep(s64 nanoseconds);
-
- /// Yields this thread without rebalancing loads.
- std::pair<ResultCode, bool> YieldSimple();
-
- /// Yields this thread and does a load rebalancing.
- std::pair<ResultCode, bool> YieldAndBalanceLoad();
-
- /// Yields this thread and if the core is left idle, loads are rebalanced
- std::pair<ResultCode, bool> YieldAndWaitForLoadBalancing();
-
- void IncrementYieldCount() {
- yield_count++;
- }
-
- u64 GetYieldCount() const {
- return yield_count;
- }
-
- ThreadSchedStatus GetSchedulingStatus() const {
- return static_cast<ThreadSchedStatus>(scheduling_state &
- static_cast<u32>(ThreadSchedMasks::LowMask));
- }
-
- bool IsRunnable() const {
- return scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable);
- }
-
- bool IsRunning() const {
- return is_running;
- }
-
- void SetIsRunning(bool value) {
- is_running = value;
- }
-
- bool IsSyncCancelled() const {
- return is_sync_cancelled;
- }
-
- void SetSyncCancelled(bool value) {
- is_sync_cancelled = value;
- }
-
- Handle GetGlobalHandle() const {
- return global_handle;
- }
-
- bool IsWaitingForArbitration() const {
- return waiting_for_arbitration;
- }
-
- void WaitForArbitration(bool set) {
- waiting_for_arbitration = set;
- }
-
- bool IsWaitingSync() const {
- return is_waiting_on_sync;
- }
-
- void SetWaitingSync(bool is_waiting) {
- is_waiting_on_sync = is_waiting;
- }
-
- bool IsPendingTermination() const {
- return will_be_terminated || GetSchedulingStatus() == ThreadSchedStatus::Exited;
- }
-
- bool IsPaused() const {
- return pausing_state != 0;
- }
-
- bool IsContinuousOnSVC() const {
- return is_continuous_on_svc;
- }
-
- void SetContinuousOnSVC(bool is_continuous) {
- is_continuous_on_svc = is_continuous;
- }
-
- bool IsPhantomMode() const {
- return is_phantom_mode;
- }
-
- void SetPhantomMode(bool phantom) {
- is_phantom_mode = phantom;
- }
-
- bool HasExited() const {
- return has_exited;
- }
-
-private:
- friend class GlobalScheduler;
- friend class Scheduler;
-
- void SetSchedulingStatus(ThreadSchedStatus new_status);
- void AddSchedulingFlag(ThreadSchedFlags flag);
- void RemoveSchedulingFlag(ThreadSchedFlags flag);
-
- void SetCurrentPriority(u32 new_priority);
-
- Common::SpinLock context_guard{};
- ThreadContext32 context_32{};
- ThreadContext64 context_64{};
- std::unique_ptr<Core::ARM_Interface> arm_interface{};
- std::shared_ptr<Common::Fiber> host_context{};
-
- u64 thread_id = 0;
-
- ThreadStatus status = ThreadStatus::Dormant;
-
- VAddr entry_point = 0;
- VAddr stack_top = 0;
-
- ThreadType type;
-
- /// Nominal thread priority, as set by the emulated application.
- /// The nominal priority is the thread priority without priority
- /// inheritance taken into account.
- u32 nominal_priority = 0;
-
- /// Current thread priority. This may change over the course of the
- /// thread's lifetime in order to facilitate priority inheritance.
- u32 current_priority = 0;
-
- u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
- u64 last_running_ticks = 0; ///< CPU tick when thread was last running
- u64 yield_count = 0; ///< Number of redundant yields carried by this thread.
- ///< a redundant yield is one where no scheduling is changed
-
- s32 processor_id = 0;
-
- VAddr tls_address = 0; ///< Virtual address of the Thread Local Storage of the thread
- u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register.
-
- /// Process that owns this thread
- Process* owner_process;
-
- /// Objects that the thread is waiting on, in the same order as they were
- /// passed to WaitSynchronization.
- ThreadSynchronizationObjects* wait_objects;
-
- SynchronizationObject* signaling_object;
- ResultCode signaling_result{RESULT_SUCCESS};
-
- /// List of threads that are waiting for a mutex that is held by this thread.
- MutexWaitingThreads wait_mutex_threads;
-
- /// Thread that owns the lock that this thread is waiting for.
- std::shared_ptr<Thread> lock_owner;
-
- /// If waiting on a ConditionVariable, this is the ConditionVariable address
- VAddr condvar_wait_address = 0;
- /// If waiting on a Mutex, this is the mutex address
- VAddr mutex_wait_address = 0;
- /// The handle used to wait for the mutex.
- Handle wait_handle = 0;
-
- /// If waiting for an AddressArbiter, this is the address being waited on.
- VAddr arb_wait_address{0};
- bool waiting_for_arbitration{};
-
- /// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
- Handle global_handle = 0;
-
- /// Callback for HLE Events
- HLECallback hle_callback;
- Handle hle_time_event;
- SynchronizationObject* hle_object;
-
- Scheduler* scheduler = nullptr;
-
- u32 ideal_core{0xFFFFFFFF};
- u64 affinity_mask{0x1};
-
- s32 ideal_core_override = -1;
- u64 affinity_mask_override = 0x1;
- u32 affinity_override_count = 0;
-
- u32 scheduling_state = 0;
- u32 pausing_state = 0;
- bool is_running = false;
- bool is_waiting_on_sync = false;
- bool is_sync_cancelled = false;
-
- bool is_continuous_on_svc = false;
-
- bool will_be_terminated = false;
- bool is_phantom_mode = false;
- bool has_exited = false;
-
- bool was_running = false;
-
- std::string name;
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp
index 95f2446c9..fd0630019 100644
--- a/src/core/hle/kernel/time_manager.cpp
+++ b/src/core/hle/kernel/time_manager.cpp
@@ -7,9 +7,9 @@
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/time_manager.h"
namespace Kernel {
@@ -18,41 +18,30 @@ TimeManager::TimeManager(Core::System& system_) : system{system_} {
time_manager_event_type = Core::Timing::CreateEvent(
"Kernel::TimeManagerCallback",
[this](std::uintptr_t thread_handle, std::chrono::nanoseconds) {
- const SchedulerLock lock(system.Kernel());
- const auto proper_handle = static_cast<Handle>(thread_handle);
- if (cancelled_events[proper_handle]) {
- return;
+ std::shared_ptr<KThread> thread;
+ {
+ std::lock_guard lock{mutex};
+ thread = SharedFrom<KThread>(reinterpret_cast<KThread*>(thread_handle));
}
- auto thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
- thread->OnWakeUp();
+ thread->Wakeup();
});
}
-void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds) {
- event_handle = timetask->GetGlobalHandle();
+void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) {
+ std::lock_guard lock{mutex};
if (nanoseconds > 0) {
- ASSERT(timetask);
- ASSERT(timetask->GetStatus() != ThreadStatus::Ready);
- ASSERT(timetask->GetStatus() != ThreadStatus::WaitMutex);
+ ASSERT(thread);
+ ASSERT(thread->GetState() != ThreadState::Runnable);
system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{nanoseconds},
- time_manager_event_type, event_handle);
- } else {
- event_handle = InvalidHandle;
+ time_manager_event_type,
+ reinterpret_cast<uintptr_t>(thread));
}
- cancelled_events[event_handle] = false;
}
-void TimeManager::UnscheduleTimeEvent(Handle event_handle) {
- if (event_handle == InvalidHandle) {
- return;
- }
- system.CoreTiming().UnscheduleEvent(time_manager_event_type, event_handle);
- cancelled_events[event_handle] = true;
-}
-
-void TimeManager::CancelTimeEvent(Thread* time_task) {
- Handle event_handle = time_task->GetGlobalHandle();
- UnscheduleTimeEvent(event_handle);
+void TimeManager::UnscheduleTimeEvent(KThread* thread) {
+ std::lock_guard lock{mutex};
+ system.CoreTiming().UnscheduleEvent(time_manager_event_type,
+ reinterpret_cast<uintptr_t>(thread));
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/time_manager.h b/src/core/hle/kernel/time_manager.h
index 307a18765..0d7f05f30 100644
--- a/src/core/hle/kernel/time_manager.h
+++ b/src/core/hle/kernel/time_manager.h
@@ -5,6 +5,7 @@
#pragma once
#include <memory>
+#include <mutex>
#include <unordered_map>
#include "core/hle/kernel/object.h"
@@ -19,7 +20,7 @@ struct EventType;
namespace Kernel {
-class Thread;
+class KThread;
/**
* The `TimeManager` takes care of scheduling time events on threads and executes their TimeUp
@@ -30,18 +31,15 @@ public:
explicit TimeManager(Core::System& system);
/// Schedule a time event on `timetask` thread that will expire in 'nanoseconds'
- /// returns a non-invalid handle in `event_handle` if correctly scheduled
- void ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds);
+ void ScheduleTimeEvent(KThread* time_task, s64 nanoseconds);
/// Unschedule an existing time event
- void UnscheduleTimeEvent(Handle event_handle);
-
- void CancelTimeEvent(Thread* time_task);
+ void UnscheduleTimeEvent(KThread* thread);
private:
Core::System& system;
std::shared_ptr<Core::Timing::EventType> time_manager_event_type;
- std::unordered_map<Handle, bool> cancelled_events;
+ std::mutex mutex;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/transfer_memory.cpp b/src/core/hle/kernel/transfer_memory.cpp
index 765f408c3..6b0fc1591 100644
--- a/src/core/hle/kernel/transfer_memory.cpp
+++ b/src/core/hle/kernel/transfer_memory.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
@@ -17,6 +18,7 @@ TransferMemory::TransferMemory(KernelCore& kernel, Core::Memory::Memory& memory)
TransferMemory::~TransferMemory() {
// Release memory region when transfer memory is destroyed
Reset();
+ owner_process->GetResourceLimit()->Release(LimitableResource::TransferMemory, 1);
}
std::shared_ptr<TransferMemory> TransferMemory::Create(KernelCore& kernel,
diff --git a/src/core/hle/kernel/transfer_memory.h b/src/core/hle/kernel/transfer_memory.h
index 05e9f7464..777799d12 100644
--- a/src/core/hle/kernel/transfer_memory.h
+++ b/src/core/hle/kernel/transfer_memory.h
@@ -72,6 +72,8 @@ public:
/// is closed.
ResultCode Reset();
+ void Finalize() override {}
+
private:
/// The base address for the memory managed by this instance.
VAddr base_address{};
diff --git a/src/core/hle/kernel/writable_event.cpp b/src/core/hle/kernel/writable_event.cpp
deleted file mode 100644
index fc2f7c424..000000000
--- a/src/core/hle/kernel/writable_event.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include "common/assert.h"
-#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/writable_event.h"
-
-namespace Kernel {
-
-WritableEvent::WritableEvent(KernelCore& kernel) : Object{kernel} {}
-WritableEvent::~WritableEvent() = default;
-
-EventPair WritableEvent::CreateEventPair(KernelCore& kernel, std::string name) {
- std::shared_ptr<WritableEvent> writable_event(new WritableEvent(kernel));
- std::shared_ptr<ReadableEvent> readable_event(new ReadableEvent(kernel));
-
- writable_event->name = name + ":Writable";
- writable_event->readable = readable_event;
- readable_event->name = name + ":Readable";
-
- return {std::move(readable_event), std::move(writable_event)};
-}
-
-std::shared_ptr<ReadableEvent> WritableEvent::GetReadableEvent() const {
- return readable;
-}
-
-void WritableEvent::Signal() {
- readable->Signal();
-}
-
-void WritableEvent::Clear() {
- readable->Clear();
-}
-
-bool WritableEvent::IsSignaled() const {
- return readable->IsSignaled();
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/writable_event.h b/src/core/hle/kernel/writable_event.h
deleted file mode 100644
index 6189cf65c..000000000
--- a/src/core/hle/kernel/writable_event.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-
-#include "core/hle/kernel/object.h"
-
-namespace Kernel {
-
-class KernelCore;
-class ReadableEvent;
-class WritableEvent;
-
-struct EventPair {
- std::shared_ptr<ReadableEvent> readable;
- std::shared_ptr<WritableEvent> writable;
-};
-
-class WritableEvent final : public Object {
-public:
- ~WritableEvent() override;
-
- /**
- * Creates an event
- * @param kernel The kernel instance to create this event under.
- * @param name Optional name of event
- */
- static EventPair CreateEventPair(KernelCore& kernel, std::string name = "Unknown");
-
- std::string GetTypeName() const override {
- return "WritableEvent";
- }
- std::string GetName() const override {
- return name;
- }
-
- static constexpr HandleType HANDLE_TYPE = HandleType::WritableEvent;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
-
- std::shared_ptr<ReadableEvent> GetReadableEvent() const;
-
- void Signal();
- void Clear();
- bool IsSignaled() const;
-
-private:
- explicit WritableEvent(KernelCore& kernel);
-
- std::shared_ptr<ReadableEvent> readable;
-
- std::string name; ///< Name of event (optional)
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index b6bdbd988..8feda7ad7 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -119,7 +119,7 @@ union ResultCode {
BitField<0, 9, ErrorModule> module;
BitField<9, 13, u32> description;
- constexpr explicit ResultCode(u32 raw) : raw(raw) {}
+ constexpr explicit ResultCode(u32 raw_) : raw(raw_) {}
constexpr ResultCode(ErrorModule module_, u32 description_)
: raw(module.FormatValue(module_) | description.FormatValue(description_)) {}
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index eb54cb123..3ec0e1eca 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -11,6 +11,7 @@
#include "common/string_util.h"
#include "common/swap.h"
#include "core/constants.h"
+#include "core/core.h"
#include "core/core_timing.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -31,9 +32,15 @@
namespace Service::Account {
-constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30};
+constexpr ResultCode ERR_INVALID_USER_ID{ErrorModule::Account, 20};
+constexpr ResultCode ERR_INVALID_APPLICATION_ID{ErrorModule::Account, 22};
+constexpr ResultCode ERR_INVALID_BUFFER{ErrorModule::Account, 30};
+constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 31};
constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
+// Thumbnails are hard coded to be at least this size
+constexpr std::size_t THUMBNAIL_SIZE = 0x24000;
+
static std::string GetImagePath(Common::UUID uuid) {
return Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
@@ -46,8 +53,8 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) {
class IManagerForSystemService final : public ServiceFramework<IManagerForSystemService> {
public:
- explicit IManagerForSystemService(Common::UUID user_id)
- : ServiceFramework("IManagerForSystemService") {
+ explicit IManagerForSystemService(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IManagerForSystemService"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CheckAvailability"},
@@ -82,8 +89,8 @@ public:
// 3.0.0+
class IFloatingRegistrationRequest final : public ServiceFramework<IFloatingRegistrationRequest> {
public:
- explicit IFloatingRegistrationRequest(Common::UUID user_id)
- : ServiceFramework("IFloatingRegistrationRequest") {
+ explicit IFloatingRegistrationRequest(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IFloatingRegistrationRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
@@ -107,7 +114,8 @@ public:
class IAdministrator final : public ServiceFramework<IAdministrator> {
public:
- explicit IAdministrator(Common::UUID user_id) : ServiceFramework("IAdministrator") {
+ explicit IAdministrator(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IAdministrator"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CheckAvailability"},
@@ -164,8 +172,8 @@ public:
class IAuthorizationRequest final : public ServiceFramework<IAuthorizationRequest> {
public:
- explicit IAuthorizationRequest(Common::UUID user_id)
- : ServiceFramework("IAuthorizationRequest") {
+ explicit IAuthorizationRequest(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IAuthorizationRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
@@ -183,7 +191,8 @@ public:
class IOAuthProcedure final : public ServiceFramework<IOAuthProcedure> {
public:
- explicit IOAuthProcedure(Common::UUID user_id) : ServiceFramework("IOAuthProcedure") {
+ explicit IOAuthProcedure(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IOAuthProcedure"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
@@ -201,8 +210,8 @@ public:
// 3.0.0+
class IOAuthProcedureForExternalNsa final : public ServiceFramework<IOAuthProcedureForExternalNsa> {
public:
- explicit IOAuthProcedureForExternalNsa(Common::UUID user_id)
- : ServiceFramework("IOAuthProcedureForExternalNsa") {
+ explicit IOAuthProcedureForExternalNsa(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IOAuthProcedureForExternalNsa"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
@@ -224,8 +233,8 @@ public:
class IOAuthProcedureForNintendoAccountLinkage final
: public ServiceFramework<IOAuthProcedureForNintendoAccountLinkage> {
public:
- explicit IOAuthProcedureForNintendoAccountLinkage(Common::UUID user_id)
- : ServiceFramework("IOAuthProcedureForNintendoAccountLinkage") {
+ explicit IOAuthProcedureForNintendoAccountLinkage(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IOAuthProcedureForNintendoAccountLinkage"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
@@ -245,7 +254,8 @@ public:
class INotifier final : public ServiceFramework<INotifier> {
public:
- explicit INotifier(Common::UUID user_id) : ServiceFramework("INotifier") {
+ explicit INotifier(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "INotifier"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
@@ -258,9 +268,9 @@ public:
class IProfileCommon : public ServiceFramework<IProfileCommon> {
public:
- explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id,
- ProfileManager& profile_manager)
- : ServiceFramework(name), profile_manager(profile_manager), user_id(user_id) {
+ explicit IProfileCommon(Core::System& system_, const char* name, bool editor_commands,
+ Common::UUID user_id_, ProfileManager& profile_manager_)
+ : ServiceFramework{system_, name}, profile_manager{profile_manager_}, user_id{user_id_} {
static const FunctionInfo functions[] = {
{0, &IProfileCommon::Get, "Get"},
{1, &IProfileCommon::GetBase, "GetBase"},
@@ -365,7 +375,7 @@ protected:
if (user_data.size() < sizeof(ProfileData)) {
LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_INVALID_BUFFER_SIZE);
+ rb.Push(ERR_INVALID_BUFFER);
return;
}
@@ -398,7 +408,7 @@ protected:
if (user_data.size() < sizeof(ProfileData)) {
LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_INVALID_BUFFER_SIZE);
+ rb.Push(ERR_INVALID_BUFFER);
return;
}
@@ -426,19 +436,21 @@ protected:
class IProfile final : public IProfileCommon {
public:
- IProfile(Common::UUID user_id, ProfileManager& profile_manager)
- : IProfileCommon("IProfile", false, user_id, profile_manager) {}
+ explicit IProfile(Core::System& system_, Common::UUID user_id_,
+ ProfileManager& profile_manager_)
+ : IProfileCommon{system_, "IProfile", false, user_id_, profile_manager_} {}
};
class IProfileEditor final : public IProfileCommon {
public:
- IProfileEditor(Common::UUID user_id, ProfileManager& profile_manager)
- : IProfileCommon("IProfileEditor", true, user_id, profile_manager) {}
+ explicit IProfileEditor(Core::System& system_, Common::UUID user_id_,
+ ProfileManager& profile_manager_)
+ : IProfileCommon{system_, "IProfileEditor", true, user_id_, profile_manager_} {}
};
class IAsyncContext final : public ServiceFramework<IAsyncContext> {
public:
- explicit IAsyncContext(Common::UUID user_id) : ServiceFramework("IAsyncContext") {
+ explicit IAsyncContext(Core::System& system_) : ServiceFramework{system_, "IAsyncContext"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
@@ -454,7 +466,8 @@ public:
class ISessionObject final : public ServiceFramework<ISessionObject> {
public:
- explicit ISessionObject(Common::UUID user_id) : ServiceFramework("ISessionObject") {
+ explicit ISessionObject(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "ISessionObject"} {
// clang-format off
static const FunctionInfo functions[] = {
{999, nullptr, "Dummy"},
@@ -467,7 +480,8 @@ public:
class IGuestLoginRequest final : public ServiceFramework<IGuestLoginRequest> {
public:
- explicit IGuestLoginRequest(Common::UUID) : ServiceFramework("IGuestLoginRequest") {
+ explicit IGuestLoginRequest(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IGuestLoginRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSessionId"},
@@ -486,8 +500,8 @@ public:
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public:
- explicit IManagerForApplication(Common::UUID user_id)
- : ServiceFramework("IManagerForApplication"), user_id(user_id) {
+ explicit IManagerForApplication(Core::System& system_, Common::UUID user_id_)
+ : ServiceFramework{system_, "IManagerForApplication"}, user_id{user_id_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
@@ -496,7 +510,7 @@ public:
{3, nullptr, "LoadIdTokenCache"},
{130, nullptr, "GetNintendoAccountUserResourceCacheForApplication"},
{150, nullptr, "CreateAuthorizationRequest"},
- {160, nullptr, "StoreOpenContext"},
+ {160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"},
{170, nullptr, "LoadNetworkServiceLicenseKindAsync"},
};
// clang-format on
@@ -520,15 +534,21 @@ private:
rb.PushRaw<u64>(user_id.GetNintendoID());
}
- Common::UUID user_id;
+ void StoreOpenContext(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ Common::UUID user_id{Common::INVALID_UUID};
};
// 6.0.0+
class IAsyncNetworkServiceLicenseKindContext final
: public ServiceFramework<IAsyncNetworkServiceLicenseKindContext> {
public:
- explicit IAsyncNetworkServiceLicenseKindContext(Common::UUID user_id)
- : ServiceFramework("IAsyncNetworkServiceLicenseKindContext") {
+ explicit IAsyncNetworkServiceLicenseKindContext(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IAsyncNetworkServiceLicenseKindContext"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetSystemEvent"},
@@ -547,8 +567,8 @@ public:
class IOAuthProcedureForUserRegistration final
: public ServiceFramework<IOAuthProcedureForUserRegistration> {
public:
- explicit IOAuthProcedureForUserRegistration(Common::UUID user_id)
- : ServiceFramework("IOAuthProcedureForUserRegistration") {
+ explicit IOAuthProcedureForUserRegistration(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IOAuthProcedureForUserRegistration"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "PrepareAsync"},
@@ -571,7 +591,7 @@ public:
class DAUTH_O final : public ServiceFramework<DAUTH_O> {
public:
- explicit DAUTH_O(Common::UUID) : ServiceFramework("dauth:o") {
+ explicit DAUTH_O(Core::System& system_, Common::UUID) : ServiceFramework{system_, "dauth:o"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "EnsureAuthenticationTokenCacheAsync"}, // [5.0.0-5.1.0] GeneratePostData
@@ -590,7 +610,8 @@ public:
// 6.0.0+
class IAsyncResult final : public ServiceFramework<IAsyncResult> {
public:
- explicit IAsyncResult(Common::UUID user_id) : ServiceFramework("IAsyncResult") {
+ explicit IAsyncResult(Core::System& system_, Common::UUID)
+ : ServiceFramework{system_, "IAsyncResult"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetResult"},
@@ -649,7 +670,7 @@ void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IProfile>(user_id, *profile_manager);
+ rb.PushIpcInterface<IProfile>(system, user_id, *profile_manager);
}
void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) {
@@ -724,7 +745,7 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IManagerForApplication>(profile_manager->GetLastOpenedUser());
+ rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser());
}
void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) {
@@ -735,8 +756,10 @@ void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx
bool is_locked = false;
if (res != Loader::ResultStatus::Success) {
- FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
- auto nacp_unique = pm.GetControlMetadata().first;
+ const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(),
+ system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto nacp_unique = pm.GetControlMetadata().first;
if (nacp_unique != nullptr) {
is_locked = nacp_unique->GetUserAccountSwitchLock();
@@ -760,7 +783,7 @@ void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IProfileEditor>(user_id, *profile_manager);
+ rb.PushIpcInterface<IProfileEditor>(system, user_id, *profile_manager);
}
void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) {
@@ -774,6 +797,17 @@ void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
+void Module::Interface::LoadOpenContext(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_ACC, "(STUBBED) called");
+
+ // This is similar to GetBaasAccountManagerForApplication
+ // This command is used concurrently with ListOpenContextStoredUsers
+ // TODO: Find the differences between this and GetBaasAccountManagerForApplication
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser());
+}
+
void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_ACC, "(STUBBED) called");
@@ -783,6 +817,55 @@ void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ct
rb.Push(RESULT_SUCCESS);
}
+void Module::Interface::StoreSaveDataThumbnailApplication(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto uuid = rp.PopRaw<Common::UUID>();
+
+ LOG_WARNING(Service_ACC, "(STUBBED) called, uuid={}", uuid.Format());
+
+ // TODO(ogniK): Check if application ID is zero on acc initialize. As we don't have a reliable
+ // way of confirming things like the TID, we're going to assume a non zero value for the time
+ // being.
+ constexpr u64 tid{1};
+ StoreSaveDataThumbnail(ctx, uuid, tid);
+}
+
+void Module::Interface::StoreSaveDataThumbnailSystem(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto uuid = rp.PopRaw<Common::UUID>();
+ const auto tid = rp.Pop<u64_le>();
+
+ LOG_WARNING(Service_ACC, "(STUBBED) called, uuid={}, tid={:016X}", uuid.Format(), tid);
+ StoreSaveDataThumbnail(ctx, uuid, tid);
+}
+
+void Module::Interface::StoreSaveDataThumbnail(Kernel::HLERequestContext& ctx,
+ const Common::UUID& uuid, const u64 tid) {
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ if (tid == 0) {
+ LOG_ERROR(Service_ACC, "TitleID is not valid!");
+ rb.Push(ERR_INVALID_APPLICATION_ID);
+ return;
+ }
+
+ if (!uuid) {
+ LOG_ERROR(Service_ACC, "User ID is not valid!");
+ rb.Push(ERR_INVALID_USER_ID);
+ return;
+ }
+ const auto thumbnail_size = ctx.GetReadBufferSize();
+ if (thumbnail_size != THUMBNAIL_SIZE) {
+ LOG_ERROR(Service_ACC, "Buffer size is empty! size={:X} expecting {:X}", thumbnail_size,
+ THUMBNAIL_SIZE);
+ rb.Push(ERR_INVALID_BUFFER_SIZE);
+ return;
+ }
+
+ // TODO(ogniK): Construct save data thumbnail
+ rb.Push(RESULT_SUCCESS);
+}
+
void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ACC, "called");
// A u8 is passed into this function which we can safely ignore. It's to determine if we have
@@ -807,11 +890,11 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex
rb.PushRaw<u128>(profile_manager->GetUser(0)->uuid);
}
-Module::Interface::Interface(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager, Core::System& system,
- const char* name)
- : ServiceFramework(name), module(std::move(module)),
- profile_manager(std::move(profile_manager)), system(system) {}
+Module::Interface::Interface(std::shared_ptr<Module> module_,
+ std::shared_ptr<ProfileManager> profile_manager_,
+ Core::System& system_, const char* name)
+ : ServiceFramework{system_, name}, module{std::move(module_)}, profile_manager{std::move(
+ profile_manager_)} {}
Module::Interface::~Interface() = default;
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index d4c6395c6..0e3ad8ec6 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -4,6 +4,7 @@
#pragma once
+#include "common/uuid.h"
#include "core/hle/service/glue/manager.h"
#include "core/hle/service/service.h"
@@ -15,8 +16,8 @@ class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module,
- std::shared_ptr<ProfileManager> profile_manager, Core::System& system,
+ explicit Interface(std::shared_ptr<Module> module_,
+ std::shared_ptr<ProfileManager> profile_manager_, Core::System& system_,
const char* name);
~Interface() override;
@@ -34,10 +35,15 @@ public:
void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
void GetProfileEditor(Kernel::HLERequestContext& ctx);
void ListQualifiedUsers(Kernel::HLERequestContext& ctx);
+ void LoadOpenContext(Kernel::HLERequestContext& ctx);
void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx);
+ void StoreSaveDataThumbnailApplication(Kernel::HLERequestContext& ctx);
+ void StoreSaveDataThumbnailSystem(Kernel::HLERequestContext& ctx);
private:
ResultCode InitializeApplicationInfoBase();
+ void StoreSaveDataThumbnail(Kernel::HLERequestContext& ctx, const Common::UUID& uuid,
+ const u64 tid);
enum class ApplicationType : u32_le {
GameCard = 0,
@@ -59,7 +65,6 @@ public:
protected:
std::shared_ptr<Module> module;
std::shared_ptr<ProfileManager> profile_manager;
- Core::System& system;
};
};
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index d2bb8c2c8..49b22583e 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -29,7 +29,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{104, nullptr, "GetProfileUpdateNotifier"},
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+
- {110, nullptr, "StoreSaveDataThumbnail"},
+ {110, &ACC_SU::StoreSaveDataThumbnailSystem, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"},
{113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index cb44e06b7..8d66d180d 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -26,10 +26,10 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
{102, nullptr, "AuthenticateApplicationAsync"},
{103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
- {110, nullptr, "StoreSaveDataThumbnail"},
+ {110, &ACC_U0::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{120, nullptr, "CreateGuestLoginRequest"},
- {130, nullptr, "LoadOpenContext"}, // 5.0.0+
+ {130, &ACC_U0::LoadOpenContext, "LoadOpenContext"}, // 5.0.0+
{131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
{141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp
index a4aa5316a..951081cd0 100644
--- a/src/core/hle/service/acc/acc_u1.cpp
+++ b/src/core/hle/service/acc/acc_u1.cpp
@@ -29,7 +29,7 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
{104, nullptr, "GetProfileUpdateNotifier"},
{105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+
{106, nullptr, "GetProfileSyncNotifier"}, // 9.0.0+
- {110, nullptr, "StoreSaveDataThumbnail"},
+ {110, &ACC_U1::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{112, nullptr, "LoadSaveDataThumbnail"},
{113, nullptr, "GetSaveDataThumbnailExistence"}, // 5.0.0+
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 9b829e957..50b2c58e2 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -41,12 +41,18 @@ constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/
ProfileManager::ProfileManager() {
ParseUserSaveFile();
- if (user_count == 0)
+ // Create an user if none are present
+ if (user_count == 0) {
CreateNewUser(UUID::Generate(), "yuzu");
+ }
auto current = std::clamp<int>(Settings::values.current_user, 0, MAX_USERS - 1);
- if (UserExistsIndex(current))
+
+ // If user index don't exist. Load the first user and change the active user
+ if (!UserExistsIndex(current)) {
current = 0;
+ Settings::values.current_user = 0;
+ }
OpenUser(*GetUser(current));
}
@@ -227,17 +233,17 @@ void ProfileManager::CloseUser(UUID uuid) {
/// Gets all valid user ids on the system
UserIDArray ProfileManager::GetAllUsers() const {
- UserIDArray output;
- std::transform(profiles.begin(), profiles.end(), output.begin(),
- [](const ProfileInfo& p) { return p.user_uuid; });
+ UserIDArray output{};
+ std::ranges::transform(profiles, output.begin(),
+ [](const ProfileInfo& p) { return p.user_uuid; });
return output;
}
/// Get all the open users on the system and zero out the rest of the data. This is specifically
/// needed for GetOpenUsers and we need to ensure the rest of the output buffer is zero'd out
UserIDArray ProfileManager::GetOpenUsers() const {
- UserIDArray output;
- std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) {
+ UserIDArray output{};
+ std::ranges::transform(profiles, output.begin(), [](const ProfileInfo& p) {
if (p.is_open)
return p.user_uuid;
return UUID{Common::INVALID_UUID};
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 5310637a6..71b9d5518 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -23,12 +23,12 @@ using UserIDArray = std::array<Common::UUID, MAX_USERS>;
/// Contains extra data related to a user.
/// TODO: RE this structure
struct ProfileData {
- INSERT_PADDING_WORDS(1);
- u32 icon_id{};
- u8 bg_color_id{};
- INSERT_PADDING_BYTES(0x7);
- INSERT_PADDING_BYTES(0x10);
- INSERT_PADDING_BYTES(0x60);
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u32 icon_id;
+ u8 bg_color_id;
+ INSERT_PADDING_BYTES_NOINIT(0x7);
+ INSERT_PADDING_BYTES_NOINIT(0x10);
+ INSERT_PADDING_BYTES_NOINIT(0x60);
};
static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect size");
@@ -43,9 +43,9 @@ struct ProfileInfo {
};
struct ProfileBase {
- Common::UUID user_uuid{Common::INVALID_UUID};
- u64_le timestamp{};
- ProfileUsername username{};
+ Common::UUID user_uuid;
+ u64_le timestamp;
+ ProfileUsername username;
// Zero out all the fields to make the profile slot considered "Empty"
void Invalidate() {
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 7d92b25a3..8e1fe9438 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -13,11 +13,12 @@
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/savedata_factory.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/transfer_memory.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
@@ -64,7 +65,7 @@ struct LaunchParameterAccountPreselectedUser {
static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88);
IWindowController::IWindowController(Core::System& system_)
- : ServiceFramework("IWindowController"), system{system_} {
+ : ServiceFramework{system_, "IWindowController"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateWindow"},
@@ -99,7 +100,8 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx)
rb.Push(RESULT_SUCCESS);
}
-IAudioController::IAudioController() : ServiceFramework("IAudioController") {
+IAudioController::IAudioController(Core::System& system_)
+ : ServiceFramework{system_, "IAudioController"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"},
@@ -180,7 +182,8 @@ void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
-IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
+IDisplayController::IDisplayController(Core::System& system_)
+ : ServiceFramework{system_, "IDisplayController"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetLastForegroundCaptureImage"},
@@ -219,7 +222,8 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
IDisplayController::~IDisplayController() = default;
-IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
+IDebugFunctions::IDebugFunctions(Core::System& system_)
+ : ServiceFramework{system_, "IDebugFunctions"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "NotifyMessageToHomeMenuForDebug"},
@@ -246,9 +250,8 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
IDebugFunctions::~IDebugFunctions() = default;
-ISelfController::ISelfController(Core::System& system,
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
- : ServiceFramework("ISelfController"), system(system), nvflinger(std::move(nvflinger)) {
+ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nvflinger_)
+ : ServiceFramework{system_, "ISelfController"}, nvflinger{nvflinger_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISelfController::Exit, "Exit"},
@@ -301,17 +304,18 @@ ISelfController::ISelfController(Core::System& system,
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- launchable_event =
- Kernel::WritableEvent::CreateEventPair(kernel, "ISelfController:LaunchableEvent");
+ launchable_event = Kernel::KEvent::Create(kernel, "ISelfController:LaunchableEvent");
+ launchable_event->Initialize();
// This event is created by AM on the first time GetAccumulatedSuspendedTickChangedEvent() is
// called. Yuzu can just create it unconditionally, since it doesn't need to support multiple
// ISelfControllers. The event is signaled on creation, and on transition from suspended -> not
// suspended if the event has previously been created by a call to
// GetAccumulatedSuspendedTickChangedEvent.
- accumulated_suspended_tick_changed_event = Kernel::WritableEvent::CreateEventPair(
- kernel, "ISelfController:AccumulatedSuspendedTickChangedEvent");
- accumulated_suspended_tick_changed_event.writable->Signal();
+ accumulated_suspended_tick_changed_event =
+ Kernel::KEvent::Create(kernel, "ISelfController:AccumulatedSuspendedTickChangedEvent");
+ accumulated_suspended_tick_changed_event->Initialize();
+ accumulated_suspended_tick_changed_event->GetWritableEvent()->Signal();
}
ISelfController::~ISelfController() = default;
@@ -370,11 +374,11 @@ void ISelfController::LeaveFatalSection(Kernel::HLERequestContext& ctx) {
void ISelfController::GetLibraryAppletLaunchableEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
- launchable_event.writable->Signal();
+ launchable_event->GetWritableEvent()->Signal();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(launchable_event.readable);
+ rb.PushCopyObjects(launchable_event->GetReadableEvent());
}
void ISelfController::SetScreenShotPermission(Kernel::HLERequestContext& ctx) {
@@ -458,8 +462,8 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx)
// TODO(Subv): Find out how AM determines the display to use, for now just
// create the layer in the Default display.
- const auto display_id = nvflinger->OpenDisplay("Default");
- const auto layer_id = nvflinger->CreateLayer(*display_id);
+ const auto display_id = nvflinger.OpenDisplay("Default");
+ const auto layer_id = nvflinger.CreateLayer(*display_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
@@ -476,8 +480,8 @@ void ISelfController::CreateManagedDisplaySeparableLayer(Kernel::HLERequestConte
// Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse
// side effects.
// TODO: Support multiple layers
- const auto display_id = nvflinger->OpenDisplay("Default");
- const auto layer_id = nvflinger->CreateLayer(*display_id);
+ const auto display_id = nvflinger.OpenDisplay("Default");
+ const auto layer_id = nvflinger.CreateLayer(*display_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
@@ -553,41 +557,42 @@ void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequest
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable);
+ rb.PushCopyObjects(accumulated_suspended_tick_changed_event->GetReadableEvent());
}
AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) {
- on_new_message =
- Kernel::WritableEvent::CreateEventPair(kernel, "AMMessageQueue:OnMessageRecieved");
+ on_new_message = Kernel::KEvent::Create(kernel, "AMMessageQueue:OnMessageReceived");
+ on_new_message->Initialize();
on_operation_mode_changed =
- Kernel::WritableEvent::CreateEventPair(kernel, "AMMessageQueue:OperationModeChanged");
+ Kernel::KEvent::Create(kernel, "AMMessageQueue:OperationModeChanged");
+ on_operation_mode_changed->Initialize();
}
AppletMessageQueue::~AppletMessageQueue() = default;
-const std::shared_ptr<Kernel::ReadableEvent>& AppletMessageQueue::GetMesssageRecieveEvent() const {
- return on_new_message.readable;
+const std::shared_ptr<Kernel::KReadableEvent>& AppletMessageQueue::GetMessageReceiveEvent() const {
+ return on_new_message->GetReadableEvent();
}
-const std::shared_ptr<Kernel::ReadableEvent>& AppletMessageQueue::GetOperationModeChangedEvent()
+const std::shared_ptr<Kernel::KReadableEvent>& AppletMessageQueue::GetOperationModeChangedEvent()
const {
- return on_operation_mode_changed.readable;
+ return on_operation_mode_changed->GetReadableEvent();
}
void AppletMessageQueue::PushMessage(AppletMessage msg) {
messages.push(msg);
- on_new_message.writable->Signal();
+ on_new_message->GetWritableEvent()->Signal();
}
AppletMessageQueue::AppletMessage AppletMessageQueue::PopMessage() {
if (messages.empty()) {
- on_new_message.writable->Clear();
+ on_new_message->GetWritableEvent()->Clear();
return AppletMessage::NoMessage;
}
auto msg = messages.front();
messages.pop();
if (messages.empty()) {
- on_new_message.writable->Clear();
+ on_new_message->GetWritableEvent()->Clear();
}
return msg;
}
@@ -599,16 +604,16 @@ std::size_t AppletMessageQueue::GetMessageCount() const {
void AppletMessageQueue::OperationModeChanged() {
PushMessage(AppletMessage::OperationModeChanged);
PushMessage(AppletMessage::PerformanceModeChanged);
- on_operation_mode_changed.writable->Signal();
+ on_operation_mode_changed->GetWritableEvent()->Signal();
}
void AppletMessageQueue::RequestExit() {
PushMessage(AppletMessage::ExitRequested);
}
-ICommonStateGetter::ICommonStateGetter(Core::System& system,
- std::shared_ptr<AppletMessageQueue> msg_queue)
- : ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) {
+ICommonStateGetter::ICommonStateGetter(Core::System& system_,
+ std::shared_ptr<AppletMessageQueue> msg_queue_)
+ : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -633,7 +638,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system,
{50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
{51, &ICommonStateGetter::SetVrModeEnabled, "SetVrModeEnabled"},
{52, &ICommonStateGetter::SetLcdBacklighOffEnabled, "SetLcdBacklighOffEnabled"},
- {53, nullptr, "BeginVrModeEx"},
+ {53, &ICommonStateGetter::BeginVrModeEx, "BeginVrModeEx"},
{54, &ICommonStateGetter::EndVrModeEx, "EndVrModeEx"},
{55, nullptr, "IsInControllerFirmwareUpdateSection"},
{60, &ICommonStateGetter::GetDefaultDisplayResolution, "GetDefaultDisplayResolution"},
@@ -673,7 +678,7 @@ void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(msg_queue->GetMesssageRecieveEvent());
+ rb.PushCopyObjects(msg_queue->GetMessageReceiveEvent());
}
void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) {
@@ -730,6 +735,13 @@ void ICommonStateGetter::SetLcdBacklighOffEnabled(Kernel::HLERequestContext& ctx
rb.Push(RESULT_SUCCESS);
}
+void ICommonStateGetter::BeginVrModeEx(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
void ICommonStateGetter::EndVrModeEx(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -751,7 +763,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext&
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- if (Settings::values.use_docked_mode) {
+ if (Settings::values.use_docked_mode.GetValue()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *
@@ -796,8 +808,9 @@ private:
std::vector<u8> buffer;
};
-IStorage::IStorage(std::vector<u8>&& buffer)
- : ServiceFramework("IStorage"), impl{std::make_shared<StorageDataImpl>(std::move(buffer))} {
+IStorage::IStorage(Core::System& system_, std::vector<u8>&& buffer)
+ : ServiceFramework{system_, "IStorage"}, impl{std::make_shared<StorageDataImpl>(
+ std::move(buffer))} {
Register();
}
@@ -820,11 +833,11 @@ void IStorage::Open(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IStorageAccessor>(*this);
+ rb.PushIpcInterface<IStorageAccessor>(system, *this);
}
void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
- const bool use_docked_mode{Settings::values.use_docked_mode};
+ const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()};
LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
IPC::ResponseBuilder rb{ctx, 3};
@@ -842,8 +855,8 @@ void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
public:
- explicit ILibraryAppletAccessor(std::shared_ptr<Applets::Applet> applet)
- : ServiceFramework("ILibraryAppletAccessor"), applet(std::move(applet)) {
+ explicit ILibraryAppletAccessor(Core::System& system_, std::shared_ptr<Applets::Applet> applet_)
+ : ServiceFramework{system_, "ILibraryAppletAccessor"}, applet{std::move(applet_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"},
@@ -853,7 +866,7 @@ public:
{25, nullptr, "Terminate"},
{30, &ILibraryAppletAccessor::GetResult, "GetResult"},
{50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"},
- {60, nullptr, "PresetLibraryAppletGpuTimeSliceZero"},
+ {60, &ILibraryAppletAccessor::PresetLibraryAppletGpuTimeSliceZero, "PresetLibraryAppletGpuTimeSliceZero"},
{100, &ILibraryAppletAccessor::PushInData, "PushInData"},
{101, &ILibraryAppletAccessor::PopOutData, "PopOutData"},
{102, nullptr, "PushExtraStorage"},
@@ -897,6 +910,13 @@ private:
rb.Push(applet->GetStatus());
}
+ void PresetLibraryAppletGpuTimeSliceZero(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
void Start(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
@@ -998,8 +1018,8 @@ private:
std::shared_ptr<Applets::Applet> applet;
};
-IStorageAccessor::IStorageAccessor(IStorage& storage)
- : ServiceFramework("IStorageAccessor"), backing(storage) {
+IStorageAccessor::IStorageAccessor(Core::System& system_, IStorage& backing_)
+ : ServiceFramework{system_, "IStorageAccessor"}, backing{backing_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IStorageAccessor::GetSize, "GetSize"},
@@ -1027,20 +1047,21 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
const u64 offset{rp.Pop<u64>()};
const std::vector<u8> data{ctx.ReadBuffer()};
+ const std::size_t size{std::min(data.size(), backing.GetSize() - offset)};
- LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size());
+ LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
- if (data.size() > backing.GetSize() - offset) {
+ if (offset > backing.GetSize()) {
LOG_ERROR(Service_AM,
"offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
- backing.GetSize(), data.size(), offset);
+ backing.GetSize(), size, offset);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
return;
}
- std::memcpy(backing.GetData().data() + offset, data.data(), data.size());
+ std::memcpy(backing.GetData().data() + offset, data.data(), size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -1050,11 +1071,11 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
- const std::size_t size{ctx.GetWriteBufferSize()};
+ const std::size_t size{std::min(ctx.GetWriteBufferSize(), backing.GetSize() - offset)};
LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
- if (size > backing.GetSize() - offset) {
+ if (offset > backing.GetSize()) {
LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
backing.GetSize(), size, offset);
@@ -1070,7 +1091,7 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
}
ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_)
- : ServiceFramework("ILibraryAppletCreator"), system{system_} {
+ : ServiceFramework{system_, "ILibraryAppletCreator"} {
static const FunctionInfo functions[] = {
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
{1, nullptr, "TerminateAllLibraryApplets"},
@@ -1089,14 +1110,14 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
const auto applet_id = rp.PopRaw<Applets::AppletId>();
const auto applet_mode = rp.PopRaw<u32>();
- LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}",
- static_cast<u32>(applet_id), applet_mode);
+ LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id,
+ applet_mode);
const auto& applet_manager{system.GetAppletManager()};
const auto applet = applet_manager.GetApplet(applet_id);
if (applet == nullptr) {
- LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id));
+ LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_UNKNOWN);
@@ -1106,7 +1127,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<AM::ILibraryAppletAccessor>(applet);
+ rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
}
void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) {
@@ -1118,7 +1139,7 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<AM::IStorage>(std::move(buffer));
+ rb.PushIpcInterface<IStorage>(system, std::move(buffer));
}
void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) {
@@ -1145,11 +1166,11 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IStorage>(std::move(memory));
+ rb.PushIpcInterface<IStorage>(system, std::move(memory));
}
IApplicationFunctions::IApplicationFunctions(Core::System& system_)
- : ServiceFramework("IApplicationFunctions"), system{system_} {
+ : ServiceFramework{system_, "IApplicationFunctions"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
@@ -1175,7 +1196,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
{50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"},
{60, nullptr, "SetMediaPlaybackStateForApplication"},
- {65, nullptr, "IsGamePlayRecordingSupported"},
+ {65, &IApplicationFunctions::IsGamePlayRecordingSupported, "IsGamePlayRecordingSupported"},
{66, &IApplicationFunctions::InitializeGamePlayRecording, "InitializeGamePlayRecording"},
{67, &IApplicationFunctions::SetGamePlayRecordingState, "SetGamePlayRecordingState"},
{68, nullptr, "RequestFlushGamePlayingMovieForDebug"},
@@ -1189,18 +1210,20 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{102, &IApplicationFunctions::SetApplicationCopyrightVisibility, "SetApplicationCopyrightVisibility"},
{110, &IApplicationFunctions::QueryApplicationPlayStatistics, "QueryApplicationPlayStatistics"},
{111, &IApplicationFunctions::QueryApplicationPlayStatisticsByUid, "QueryApplicationPlayStatisticsByUid"},
- {120, nullptr, "ExecuteProgram"},
- {121, nullptr, "ClearUserChannel"},
- {122, nullptr, "UnpopToUserChannel"},
- {123, nullptr, "GetPreviousProgramIndex"},
+ {120, &IApplicationFunctions::ExecuteProgram, "ExecuteProgram"},
+ {121, &IApplicationFunctions::ClearUserChannel, "ClearUserChannel"},
+ {122, &IApplicationFunctions::UnpopToUserChannel, "UnpopToUserChannel"},
+ {123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"},
{124, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
{130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"},
{140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"},
- {141, nullptr, "TryPopFromFriendInvitationStorageChannel"},
+ {141, &IApplicationFunctions::TryPopFromFriendInvitationStorageChannel, "TryPopFromFriendInvitationStorageChannel"},
{150, nullptr, "GetNotificationStorageChannelEvent"},
{151, nullptr, "TryPopFromNotificationStorageChannel"},
- {160, nullptr, "GetHealthWarningDisappearedSystemEvent"},
+ {160, &IApplicationFunctions::GetHealthWarningDisappearedSystemEvent, "GetHealthWarningDisappearedSystemEvent"},
{170, nullptr, "SetHdcpAuthenticationActivated"},
+ {180, nullptr, "GetLaunchRequiredVersion"},
+ {181, nullptr, "UpgradeLaunchRequiredVersion"},
{500, nullptr, "StartContinuousRecordingFlushForDebug"},
{1000, nullptr, "CreateMovieMaker"},
{1001, nullptr, "PrepareForJit"},
@@ -1210,11 +1233,15 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair(
- kernel, "IApplicationFunctions:GpuErrorDetectedSystemEvent");
-
- friend_invitation_storage_channel_event = Kernel::WritableEvent::CreateEventPair(
- kernel, "IApplicationFunctions:FriendInvitationStorageChannelEvent");
+ gpu_error_detected_event =
+ Kernel::KEvent::Create(kernel, "IApplicationFunctions:GpuErrorDetectedSystemEvent");
+ gpu_error_detected_event->Initialize();
+ friend_invitation_storage_channel_event =
+ Kernel::KEvent::Create(kernel, "IApplicationFunctions:FriendInvitationStorageChannelEvent");
+ friend_invitation_storage_channel_event->Initialize();
+ health_warning_disappeared_system_event =
+ Kernel::KEvent::Create(kernel, "IApplicationFunctions:HealthWarningDisappearedSystemEvent");
+ health_warning_disappeared_system_event->Initialize();
}
IApplicationFunctions::~IApplicationFunctions() = default;
@@ -1285,7 +1312,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto kind = rp.PopEnum<LaunchParameterKind>();
- LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast<u8>(kind));
+ LOG_DEBUG(Service_AM, "called, kind={:08X}", kind);
if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) {
@@ -1299,7 +1326,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
if (data.has_value()) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IStorage>(std::move(*data));
+ rb.PushIpcInterface<IStorage>(system, std::move(*data));
launch_popped_application_specific = true;
return;
}
@@ -1322,7 +1349,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
std::memcpy(buffer.data(), &params, buffer.size());
- rb.PushIpcInterface<IStorage>(std::move(buffer));
+ rb.PushIpcInterface<IStorage>(system, std::move(buffer));
launch_popped_account_preselect = true;
return;
}
@@ -1379,13 +1406,16 @@ void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) {
const auto res = [this] {
const auto title_id = system.CurrentProcess()->GetTitleID();
- FileSys::PatchManager pm{title_id};
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
auto res = pm.GetControlMetadata();
if (res.first != nullptr) {
return res;
}
- FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)};
+ const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id),
+ system.GetFileSystemController(),
+ system.GetContentProvider()};
return pm_update.GetControlMetadata();
}();
@@ -1413,13 +1443,16 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
const auto res = [this] {
const auto title_id = system.CurrentProcess()->GetTitleID();
- FileSys::PatchManager pm{title_id};
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
auto res = pm.GetControlMetadata();
if (res.first != nullptr) {
return res;
}
- FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)};
+ const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id),
+ system.GetFileSystemController(),
+ system.GetContentProvider()};
return pm_update.GetControlMetadata();
}();
@@ -1455,6 +1488,16 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
rb.Push(*res_code);
}
+void IApplicationFunctions::IsGamePlayRecordingSupported(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ constexpr bool gameplay_recording_supported = false;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(gameplay_recording_supported);
+}
+
void IApplicationFunctions::InitializeGamePlayRecording(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -1526,8 +1569,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto [type, user_id] = rp.PopRaw<Parameters>();
- LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type),
- user_id[1], user_id[0]);
+ LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", type, user_id[1],
+ user_id[0]);
const auto size = system.GetFileSystemController().ReadSaveDataSize(
type, system.CurrentProcess()->GetTitleID(), user_id);
@@ -1554,12 +1597,48 @@ void IApplicationFunctions::QueryApplicationPlayStatisticsByUid(Kernel::HLEReque
rb.Push<u32>(0);
}
+void IApplicationFunctions::ExecuteProgram(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::RequestParser rp{ctx};
+ [[maybe_unused]] const auto unk_1 = rp.Pop<u32>();
+ [[maybe_unused]] const auto unk_2 = rp.Pop<u32>();
+ const auto program_index = rp.Pop<u64>();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+
+ system.ExecuteProgram(program_index);
+}
+
+void IApplicationFunctions::ClearUserChannel(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void IApplicationFunctions::UnpopToUserChannel(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void IApplicationFunctions::GetPreviousProgramIndex(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(previous_program_index);
+}
+
void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(gpu_error_detected_event.readable);
+ rb.PushCopyObjects(gpu_error_detected_event->GetReadableEvent());
}
void IApplicationFunctions::GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx) {
@@ -1567,25 +1646,41 @@ void IApplicationFunctions::GetFriendInvitationStorageChannelEvent(Kernel::HLERe
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(friend_invitation_storage_channel_event.readable);
+ rb.PushCopyObjects(friend_invitation_storage_channel_event->GetReadableEvent());
+}
+
+void IApplicationFunctions::TryPopFromFriendInvitationStorageChannel(
+ Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_NO_DATA_IN_CHANNEL);
+}
+
+void IApplicationFunctions::GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(health_warning_disappeared_system_event->GetReadableEvent());
}
-void InstallInterfaces(SM::ServiceManager& service_manager,
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) {
+void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
+ Core::System& system) {
auto message_queue = std::make_shared<AppletMessageQueue>(system.Kernel());
// Needed on game boot
message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
- std::make_shared<IdleSys>()->InstallAsService(service_manager);
- std::make_shared<OMM>()->InstallAsService(service_manager);
- std::make_shared<SPSM>()->InstallAsService(service_manager);
- std::make_shared<TCAP>()->InstallAsService(service_manager);
+ std::make_shared<IdleSys>(system)->InstallAsService(service_manager);
+ std::make_shared<OMM>(system)->InstallAsService(service_manager);
+ std::make_shared<SPSM>(system)->InstallAsService(service_manager);
+ std::make_shared<TCAP>(system)->InstallAsService(service_manager);
}
-IHomeMenuFunctions::IHomeMenuFunctions(Kernel::KernelCore& kernel)
- : ServiceFramework("IHomeMenuFunctions"), kernel(kernel) {
+IHomeMenuFunctions::IHomeMenuFunctions(Core::System& system_)
+ : ServiceFramework{system_, "IHomeMenuFunctions"} {
// clang-format off
static const FunctionInfo functions[] = {
{10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"},
@@ -1603,8 +1698,9 @@ IHomeMenuFunctions::IHomeMenuFunctions(Kernel::KernelCore& kernel)
RegisterHandlers(functions);
- pop_from_general_channel_event = Kernel::WritableEvent::CreateEventPair(
- kernel, "IHomeMenuFunctions:PopFromGeneralChannelEvent");
+ pop_from_general_channel_event =
+ Kernel::KEvent::Create(system.Kernel(), "IHomeMenuFunctions:PopFromGeneralChannelEvent");
+ pop_from_general_channel_event->Initialize();
}
IHomeMenuFunctions::~IHomeMenuFunctions() = default;
@@ -1621,10 +1717,11 @@ void IHomeMenuFunctions::GetPopFromGeneralChannelEvent(Kernel::HLERequestContext
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(pop_from_general_channel_event.readable);
+ rb.PushCopyObjects(pop_from_general_channel_event->GetReadableEvent());
}
-IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") {
+IGlobalStateController::IGlobalStateController(Core::System& system_)
+ : ServiceFramework{system_, "IGlobalStateController"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestToEnterSleep"},
@@ -1647,7 +1744,8 @@ IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStat
IGlobalStateController::~IGlobalStateController() = default;
-IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") {
+IApplicationCreator::IApplicationCreator(Core::System& system_)
+ : ServiceFramework{system_, "IApplicationCreator"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateApplication"},
@@ -1662,8 +1760,8 @@ IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreat
IApplicationCreator::~IApplicationCreator() = default;
-IProcessWindingController::IProcessWindingController()
- : ServiceFramework("IProcessWindingController") {
+IProcessWindingController::IProcessWindingController(Core::System& system_)
+ : ServiceFramework{system_, "IProcessWindingController"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetLaunchReason"},
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 6e69796ec..6911f0d6e 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -7,11 +7,12 @@
#include <chrono>
#include <memory>
#include <queue>
-#include "core/hle/kernel/writable_event.h"
+
#include "core/hle/service/service.h"
namespace Kernel {
class KernelCore;
+class KEvent;
class TransferMemory;
} // namespace Kernel
@@ -55,8 +56,8 @@ public:
explicit AppletMessageQueue(Kernel::KernelCore& kernel);
~AppletMessageQueue();
- const std::shared_ptr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const;
- const std::shared_ptr<Kernel::ReadableEvent>& GetOperationModeChangedEvent() const;
+ const std::shared_ptr<Kernel::KReadableEvent>& GetMessageReceiveEvent() const;
+ const std::shared_ptr<Kernel::KReadableEvent>& GetOperationModeChangedEvent() const;
void PushMessage(AppletMessage msg);
AppletMessage PopMessage();
std::size_t GetMessageCount() const;
@@ -65,8 +66,8 @@ public:
private:
std::queue<AppletMessage> messages;
- Kernel::EventPair on_new_message;
- Kernel::EventPair on_operation_mode_changed;
+ std::shared_ptr<Kernel::KEvent> on_new_message;
+ std::shared_ptr<Kernel::KEvent> on_operation_mode_changed;
};
class IWindowController final : public ServiceFramework<IWindowController> {
@@ -77,13 +78,11 @@ public:
private:
void GetAppletResourceUserId(Kernel::HLERequestContext& ctx);
void AcquireForegroundRights(Kernel::HLERequestContext& ctx);
-
- Core::System& system;
};
class IAudioController final : public ServiceFramework<IAudioController> {
public:
- IAudioController();
+ explicit IAudioController(Core::System& system_);
~IAudioController() override;
private:
@@ -109,20 +108,19 @@ private:
class IDisplayController final : public ServiceFramework<IDisplayController> {
public:
- IDisplayController();
+ explicit IDisplayController(Core::System& system_);
~IDisplayController() override;
};
class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
public:
- IDebugFunctions();
+ explicit IDebugFunctions(Core::System& system_);
~IDebugFunctions() override;
};
class ISelfController final : public ServiceFramework<ISelfController> {
public:
- explicit ISelfController(Core::System& system_,
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger_);
+ explicit ISelfController(Core::System& system_, NVFlinger::NVFlinger& nvflinger_);
~ISelfController() override;
private:
@@ -155,10 +153,9 @@ private:
Disable = 2,
};
- Core::System& system;
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
- Kernel::EventPair launchable_event;
- Kernel::EventPair accumulated_suspended_tick_changed_event;
+ NVFlinger::NVFlinger& nvflinger;
+ std::shared_ptr<Kernel::KEvent> launchable_event;
+ std::shared_ptr<Kernel::KEvent> accumulated_suspended_tick_changed_event;
u32 idle_time_detection_extension = 0;
u64 num_fatal_sections_entered = 0;
@@ -168,8 +165,8 @@ private:
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
public:
- explicit ICommonStateGetter(Core::System& system,
- std::shared_ptr<AppletMessageQueue> msg_queue);
+ explicit ICommonStateGetter(Core::System& system_,
+ std::shared_ptr<AppletMessageQueue> msg_queue_);
~ICommonStateGetter() override;
private:
@@ -193,11 +190,11 @@ private:
void IsVrModeEnabled(Kernel::HLERequestContext& ctx);
void SetVrModeEnabled(Kernel::HLERequestContext& ctx);
void SetLcdBacklighOffEnabled(Kernel::HLERequestContext& ctx);
+ void BeginVrModeEx(Kernel::HLERequestContext& ctx);
void EndVrModeEx(Kernel::HLERequestContext& ctx);
void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx);
void SetCpuBoostMode(Kernel::HLERequestContext& ctx);
- Core::System& system;
std::shared_ptr<AppletMessageQueue> msg_queue;
bool vr_mode_state{};
};
@@ -212,7 +209,7 @@ public:
class IStorage final : public ServiceFramework<IStorage> {
public:
- explicit IStorage(std::vector<u8>&& buffer);
+ explicit IStorage(Core::System& system_, std::vector<u8>&& buffer);
~IStorage() override;
std::vector<u8>& GetData() {
@@ -236,7 +233,7 @@ private:
class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
public:
- explicit IStorageAccessor(IStorage& backing);
+ explicit IStorageAccessor(Core::System& system_, IStorage& backing_);
~IStorageAccessor() override;
private:
@@ -256,8 +253,6 @@ private:
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
void CreateStorage(Kernel::HLERequestContext& ctx);
void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
-
- Core::System& system;
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
@@ -272,6 +267,7 @@ private:
void SetTerminateResult(Kernel::HLERequestContext& ctx);
void GetDisplayVersion(Kernel::HLERequestContext& ctx);
void GetDesiredLanguage(Kernel::HLERequestContext& ctx);
+ void IsGamePlayRecordingSupported(Kernel::HLERequestContext& ctx);
void InitializeGamePlayRecording(Kernel::HLERequestContext& ctx);
void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx);
void NotifyRunning(Kernel::HLERequestContext& ctx);
@@ -288,49 +284,55 @@ private:
void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx);
void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx);
void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx);
+ void ExecuteProgram(Kernel::HLERequestContext& ctx);
+ void ClearUserChannel(Kernel::HLERequestContext& ctx);
+ void UnpopToUserChannel(Kernel::HLERequestContext& ctx);
+ void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx);
void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx);
+ void TryPopFromFriendInvitationStorageChannel(Kernel::HLERequestContext& ctx);
+ void GetHealthWarningDisappearedSystemEvent(Kernel::HLERequestContext& ctx);
bool launch_popped_application_specific = false;
bool launch_popped_account_preselect = false;
- Kernel::EventPair gpu_error_detected_event;
- Kernel::EventPair friend_invitation_storage_channel_event;
- Core::System& system;
+ s32 previous_program_index{-1};
+ std::shared_ptr<Kernel::KEvent> gpu_error_detected_event;
+ std::shared_ptr<Kernel::KEvent> friend_invitation_storage_channel_event;
+ std::shared_ptr<Kernel::KEvent> health_warning_disappeared_system_event;
};
class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
public:
- explicit IHomeMenuFunctions(Kernel::KernelCore& kernel);
+ explicit IHomeMenuFunctions(Core::System& system_);
~IHomeMenuFunctions() override;
private:
void RequestToGetForeground(Kernel::HLERequestContext& ctx);
void GetPopFromGeneralChannelEvent(Kernel::HLERequestContext& ctx);
- Kernel::EventPair pop_from_general_channel_event;
- Kernel::KernelCore& kernel;
+ std::shared_ptr<Kernel::KEvent> pop_from_general_channel_event;
};
class IGlobalStateController final : public ServiceFramework<IGlobalStateController> {
public:
- IGlobalStateController();
+ explicit IGlobalStateController(Core::System& system_);
~IGlobalStateController() override;
};
class IApplicationCreator final : public ServiceFramework<IApplicationCreator> {
public:
- IApplicationCreator();
+ explicit IApplicationCreator(Core::System& system_);
~IApplicationCreator() override;
};
class IProcessWindingController final : public ServiceFramework<IProcessWindingController> {
public:
- IProcessWindingController();
+ explicit IProcessWindingController(Core::System& system_);
~IProcessWindingController() override;
};
/// Registers all AM services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager,
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system);
+void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
+ Core::System& system);
} // namespace Service::AM
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index 9df286d17..5421e0da0 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -3,8 +3,8 @@
// Refer to the license.txt file included.
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -13,11 +13,11 @@ namespace Service::AM {
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
public:
- explicit ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue,
- Core::System& system)
- : ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)), system(system) {
+ explicit ILibraryAppletProxy(NVFlinger::NVFlinger& nvflinger_,
+ std::shared_ptr<AppletMessageQueue> msg_queue_,
+ Core::System& system_)
+ : ServiceFramework{system_, "ILibraryAppletProxy"}, nvflinger{nvflinger_},
+ msg_queue{std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -66,7 +66,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IAudioController>();
+ rb.PushIpcInterface<IAudioController>(system);
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
@@ -74,7 +74,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDisplayController>();
+ rb.PushIpcInterface<IDisplayController>(system);
}
void GetProcessWindingController(Kernel::HLERequestContext& ctx) {
@@ -82,7 +82,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IProcessWindingController>();
+ rb.PushIpcInterface<IProcessWindingController>(system);
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
@@ -90,7 +90,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDebugFunctions>();
+ rb.PushIpcInterface<IDebugFunctions>(system);
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
@@ -109,17 +109,17 @@ private:
rb.PushIpcInterface<IApplicationFunctions>(system);
}
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+ NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
- Core::System& system;
};
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
public:
- explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
- : ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)), system(system) {
+ explicit ISystemAppletProxy(NVFlinger::NVFlinger& nvflinger_,
+ std::shared_ptr<AppletMessageQueue> msg_queue_,
+ Core::System& system_)
+ : ServiceFramework{system_, "ISystemAppletProxy"}, nvflinger{nvflinger_},
+ msg_queue{std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -170,7 +170,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IAudioController>();
+ rb.PushIpcInterface<IAudioController>(system);
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
@@ -178,7 +178,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDisplayController>();
+ rb.PushIpcInterface<IDisplayController>(system);
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
@@ -186,7 +186,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDebugFunctions>();
+ rb.PushIpcInterface<IDebugFunctions>(system);
}
void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) {
@@ -202,7 +202,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHomeMenuFunctions>(system.Kernel());
+ rb.PushIpcInterface<IHomeMenuFunctions>(system);
}
void GetGlobalStateController(Kernel::HLERequestContext& ctx) {
@@ -210,7 +210,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IGlobalStateController>();
+ rb.PushIpcInterface<IGlobalStateController>(system);
}
void GetApplicationCreator(Kernel::HLERequestContext& ctx) {
@@ -218,11 +218,11 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationCreator>();
+ rb.PushIpcInterface<IApplicationCreator>(system);
}
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+
+ NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
- Core::System& system;
};
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
@@ -249,10 +249,10 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
}
-AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
- : ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)), system(system) {
+AppletAE::AppletAE(NVFlinger::NVFlinger& nvflinger_, std::shared_ptr<AppletMessageQueue> msg_queue_,
+ Core::System& system_)
+ : ServiceFramework{system_, "appletAE"}, nvflinger{nvflinger_}, msg_queue{
+ std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h
index 2e3e45915..adb207349 100644
--- a/src/core/hle/service/am/applet_ae.h
+++ b/src/core/hle/service/am/applet_ae.h
@@ -23,8 +23,8 @@ class AppletMessageQueue;
class AppletAE final : public ServiceFramework<AppletAE> {
public:
- explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
+ explicit AppletAE(NVFlinger::NVFlinger& nvflinger_,
+ std::shared_ptr<AppletMessageQueue> msg_queue_, Core::System& system_);
~AppletAE() override;
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
@@ -34,9 +34,8 @@ private:
void OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx);
void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx);
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+ NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
- Core::System& system;
};
} // namespace AM
diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp
index a2ffaa440..f9eba8f52 100644
--- a/src/core/hle/service/am/applet_oe.cpp
+++ b/src/core/hle/service/am/applet_oe.cpp
@@ -12,10 +12,11 @@ namespace Service::AM {
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
public:
- explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
- : ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)), system(system) {
+ explicit IApplicationProxy(NVFlinger::NVFlinger& nvflinger_,
+ std::shared_ptr<AppletMessageQueue> msg_queue_,
+ Core::System& system_)
+ : ServiceFramework{system_, "IApplicationProxy"}, nvflinger{nvflinger_},
+ msg_queue{std::move(msg_queue_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
@@ -39,7 +40,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IAudioController>();
+ rb.PushIpcInterface<IAudioController>(system);
}
void GetDisplayController(Kernel::HLERequestContext& ctx) {
@@ -47,7 +48,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDisplayController>();
+ rb.PushIpcInterface<IDisplayController>(system);
}
void GetDebugFunctions(Kernel::HLERequestContext& ctx) {
@@ -55,7 +56,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDebugFunctions>();
+ rb.PushIpcInterface<IDebugFunctions>(system);
}
void GetWindowController(Kernel::HLERequestContext& ctx) {
@@ -98,9 +99,8 @@ private:
rb.PushIpcInterface<IApplicationFunctions>(system);
}
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+ NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
- Core::System& system;
};
void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
@@ -111,10 +111,10 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue, system);
}
-AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
- : ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)),
- msg_queue(std::move(msg_queue)), system(system) {
+AppletOE::AppletOE(NVFlinger::NVFlinger& nvflinger_, std::shared_ptr<AppletMessageQueue> msg_queue_,
+ Core::System& system_)
+ : ServiceFramework{system_, "appletOE"}, nvflinger{nvflinger_}, msg_queue{
+ std::move(msg_queue_)} {
static const FunctionInfo functions[] = {
{0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
};
diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h
index 758da792d..6c1aa255a 100644
--- a/src/core/hle/service/am/applet_oe.h
+++ b/src/core/hle/service/am/applet_oe.h
@@ -23,8 +23,8 @@ class AppletMessageQueue;
class AppletOE final : public ServiceFramework<AppletOE> {
public:
- explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
- std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
+ explicit AppletOE(NVFlinger::NVFlinger& nvflinger_,
+ std::shared_ptr<AppletMessageQueue> msg_queue_, Core::System& system_);
~AppletOE() override;
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
@@ -32,9 +32,8 @@ public:
private:
void OpenApplicationProxy(Kernel::HLERequestContext& ctx);
- std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
+ NVFlinger::NVFlinger& nvflinger;
std::shared_ptr<AppletMessageQueue> msg_queue;
- Core::System& system;
};
} // namespace AM
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index c3261f3e6..e2f3b7563 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -3,18 +3,22 @@
// Refer to the license.txt file included.
#include <cstring>
+
#include "common/assert.h"
#include "core/core.h"
+#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
#include "core/frontend/applets/general_frontend.h"
#include "core/frontend/applets/profile_select.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h"
-#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/server_session.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/am/applets/controller.h"
#include "core/hle/service/am/applets/error.h"
#include "core/hle/service/am/applets/general_backend.h"
#include "core/hle/service/am/applets/profile_select.h"
@@ -25,11 +29,13 @@ namespace Service::AM::Applets {
AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) {
state_changed_event =
- Kernel::WritableEvent::CreateEventPair(kernel, "ILibraryAppletAccessor:StateChangedEvent");
- pop_out_data_event =
- Kernel::WritableEvent::CreateEventPair(kernel, "ILibraryAppletAccessor:PopDataOutEvent");
- pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair(
- kernel, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
+ Kernel::KEvent::Create(kernel, "ILibraryAppletAccessor:StateChangedEvent");
+ state_changed_event->Initialize();
+ pop_out_data_event = Kernel::KEvent::Create(kernel, "ILibraryAppletAccessor:PopDataOutEvent");
+ pop_out_data_event->Initialize();
+ pop_interactive_out_data_event =
+ Kernel::KEvent::Create(kernel, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
+ pop_interactive_out_data_event->Initialize();
}
AppletDataBroker::~AppletDataBroker() = default;
@@ -56,7 +62,7 @@ std::shared_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() {
auto out = std::move(out_channel.front());
out_channel.pop_front();
- pop_out_data_event.writable->Clear();
+ pop_out_data_event->GetWritableEvent()->Clear();
return out;
}
@@ -75,7 +81,7 @@ std::shared_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() {
auto out = std::move(out_interactive_channel.front());
out_interactive_channel.pop_front();
- pop_interactive_out_data_event.writable->Clear();
+ pop_interactive_out_data_event->GetWritableEvent()->Clear();
return out;
}
@@ -94,7 +100,7 @@ void AppletDataBroker::PushNormalDataFromGame(std::shared_ptr<IStorage>&& storag
void AppletDataBroker::PushNormalDataFromApplet(std::shared_ptr<IStorage>&& storage) {
out_channel.emplace_back(std::move(storage));
- pop_out_data_event.writable->Signal();
+ pop_out_data_event->GetWritableEvent()->Signal();
}
void AppletDataBroker::PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& storage) {
@@ -103,23 +109,23 @@ void AppletDataBroker::PushInteractiveDataFromGame(std::shared_ptr<IStorage>&& s
void AppletDataBroker::PushInteractiveDataFromApplet(std::shared_ptr<IStorage>&& storage) {
out_interactive_channel.emplace_back(std::move(storage));
- pop_interactive_out_data_event.writable->Signal();
+ pop_interactive_out_data_event->GetWritableEvent()->Signal();
}
void AppletDataBroker::SignalStateChanged() const {
- state_changed_event.writable->Signal();
+ state_changed_event->GetWritableEvent()->Signal();
}
-std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetNormalDataEvent() const {
- return pop_out_data_event.readable;
+std::shared_ptr<Kernel::KReadableEvent> AppletDataBroker::GetNormalDataEvent() const {
+ return pop_out_data_event->GetReadableEvent();
}
-std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetInteractiveDataEvent() const {
- return pop_interactive_out_data_event.readable;
+std::shared_ptr<Kernel::KReadableEvent> AppletDataBroker::GetInteractiveDataEvent() const {
+ return pop_interactive_out_data_event->GetReadableEvent();
}
-std::shared_ptr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent() const {
- return state_changed_event.readable;
+std::shared_ptr<Kernel::KReadableEvent> AppletDataBroker::GetStateChangedEvent() const {
+ return state_changed_event->GetReadableEvent();
}
Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {}
@@ -140,14 +146,14 @@ void Applet::Initialize() {
AppletFrontendSet::AppletFrontendSet() = default;
-AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
- PhotoViewer photo_viewer, ProfileSelect profile_select,
- SoftwareKeyboard software_keyboard, WebBrowser web_browser,
- ECommerceApplet e_commerce)
- : parental_controls{std::move(parental_controls)}, error{std::move(error)},
- photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)},
- software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)},
- e_commerce{std::move(e_commerce)} {}
+AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
+ ParentalControlsApplet parental_controls_applet,
+ PhotoViewer photo_viewer_, ProfileSelect profile_select_,
+ SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
+ : controller{std::move(controller_applet)}, error{std::move(error_applet)},
+ parental_controls{std::move(parental_controls_applet)},
+ photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},
+ software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {}
AppletFrontendSet::~AppletFrontendSet() = default;
@@ -164,20 +170,33 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
}
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
- if (set.parental_controls != nullptr)
- frontend.parental_controls = std::move(set.parental_controls);
- if (set.error != nullptr)
+ if (set.controller != nullptr) {
+ frontend.controller = std::move(set.controller);
+ }
+
+ if (set.error != nullptr) {
frontend.error = std::move(set.error);
- if (set.photo_viewer != nullptr)
+ }
+
+ if (set.parental_controls != nullptr) {
+ frontend.parental_controls = std::move(set.parental_controls);
+ }
+
+ if (set.photo_viewer != nullptr) {
frontend.photo_viewer = std::move(set.photo_viewer);
- if (set.profile_select != nullptr)
+ }
+
+ if (set.profile_select != nullptr) {
frontend.profile_select = std::move(set.profile_select);
- if (set.software_keyboard != nullptr)
+ }
+
+ if (set.software_keyboard != nullptr) {
frontend.software_keyboard = std::move(set.software_keyboard);
- if (set.web_browser != nullptr)
+ }
+
+ if (set.web_browser != nullptr) {
frontend.web_browser = std::move(set.web_browser);
- if (set.e_commerce != nullptr)
- frontend.e_commerce = std::move(set.e_commerce);
+ }
}
void AppletManager::SetDefaultAppletFrontendSet() {
@@ -186,15 +205,20 @@ void AppletManager::SetDefaultAppletFrontendSet() {
}
void AppletManager::SetDefaultAppletsIfMissing() {
- if (frontend.parental_controls == nullptr) {
- frontend.parental_controls =
- std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
+ if (frontend.controller == nullptr) {
+ frontend.controller =
+ std::make_unique<Core::Frontend::DefaultControllerApplet>(system.ServiceManager());
}
if (frontend.error == nullptr) {
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
}
+ if (frontend.parental_controls == nullptr) {
+ frontend.parental_controls =
+ std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
+ }
+
if (frontend.photo_viewer == nullptr) {
frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
}
@@ -211,10 +235,6 @@ void AppletManager::SetDefaultAppletsIfMissing() {
if (frontend.web_browser == nullptr) {
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
}
-
- if (frontend.e_commerce == nullptr) {
- frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
- }
}
void AppletManager::ClearAll() {
@@ -225,19 +245,22 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
switch (id) {
case AppletId::Auth:
return std::make_shared<Auth>(system, *frontend.parental_controls);
+ case AppletId::Controller:
+ return std::make_shared<Controller>(system, *frontend.controller);
case AppletId::Error:
return std::make_shared<Error>(system, *frontend.error);
case AppletId::ProfileSelect:
return std::make_shared<ProfileSelect>(system, *frontend.profile_select);
case AppletId::SoftwareKeyboard:
return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard);
+ case AppletId::Web:
+ case AppletId::Shop:
+ case AppletId::OfflineWeb:
+ case AppletId::LoginShare:
+ case AppletId::WebAuth:
+ return std::make_shared<WebBrowser>(system, *frontend.web_browser);
case AppletId::PhotoViewer:
return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer);
- case AppletId::LibAppletShop:
- return std::make_shared<WebBrowser>(system, *frontend.web_browser,
- frontend.e_commerce.get());
- case AppletId::LibAppletOff:
- return std::make_shared<WebBrowser>(system, *frontend.web_browser);
default:
UNIMPLEMENTED_MSG(
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index e75be86a2..b9a006317 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -6,9 +6,9 @@
#include <memory>
#include <queue>
+
#include "common/swap.h"
#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/writable_event.h"
union ResultCode;
@@ -17,6 +17,7 @@ class System;
}
namespace Core::Frontend {
+class ControllerApplet;
class ECommerceApplet;
class ErrorApplet;
class ParentalControlsApplet;
@@ -28,7 +29,9 @@ class WebBrowserApplet;
namespace Kernel {
class KernelCore;
-}
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
namespace Service::AM {
@@ -49,13 +52,13 @@ enum class AppletId : u32 {
ProfileSelect = 0x10,
SoftwareKeyboard = 0x11,
MiiEdit = 0x12,
- LibAppletWeb = 0x13,
- LibAppletShop = 0x14,
+ Web = 0x13,
+ Shop = 0x14,
PhotoViewer = 0x15,
Settings = 0x16,
- LibAppletOff = 0x17,
- LibAppletWhitelisted = 0x18,
- LibAppletAuth = 0x19,
+ OfflineWeb = 0x17,
+ LoginShare = 0x18,
+ WebAuth = 0x19,
MyPage = 0x1A,
};
@@ -86,9 +89,9 @@ public:
void SignalStateChanged() const;
- std::shared_ptr<Kernel::ReadableEvent> GetNormalDataEvent() const;
- std::shared_ptr<Kernel::ReadableEvent> GetInteractiveDataEvent() const;
- std::shared_ptr<Kernel::ReadableEvent> GetStateChangedEvent() const;
+ std::shared_ptr<Kernel::KReadableEvent> GetNormalDataEvent() const;
+ std::shared_ptr<Kernel::KReadableEvent> GetInteractiveDataEvent() const;
+ std::shared_ptr<Kernel::KReadableEvent> GetStateChangedEvent() const;
private:
// Queues are named from applet's perspective
@@ -105,13 +108,13 @@ private:
// PopInteractiveDataToGame and PushInteractiveDataFromApplet
std::deque<std::shared_ptr<IStorage>> out_interactive_channel;
- Kernel::EventPair state_changed_event;
+ std::shared_ptr<Kernel::KEvent> state_changed_event;
// Signaled on PushNormalDataFromApplet
- Kernel::EventPair pop_out_data_event;
+ std::shared_ptr<Kernel::KEvent> pop_out_data_event;
// Signaled on PushInteractiveDataFromApplet
- Kernel::EventPair pop_interactive_out_data_event;
+ std::shared_ptr<Kernel::KEvent> pop_interactive_out_data_event;
};
class Applet {
@@ -155,19 +158,19 @@ protected:
};
struct AppletFrontendSet {
- using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
+ using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
+ using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
- using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
AppletFrontendSet();
- AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
- PhotoViewer photo_viewer, ProfileSelect profile_select,
- SoftwareKeyboard software_keyboard, WebBrowser web_browser,
- ECommerceApplet e_commerce);
+ AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
+ ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_,
+ ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_,
+ WebBrowser web_browser_);
~AppletFrontendSet();
AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -176,13 +179,13 @@ struct AppletFrontendSet {
AppletFrontendSet(AppletFrontendSet&&) noexcept;
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
- ParentalControlsApplet parental_controls;
+ ControllerApplet controller;
ErrorApplet error;
+ ParentalControlsApplet parental_controls;
PhotoViewer photo_viewer;
ProfileSelect profile_select;
SoftwareKeyboard software_keyboard;
WebBrowser web_browser;
- ECommerceApplet e_commerce;
};
class AppletManager {
diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp
new file mode 100644
index 000000000..c2bfe698f
--- /dev/null
+++ b/src/core/hle/service/am/applets/controller.cpp
@@ -0,0 +1,252 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/frontend/applets/controller.h"
+#include "core/hle/result.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/am/applets/controller.h"
+#include "core/hle/service/hid/controllers/npad.h"
+
+namespace Service::AM::Applets {
+
+// This error code (0x183ACA) is thrown when the applet fails to initialize.
+[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101};
+// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2.
+[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102};
+
+static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
+ ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
+ std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
+ HID::Controller_NPad::NpadStyleSet npad_style_set;
+ npad_style_set.raw = private_arg.style_set;
+
+ return {
+ .min_players = std::max(s8{1}, header.player_count_min),
+ .max_players = header.player_count_max,
+ .keep_controllers_connected = header.enable_take_over_connection,
+ .enable_single_mode = header.enable_single_mode,
+ .enable_border_color = header.enable_identification_color,
+ .border_colors = std::move(identification_colors),
+ .enable_explain_text = enable_text,
+ .explain_text = std::move(text),
+ .allow_pro_controller = npad_style_set.fullkey == 1,
+ .allow_handheld = npad_style_set.handheld == 1,
+ .allow_dual_joycons = npad_style_set.joycon_dual == 1,
+ .allow_left_joycon = npad_style_set.joycon_left == 1,
+ .allow_right_joycon = npad_style_set.joycon_right == 1,
+ };
+}
+
+Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
+
+Controller::~Controller() = default;
+
+void Controller::Initialize() {
+ Applet::Initialize();
+
+ LOG_INFO(Service_HID, "Initializing Controller Applet.");
+
+ LOG_DEBUG(Service_HID,
+ "Initializing Applet with common_args: arg_version={}, lib_version={}, "
+ "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
+ common_args.arguments_version, common_args.library_version,
+ common_args.play_startup_sound, common_args.size, common_args.system_tick,
+ common_args.theme_color);
+
+ controller_applet_version = ControllerAppletVersion{common_args.library_version};
+
+ const auto private_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(private_arg_storage != nullptr);
+
+ const auto& private_arg = private_arg_storage->GetData();
+ ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate));
+
+ std::memcpy(&controller_private_arg, private_arg.data(), private_arg.size());
+ ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate),
+ "Unknown ControllerSupportArgPrivate revision={} with size={}",
+ controller_applet_version, controller_private_arg.arg_private_size);
+
+ // Some games such as Cave Story+ set invalid values for the ControllerSupportMode.
+ // Defer to arg_size to set the ControllerSupportMode.
+ if (controller_private_arg.mode >= ControllerSupportMode::MaxControllerSupportMode) {
+ switch (controller_private_arg.arg_size) {
+ case sizeof(ControllerSupportArgOld):
+ case sizeof(ControllerSupportArgNew):
+ controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport;
+ break;
+ case sizeof(ControllerUpdateFirmwareArg):
+ controller_private_arg.mode = ControllerSupportMode::ShowControllerFirmwareUpdate;
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown ControllerPrivateArg mode={} with arg_size={}",
+ controller_private_arg.mode, controller_private_arg.arg_size);
+ controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport;
+ break;
+ }
+ }
+
+ // Some games such as Cave Story+ set invalid values for the ControllerSupportCaller.
+ // This is always 0 (Application) except with ShowControllerFirmwareUpdateForSystem.
+ if (controller_private_arg.caller >= ControllerSupportCaller::MaxControllerSupportCaller) {
+ if (controller_private_arg.flag_1 &&
+ controller_private_arg.mode == ControllerSupportMode::ShowControllerFirmwareUpdate) {
+ controller_private_arg.caller = ControllerSupportCaller::System;
+ } else {
+ controller_private_arg.caller = ControllerSupportCaller::Application;
+ }
+ }
+
+ switch (controller_private_arg.mode) {
+ case ControllerSupportMode::ShowControllerSupport:
+ case ControllerSupportMode::ShowControllerStrapGuide: {
+ const auto user_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(user_arg_storage != nullptr);
+
+ const auto& user_arg = user_arg_storage->GetData();
+ switch (controller_applet_version) {
+ case ControllerAppletVersion::Version3:
+ case ControllerAppletVersion::Version4:
+ case ControllerAppletVersion::Version5:
+ ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld));
+ std::memcpy(&controller_user_arg_old, user_arg.data(), user_arg.size());
+ break;
+ case ControllerAppletVersion::Version7:
+ ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew));
+ std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size());
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}",
+ controller_applet_version, controller_private_arg.arg_size);
+ ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew));
+ std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
+ break;
+ }
+ break;
+ }
+ case ControllerSupportMode::ShowControllerFirmwareUpdate: {
+ const auto update_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(update_arg_storage != nullptr);
+
+ const auto& update_arg = update_arg_storage->GetData();
+ ASSERT(update_arg.size() == sizeof(ControllerUpdateFirmwareArg));
+
+ std::memcpy(&controller_update_arg, update_arg.data(), update_arg.size());
+ break;
+ }
+ default: {
+ UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode);
+ break;
+ }
+ }
+}
+
+bool Controller::TransactionComplete() const {
+ return complete;
+}
+
+ResultCode Controller::GetStatus() const {
+ return status;
+}
+
+void Controller::ExecuteInteractive() {
+ UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
+}
+
+void Controller::Execute() {
+ switch (controller_private_arg.mode) {
+ case ControllerSupportMode::ShowControllerSupport: {
+ const auto parameters = [this] {
+ switch (controller_applet_version) {
+ case ControllerAppletVersion::Version3:
+ case ControllerAppletVersion::Version4:
+ case ControllerAppletVersion::Version5:
+ return ConvertToFrontendParameters(
+ controller_private_arg, controller_user_arg_old.header,
+ controller_user_arg_old.enable_explain_text,
+ std::vector<IdentificationColor>(
+ controller_user_arg_old.identification_colors.begin(),
+ controller_user_arg_old.identification_colors.end()),
+ std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(),
+ controller_user_arg_old.explain_text.end()));
+ case ControllerAppletVersion::Version7:
+ default:
+ return ConvertToFrontendParameters(
+ controller_private_arg, controller_user_arg_new.header,
+ controller_user_arg_new.enable_explain_text,
+ std::vector<IdentificationColor>(
+ controller_user_arg_new.identification_colors.begin(),
+ controller_user_arg_new.identification_colors.end()),
+ std::vector<ExplainText>(controller_user_arg_new.explain_text.begin(),
+ controller_user_arg_new.explain_text.end()));
+ }
+ }();
+
+ is_single_mode = parameters.enable_single_mode;
+
+ LOG_DEBUG(Service_HID,
+ "Controller Parameters: min_players={}, max_players={}, "
+ "keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, "
+ "enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, "
+ "allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}",
+ parameters.min_players, parameters.max_players,
+ parameters.keep_controllers_connected, parameters.enable_single_mode,
+ parameters.enable_border_color, parameters.enable_explain_text,
+ parameters.allow_pro_controller, parameters.allow_handheld,
+ parameters.allow_dual_joycons, parameters.allow_left_joycon,
+ parameters.allow_right_joycon);
+
+ frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters);
+ break;
+ }
+ case ControllerSupportMode::ShowControllerStrapGuide:
+ case ControllerSupportMode::ShowControllerFirmwareUpdate:
+ UNIMPLEMENTED_MSG("ControllerSupportMode={} is not implemented",
+ controller_private_arg.mode);
+ ConfigurationComplete();
+ break;
+ default: {
+ ConfigurationComplete();
+ break;
+ }
+ }
+}
+
+void Controller::ConfigurationComplete() {
+ ControllerSupportResultInfo result_info{};
+
+ const auto& players = Settings::values.players.GetValue();
+
+ // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
+ // Otherwise, only count connected players from P1-P8.
+ result_info.player_count =
+ is_single_mode
+ ? 1
+ : static_cast<s8>(std::count_if(players.begin(), players.end() - 2,
+ [](const auto& player) { return player.connected; }));
+
+ result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance(
+ players.begin(), std::find_if(players.begin(), players.end(),
+ [](const auto& player) { return player.connected; })));
+
+ result_info.result = 0;
+
+ LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}",
+ result_info.player_count, result_info.selected_id, result_info.result);
+
+ complete = true;
+ out_data = std::vector<u8>(sizeof(ControllerSupportResultInfo));
+ std::memcpy(out_data.data(), &result_info, out_data.size());
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
+ broker.SignalStateChanged();
+}
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h
new file mode 100644
index 000000000..d4c9da7b1
--- /dev/null
+++ b/src/core/hle/service/am/applets/controller.h
@@ -0,0 +1,136 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "core/hle/service/am/applets/applets.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::AM::Applets {
+
+using IdentificationColor = std::array<u8, 4>;
+using ExplainText = std::array<char, 0x81>;
+
+enum class ControllerAppletVersion : u32_le {
+ Version3 = 0x3, // 1.0.0 - 2.3.0
+ Version4 = 0x4, // 3.0.0 - 5.1.0
+ Version5 = 0x5, // 6.0.0 - 7.0.1
+ Version7 = 0x7, // 8.0.0+
+};
+
+enum class ControllerSupportMode : u8 {
+ ShowControllerSupport,
+ ShowControllerStrapGuide,
+ ShowControllerFirmwareUpdate,
+
+ MaxControllerSupportMode,
+};
+
+enum class ControllerSupportCaller : u8 {
+ Application,
+ System,
+
+ MaxControllerSupportCaller,
+};
+
+struct ControllerSupportArgPrivate {
+ u32 arg_private_size{};
+ u32 arg_size{};
+ bool flag_0{};
+ bool flag_1{};
+ ControllerSupportMode mode{};
+ ControllerSupportCaller caller{};
+ u32 style_set{};
+ u32 joy_hold_type{};
+};
+static_assert(sizeof(ControllerSupportArgPrivate) == 0x14,
+ "ControllerSupportArgPrivate has incorrect size.");
+
+struct ControllerSupportArgHeader {
+ s8 player_count_min{};
+ s8 player_count_max{};
+ bool enable_take_over_connection{};
+ bool enable_left_justify{};
+ bool enable_permit_joy_dual{};
+ bool enable_single_mode{};
+ bool enable_identification_color{};
+};
+static_assert(sizeof(ControllerSupportArgHeader) == 0x7,
+ "ControllerSupportArgHeader has incorrect size.");
+
+// LibraryAppletVersion 0x3, 0x4, 0x5
+struct ControllerSupportArgOld {
+ ControllerSupportArgHeader header{};
+ std::array<IdentificationColor, 4> identification_colors{};
+ bool enable_explain_text{};
+ std::array<ExplainText, 4> explain_text{};
+};
+static_assert(sizeof(ControllerSupportArgOld) == 0x21C,
+ "ControllerSupportArgOld has incorrect size.");
+
+// LibraryAppletVersion 0x7
+struct ControllerSupportArgNew {
+ ControllerSupportArgHeader header{};
+ std::array<IdentificationColor, 8> identification_colors{};
+ bool enable_explain_text{};
+ std::array<ExplainText, 8> explain_text{};
+};
+static_assert(sizeof(ControllerSupportArgNew) == 0x430,
+ "ControllerSupportArgNew has incorrect size.");
+
+struct ControllerUpdateFirmwareArg {
+ bool enable_force_update{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(ControllerUpdateFirmwareArg) == 0x4,
+ "ControllerUpdateFirmwareArg has incorrect size.");
+
+struct ControllerSupportResultInfo {
+ s8 player_count{};
+ INSERT_PADDING_BYTES(3);
+ u32 selected_id{};
+ u32 result{};
+};
+static_assert(sizeof(ControllerSupportResultInfo) == 0xC,
+ "ControllerSupportResultInfo has incorrect size.");
+
+class Controller final : public Applet {
+public:
+ explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_);
+ ~Controller() override;
+
+ void Initialize() override;
+
+ bool TransactionComplete() const override;
+ ResultCode GetStatus() const override;
+ void ExecuteInteractive() override;
+ void Execute() override;
+
+ void ConfigurationComplete();
+
+private:
+ const Core::Frontend::ControllerApplet& frontend;
+ Core::System& system;
+
+ ControllerAppletVersion controller_applet_version;
+ ControllerSupportArgPrivate controller_private_arg;
+ ControllerSupportArgOld controller_user_arg_old;
+ ControllerSupportArgNew controller_user_arg_new;
+ ControllerUpdateFirmwareArg controller_update_arg;
+ bool complete{false};
+ ResultCode status{RESULT_SUCCESS};
+ bool is_single_mode{false};
+ std::vector<u8> out_data;
+};
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp
index f12fd7f89..0c8b632e8 100644
--- a/src/core/hle/service/am/applets/error.cpp
+++ b/src/core/hle/service/am/applets/error.cpp
@@ -20,9 +20,9 @@ namespace Service::AM::Applets {
struct ShowError {
u8 mode;
bool jump;
- INSERT_UNION_PADDING_BYTES(4);
+ INSERT_PADDING_BYTES_NOINIT(4);
bool use_64bit_error_code;
- INSERT_UNION_PADDING_BYTES(1);
+ INSERT_PADDING_BYTES_NOINIT(1);
u64 error_code_64;
u32 error_code_32;
};
@@ -32,7 +32,7 @@ static_assert(sizeof(ShowError) == 0x14, "ShowError has incorrect size.");
struct ShowErrorRecord {
u8 mode;
bool jump;
- INSERT_UNION_PADDING_BYTES(6);
+ INSERT_PADDING_BYTES_NOINIT(6);
u64 error_code_64;
u64 posix_time;
};
@@ -41,7 +41,7 @@ static_assert(sizeof(ShowErrorRecord) == 0x18, "ShowErrorRecord has incorrect si
struct SystemErrorArg {
u8 mode;
bool jump;
- INSERT_UNION_PADDING_BYTES(6);
+ INSERT_PADDING_BYTES_NOINIT(6);
u64 error_code_64;
std::array<char, 8> language_code;
std::array<char, 0x800> main_text;
@@ -52,7 +52,7 @@ static_assert(sizeof(SystemErrorArg) == 0x1018, "SystemErrorArg has incorrect si
struct ApplicationErrorArg {
u8 mode;
bool jump;
- INSERT_UNION_PADDING_BYTES(6);
+ INSERT_PADDING_BYTES_NOINIT(6);
u32 error_code;
std::array<char, 8> language_code;
std::array<char, 0x800> main_text;
@@ -87,7 +87,7 @@ ResultCode Decode64BitError(u64 error) {
} // Anonymous namespace
Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_)
- : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
+ : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
Error::~Error() = default;
@@ -125,7 +125,7 @@ void Error::Initialize() {
error_code = Decode64BitError(args->error_record.error_code_64);
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", static_cast<u8>(mode));
+ UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode);
}
}
@@ -179,14 +179,14 @@ void Error::Execute() {
error_code, std::chrono::seconds{args->error_record.posix_time}, callback);
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", static_cast<u8>(mode));
+ UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode);
DisplayCompleted();
}
}
void Error::DisplayCompleted() {
complete = true;
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{}));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{}));
broker.SignalStateChanged();
}
diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp
index 104501ac5..4d1df5cbe 100644
--- a/src/core/hle/service/am/applets/general_backend.cpp
+++ b/src/core/hle/service/am/applets/general_backend.cpp
@@ -38,7 +38,7 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix)
}
Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_)
- : Applet{system_.Kernel()}, frontend(frontend_) {}
+ : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
Auth::~Auth() = default;
@@ -90,7 +90,7 @@ void Auth::Execute() {
const auto unimplemented_log = [this] {
UNIMPLEMENTED_MSG("Unimplemented Auth applet type for type={:08X}, arg0={:02X}, "
"arg1={:02X}, arg2={:02X}",
- static_cast<u32>(type), arg0, arg1, arg2);
+ type, arg0, arg1, arg2);
};
switch (type) {
@@ -135,8 +135,8 @@ void Auth::Execute() {
}
}
-void Auth::AuthFinished(bool successful) {
- this->successful = successful;
+void Auth::AuthFinished(bool is_successful) {
+ successful = is_successful;
struct Return {
ResultCode result_code;
@@ -148,12 +148,12 @@ void Auth::AuthFinished(bool successful) {
std::vector<u8> out(sizeof(Return));
std::memcpy(out.data(), &return_, sizeof(Return));
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(out)));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out)));
broker.SignalStateChanged();
}
PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_)
- : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
+ : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
PhotoViewer::~PhotoViewer() = default;
@@ -193,17 +193,17 @@ void PhotoViewer::Execute() {
frontend.ShowAllPhotos(callback);
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", static_cast<u8>(mode));
+ UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode);
}
}
void PhotoViewer::ViewFinished() {
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{}));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{}));
broker.SignalStateChanged();
}
StubApplet::StubApplet(Core::System& system_, AppletId id_)
- : Applet{system_.Kernel()}, id(id_), system{system_} {}
+ : Applet{system_.Kernel()}, id{id_}, system{system_} {}
StubApplet::~StubApplet() = default;
@@ -234,8 +234,9 @@ void StubApplet::ExecuteInteractive() {
LOG_WARNING(Service_AM, "called (STUBBED)");
LogCurrentStorage(broker, "ExecuteInteractive");
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000)));
- broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000)));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>(0x1000)));
+ broker.PushInteractiveDataFromApplet(
+ std::make_shared<IStorage>(system, std::vector<u8>(0x1000)));
broker.SignalStateChanged();
}
@@ -243,8 +244,9 @@ void StubApplet::Execute() {
LOG_WARNING(Service_AM, "called (STUBBED)");
LogCurrentStorage(broker, "Execute");
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000)));
- broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::vector<u8>(0x1000)));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>(0x1000)));
+ broker.PushInteractiveDataFromApplet(
+ std::make_shared<IStorage>(system, std::vector<u8>(0x1000)));
broker.SignalStateChanged();
}
diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h
index cfa2df369..ba76ae3d3 100644
--- a/src/core/hle/service/am/applets/general_backend.h
+++ b/src/core/hle/service/am/applets/general_backend.h
@@ -29,10 +29,11 @@ public:
void ExecuteInteractive() override;
void Execute() override;
- void AuthFinished(bool successful = true);
+ void AuthFinished(bool is_successful = true);
private:
Core::Frontend::ParentalControlsApplet& frontend;
+ Core::System& system;
bool complete = false;
bool successful = false;
diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp
index 70cc23552..77fba16c7 100644
--- a/src/core/hle/service/am/applets/profile_select.cpp
+++ b/src/core/hle/service/am/applets/profile_select.cpp
@@ -17,7 +17,7 @@ constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
ProfileSelect::ProfileSelect(Core::System& system_,
const Core::Frontend::ProfileSelectApplet& frontend_)
- : Applet{system_.Kernel()}, frontend(frontend_) {}
+ : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
ProfileSelect::~ProfileSelect() = default;
@@ -50,7 +50,7 @@ void ProfileSelect::ExecuteInteractive() {
void ProfileSelect::Execute() {
if (complete) {
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data)));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data)));
return;
}
@@ -71,7 +71,7 @@ void ProfileSelect::SelectionComplete(std::optional<Common::UUID> uuid) {
final_data = std::vector<u8>(sizeof(UserSelectionOutput));
std::memcpy(final_data.data(), &output, final_data.size());
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data)));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data)));
broker.SignalStateChanged();
}
diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h
index 16364ead7..648d33a24 100644
--- a/src/core/hle/service/am/applets/profile_select.h
+++ b/src/core/hle/service/am/applets/profile_select.h
@@ -53,6 +53,7 @@ private:
bool complete = false;
ResultCode status = RESULT_SUCCESS;
std::vector<u8> final_data;
+ Core::System& system;
};
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index bdeb0737a..79b209c6b 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -53,7 +53,7 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
SoftwareKeyboard::SoftwareKeyboard(Core::System& system_,
const Core::Frontend::SoftwareKeyboardApplet& frontend_)
- : Applet{system_.Kernel()}, frontend(frontend_) {}
+ : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {}
SoftwareKeyboard::~SoftwareKeyboard() = default;
@@ -121,8 +121,12 @@ void SoftwareKeyboard::ExecuteInteractive() {
std::memcpy(&request, data.data(), sizeof(Request));
switch (request) {
+ case Request::Finalize:
+ complete = true;
+ broker.SignalStateChanged();
+ break;
case Request::Calc: {
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::vector<u8>{1}));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::vector<u8>{1}));
broker.SignalStateChanged();
break;
}
@@ -135,7 +139,7 @@ void SoftwareKeyboard::ExecuteInteractive() {
void SoftwareKeyboard::Execute() {
if (complete) {
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(final_data)));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(final_data)));
broker.SignalStateChanged();
return;
}
@@ -179,15 +183,17 @@ void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
final_data = output_main;
if (complete) {
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(output_main)));
+ broker.PushNormalDataFromApplet(
+ std::make_shared<IStorage>(system, std::move(output_main)));
broker.SignalStateChanged();
} else {
- broker.PushInteractiveDataFromApplet(std::make_shared<IStorage>(std::move(output_sub)));
+ broker.PushInteractiveDataFromApplet(
+ std::make_shared<IStorage>(system, std::move(output_sub)));
}
} else {
output_main[0] = 1;
complete = true;
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(output_main)));
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(output_main)));
broker.SignalStateChanged();
}
}
diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h
index 5a3824b5a..1d260fef8 100644
--- a/src/core/hle/service/am/applets/software_keyboard.h
+++ b/src/core/hle/service/am/applets/software_keyboard.h
@@ -80,6 +80,7 @@ private:
bool complete = false;
bool is_inline = false;
std::vector<u8> final_data;
+ Core::System& system;
};
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index efe595c4f..2ab420789 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -1,558 +1,478 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <array>
-#include <cstring>
-#include <vector>
-
#include "common/assert.h"
-#include "common/common_funcs.h"
#include "common/common_paths.h"
#include "common/file_util.h"
-#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/system_archive.h"
-#include "core/file_sys/vfs_types.h"
-#include "core/frontend/applets/general_frontend.h"
+#include "core/file_sys/vfs_vector.h"
#include "core/frontend/applets/web_browser.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/result.h"
+#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/web_browser.h"
#include "core/hle/service/filesystem/filesystem.h"
-#include "core/loader/loader.h"
+#include "core/hle/service/ns/pl_u.h"
namespace Service::AM::Applets {
-enum class WebArgTLVType : u16 {
- InitialURL = 0x1,
- ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name.
- CallbackURL = 0x3,
- CallbackableURL = 0x4,
- ApplicationID = 0x5,
- DocumentPath = 0x6,
- DocumentKind = 0x7,
- SystemDataID = 0x8,
- ShareStartPage = 0x9,
- Whitelist = 0xA,
- News = 0xB,
- UserID = 0xE,
- AlbumEntry0 = 0xF,
- ScreenShotEnabled = 0x10,
- EcClientCertEnabled = 0x11,
- Unk12 = 0x12,
- PlayReportEnabled = 0x13,
- Unk14 = 0x14,
- Unk15 = 0x15,
- BootDisplayKind = 0x17,
- BackgroundKind = 0x18,
- FooterEnabled = 0x19,
- PointerEnabled = 0x1A,
- LeftStickMode = 0x1B,
- KeyRepeatFrame1 = 0x1C,
- KeyRepeatFrame2 = 0x1D,
- BootAsMediaPlayerInv = 0x1E,
- DisplayUrlKind = 0x1F,
- BootAsMediaPlayer = 0x21,
- ShopJumpEnabled = 0x22,
- MediaAutoPlayEnabled = 0x23,
- LobbyParameter = 0x24,
- ApplicationAlbumEntry = 0x26,
- JsExtensionEnabled = 0x27,
- AdditionalCommentText = 0x28,
- TouchEnabledOnContents = 0x29,
- UserAgentAdditionalString = 0x2A,
- AdditionalMediaData0 = 0x2B,
- MediaPlayerAutoCloseEnabled = 0x2C,
- PageCacheEnabled = 0x2D,
- WebAudioEnabled = 0x2E,
- Unk2F = 0x2F,
- YouTubeVideoWhitelist = 0x31,
- FooterFixedKind = 0x32,
- PageFadeEnabled = 0x33,
- MediaCreatorApplicationRatingAge = 0x34,
- BootLoadingIconEnabled = 0x35,
- PageScrollIndicationEnabled = 0x36,
- MediaPlayerSpeedControlEnabled = 0x37,
- AlbumEntry1 = 0x38,
- AlbumEntry2 = 0x39,
- AlbumEntry3 = 0x3A,
- AdditionalMediaData1 = 0x3B,
- AdditionalMediaData2 = 0x3C,
- AdditionalMediaData3 = 0x3D,
- BootFooterButton = 0x3E,
- OverrideWebAudioVolume = 0x3F,
- OverrideMediaAudioVolume = 0x40,
- BootMode = 0x41,
- WebSessionEnabled = 0x42,
-};
-
-enum class ShimKind : u32 {
- Shop = 1,
- Login = 2,
- Offline = 3,
- Share = 4,
- Web = 5,
- Wifi = 6,
- Lobby = 7,
-};
-
-enum class ShopWebTarget {
- ApplicationInfo,
- AddOnContentList,
- SubscriptionList,
- ConsumableItemList,
- Home,
- Settings,
-};
-
namespace {
-constexpr std::size_t SHIM_KIND_COUNT = 0x8;
-
-struct WebArgHeader {
- u16 count;
- INSERT_PADDING_BYTES(2);
- ShimKind kind;
-};
-static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
-
-struct WebArgTLV {
- WebArgTLVType type;
- u16 size;
- u32 offset;
-};
-static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size.");
-
-struct WebCommonReturnValue {
- u32 result_code;
- INSERT_PADDING_BYTES(0x4);
- std::array<char, 0x1000> last_url;
- u64 last_url_size;
-};
-static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
-
-struct WebWifiPageArg {
- INSERT_PADDING_BYTES(4);
- std::array<char, 0x100> connection_test_url;
- std::array<char, 0x400> initial_url;
- std::array<u8, 0x10> nifm_network_uuid;
- u32 nifm_requirement;
-};
-static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size.");
-
-struct WebWifiReturnValue {
- INSERT_PADDING_BYTES(4);
- u32 result;
-};
-static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size.");
-
-enum class OfflineWebSource : u32 {
- OfflineHtmlPage = 0x1,
- ApplicationLegalInformation = 0x2,
- SystemDataPage = 0x3,
-};
-
-std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) {
- if (arg.size() < sizeof(WebArgHeader))
- return {};
-
- WebArgHeader header{};
- std::memcpy(&header, arg.data(), sizeof(WebArgHeader));
-
- std::map<WebArgTLVType, std::vector<u8>> out;
- u64 offset = sizeof(WebArgHeader);
- for (std::size_t i = 0; i < header.count; ++i) {
- if (arg.size() < (offset + sizeof(WebArgTLV)))
- return out;
+template <typename T>
+void ParseRawValue(T& value, const std::vector<u8>& data) {
+ static_assert(std::is_trivially_copyable_v<T>,
+ "It's undefined behavior to use memcpy with non-trivially copyable objects");
+ std::memcpy(&value, data.data(), data.size());
+}
- WebArgTLV tlv{};
- std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV));
- offset += sizeof(WebArgTLV);
+template <typename T>
+T ParseRawValue(const std::vector<u8>& data) {
+ T value;
+ ParseRawValue(value, data);
+ return value;
+}
- offset += tlv.offset;
- if (arg.size() < (offset + tlv.size))
- return out;
+std::string ParseStringValue(const std::vector<u8>& data) {
+ return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
+ data.size());
+}
- std::vector<u8> data(tlv.size);
- std::memcpy(data.data(), arg.data() + offset, tlv.size);
- offset += tlv.size;
+std::string GetMainURL(const std::string& url) {
+ const auto index = url.find('?');
- out.insert_or_assign(tlv.type, data);
+ if (index == std::string::npos) {
+ return url;
}
- return out;
+ return url.substr(0, index);
}
-FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id,
- FileSys::ContentRecordType type) {
- const auto& installed{system.GetContentProvider()};
- const auto res = installed.GetEntry(title_id, type);
+WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
+ std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
- if (res != nullptr) {
- return res->GetRomFS();
+ if (web_arg.size() == sizeof(WebArgHeader)) {
+ return {};
}
- if (type == FileSys::ContentRecordType::Data) {
- return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
+ WebArgInputTLVMap input_tlv_map;
+
+ u64 current_offset = sizeof(WebArgHeader);
+
+ for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) {
+ if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) {
+ return input_tlv_map;
+ }
+
+ WebArgInputTLV input_tlv;
+ std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV));
+
+ current_offset += sizeof(WebArgInputTLV);
+
+ if (web_arg.size() < current_offset + input_tlv.arg_data_size) {
+ return input_tlv_map;
+ }
+
+ std::vector<u8> data(input_tlv.arg_data_size);
+ std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size);
+
+ current_offset += input_tlv.arg_data_size;
+
+ input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data));
}
- return nullptr;
+ return input_tlv_map;
}
-} // Anonymous namespace
+FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
+ FileSys::ContentRecordType nca_type) {
+ if (nca_type == FileSys::ContentRecordType::Data) {
+ const auto nca =
+ system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type);
+
+ if (nca == nullptr) {
+ LOG_ERROR(Service_AM,
+ "NCA of type={} with title_id={:016X} is not found in the System NAND!",
+ nca_type, title_id);
+ return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
+ }
-WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
- Core::Frontend::ECommerceApplet* frontend_e_commerce_)
- : Applet{system_.Kernel()}, frontend(frontend_),
- frontend_e_commerce(frontend_e_commerce_), system{system_} {}
+ return nca->GetRomFS();
+ } else {
+ const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type);
-WebBrowser::~WebBrowser() = default;
+ if (nca == nullptr) {
+ LOG_ERROR(Service_AM,
+ "NCA of type={} with title_id={:016X} is not found in the ContentProvider!",
+ nca_type, title_id);
+ return nullptr;
+ }
-void WebBrowser::Initialize() {
- Applet::Initialize();
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
- complete = false;
- temporary_dir.clear();
- filename.clear();
- status = RESULT_SUCCESS;
+ return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type);
+ }
+}
- const auto web_arg_storage = broker.PopNormalDataToApplet();
- ASSERT(web_arg_storage != nullptr);
- const auto& web_arg = web_arg_storage->GetData();
+void ExtractSharedFonts(Core::System& system) {
+ static constexpr std::array<const char*, 7> DECRYPTED_SHARED_FONTS{
+ "FontStandard.ttf",
+ "FontChineseSimplified.ttf",
+ "FontExtendedChineseSimplified.ttf",
+ "FontChineseTraditional.ttf",
+ "FontKorean.ttf",
+ "FontNintendoExtended.ttf",
+ "FontNintendoExtended2.ttf",
+ };
- ASSERT(web_arg.size() >= 0x8);
- std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind));
+ for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) {
+ const auto fonts_dir = Common::FS::SanitizePath(
+ fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
+ Common::FS::DirectorySeparator::PlatformDefault);
- args = GetWebArguments(web_arg);
+ const auto font_file_path =
+ Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]),
+ Common::FS::DirectorySeparator::PlatformDefault);
- InitializeInternal();
-}
+ if (Common::FS::Exists(font_file_path)) {
+ continue;
+ }
-bool WebBrowser::TransactionComplete() const {
- return complete;
-}
+ const auto font = NS::SHARED_FONTS[i];
+ const auto font_title_id = static_cast<u64>(font.first);
-ResultCode WebBrowser::GetStatus() const {
- return status;
-}
+ const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry(
+ font_title_id, FileSys::ContentRecordType::Data);
-void WebBrowser::ExecuteInteractive() {
- UNIMPLEMENTED_MSG("Unexpected interactive data recieved!");
-}
+ FileSys::VirtualFile romfs;
-void WebBrowser::Execute() {
- if (complete) {
- return;
- }
+ if (!nca) {
+ romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id);
+ } else {
+ romfs = nca->GetRomFS();
+ }
- if (status != RESULT_SUCCESS) {
- complete = true;
+ if (!romfs) {
+ LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!",
+ font_title_id);
+ continue;
+ }
- // This is a workaround in order not to softlock yuzu when an error happens during the
- // webapplet init. In order to avoid an svcBreak, the status is set to RESULT_SUCCESS
- Finalize();
- status = RESULT_SUCCESS;
+ const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
- return;
- }
+ if (!extracted_romfs) {
+ LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!",
+ font_title_id);
+ continue;
+ }
- ExecuteInternal();
-}
+ const auto font_file = extracted_romfs->GetFile(font.second);
-void WebBrowser::UnpackRomFS() {
- if (unpacked)
- return;
+ if (!font_file) {
+ LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!",
+ font_title_id, font.second);
+ continue;
+ }
- ASSERT(offline_romfs != nullptr);
- const auto dir =
- FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
- const auto& vfs{system.GetFilesystem()};
- const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
- FileSys::VfsRawCopyD(dir, temp_dir);
+ std::vector<u32> font_data_u32(font_file->GetSize() / sizeof(u32));
+ font_file->ReadBytes<u32>(font_data_u32.data(), font_file->GetSize());
- unpacked = true;
-}
+ std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
+ Common::swap32);
-void WebBrowser::Finalize() {
- complete = true;
+ std::vector<u8> decrypted_data(font_file->GetSize() - 8);
- WebCommonReturnValue out{};
- out.result_code = 0;
- out.last_url_size = 0;
+ NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data);
- std::vector<u8> data(sizeof(WebCommonReturnValue));
- std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue));
+ FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>(
+ std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]);
- broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(data)));
- broker.SignalStateChanged();
+ const auto temp_dir =
+ system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite);
+
+ const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]);
- if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) {
- Common::FS::DeleteDirRecursively(temporary_dir);
+ FileSys::VfsRawCopy(decrypted_font, out_file);
}
}
-void WebBrowser::InitializeInternal() {
- using WebAppletInitializer = void (WebBrowser::*)();
+} // namespace
- constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{
- nullptr, &WebBrowser::InitializeShop,
- nullptr, &WebBrowser::InitializeOffline,
- nullptr, nullptr,
- nullptr, nullptr,
- };
+WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_)
+ : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
- const auto index = static_cast<u32>(kind);
+WebBrowser::~WebBrowser() = default;
- if (index > functions.size() || functions[index] == nullptr) {
- LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
- return;
- }
+void WebBrowser::Initialize() {
+ Applet::Initialize();
- const auto function = functions[index];
- (this->*function)();
-}
+ LOG_INFO(Service_AM, "Initializing Web Browser Applet.");
-void WebBrowser::ExecuteInternal() {
- using WebAppletExecutor = void (WebBrowser::*)();
+ LOG_DEBUG(Service_AM,
+ "Initializing Applet with common_args: arg_version={}, lib_version={}, "
+ "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
+ common_args.arguments_version, common_args.library_version,
+ common_args.play_startup_sound, common_args.size, common_args.system_tick,
+ common_args.theme_color);
- constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{
- nullptr, &WebBrowser::ExecuteShop,
- nullptr, &WebBrowser::ExecuteOffline,
- nullptr, nullptr,
- nullptr, nullptr,
- };
+ web_applet_version = WebAppletVersion{common_args.library_version};
- const auto index = static_cast<u32>(kind);
+ const auto web_arg_storage = broker.PopNormalDataToApplet();
+ ASSERT(web_arg_storage != nullptr);
- if (index > functions.size() || functions[index] == nullptr) {
- LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
- return;
- }
+ const auto& web_arg = web_arg_storage->GetData();
+ ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; });
- const auto function = functions[index];
- (this->*function)();
-}
+ web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header);
-void WebBrowser::InitializeShop() {
- if (frontend_e_commerce == nullptr) {
- LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!");
- status = RESULT_UNKNOWN;
- return;
- }
+ LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}",
+ web_arg_header.total_tlv_entries, web_arg_header.shim_kind);
- const auto user_id_data = args.find(WebArgTLVType::UserID);
+ ExtractSharedFonts(system);
- user_id = std::nullopt;
- if (user_id_data != args.end()) {
- user_id = u128{};
- std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128));
+ switch (web_arg_header.shim_kind) {
+ case ShimKind::Shop:
+ InitializeShop();
+ break;
+ case ShimKind::Login:
+ InitializeLogin();
+ break;
+ case ShimKind::Offline:
+ InitializeOffline();
+ break;
+ case ShimKind::Share:
+ InitializeShare();
+ break;
+ case ShimKind::Web:
+ InitializeWeb();
+ break;
+ case ShimKind::Wifi:
+ InitializeWifi();
+ break;
+ case ShimKind::Lobby:
+ InitializeLobby();
+ break;
+ default:
+ UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
+ break;
}
+}
- const auto url = args.find(WebArgTLVType::ShopArgumentsURL);
+bool WebBrowser::TransactionComplete() const {
+ return complete;
+}
- if (url == args.end()) {
- LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!");
- status = RESULT_UNKNOWN;
- return;
- }
+ResultCode WebBrowser::GetStatus() const {
+ return status;
+}
- std::vector<std::string> split_query;
- Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(url->second.data()), url->second.size()),
- '?', split_query);
-
- // 2 -> Main URL '?' Query Parameters
- // Less is missing info, More is malformed
- if (split_query.size() != 2) {
- LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed");
- status = RESULT_UNKNOWN;
- return;
- }
+void WebBrowser::ExecuteInteractive() {
+ UNIMPLEMENTED_MSG("WebSession is not implemented");
+}
- std::vector<std::string> queries;
- Common::SplitString(split_query[1], '&', queries);
+void WebBrowser::Execute() {
+ switch (web_arg_header.shim_kind) {
+ case ShimKind::Shop:
+ ExecuteShop();
+ break;
+ case ShimKind::Login:
+ ExecuteLogin();
+ break;
+ case ShimKind::Offline:
+ ExecuteOffline();
+ break;
+ case ShimKind::Share:
+ ExecuteShare();
+ break;
+ case ShimKind::Web:
+ ExecuteWeb();
+ break;
+ case ShimKind::Wifi:
+ ExecuteWifi();
+ break;
+ case ShimKind::Lobby:
+ ExecuteLobby();
+ break;
+ default:
+ UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
+ WebBrowserExit(WebExitReason::EndButtonPressed);
+ break;
+ }
+}
- const auto split_single_query =
- [](const std::string& in) -> std::pair<std::string, std::string> {
- const auto index = in.find('=');
- if (index == std::string::npos || index == in.size() - 1) {
- return {in, ""};
- }
+void WebBrowser::ExtractOfflineRomFS() {
+ LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir);
- return {in.substr(0, index), in.substr(index + 1)};
- };
+ const auto extracted_romfs_dir =
+ FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
- std::transform(queries.begin(), queries.end(),
- std::inserter(shop_query, std::next(shop_query.begin())), split_single_query);
+ const auto temp_dir =
+ system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite);
- const auto scene = shop_query.find("scene");
+ FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir);
+}
- if (scene == shop_query.end()) {
- LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!");
- status = RESULT_UNKNOWN;
- return;
+void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
+ if ((web_arg_header.shim_kind == ShimKind::Share &&
+ web_applet_version >= WebAppletVersion::Version196608) ||
+ (web_arg_header.shim_kind == ShimKind::Web &&
+ web_applet_version >= WebAppletVersion::Version524288)) {
+ // TODO: Push Output TLVs instead of a WebCommonReturnValue
}
- const std::map<std::string, ShopWebTarget, std::less<>> target_map{
- {"product_detail", ShopWebTarget::ApplicationInfo},
- {"aocs", ShopWebTarget::AddOnContentList},
- {"subscriptions", ShopWebTarget::SubscriptionList},
- {"consumption", ShopWebTarget::ConsumableItemList},
- {"settings", ShopWebTarget::Settings},
- {"top", ShopWebTarget::Home},
- };
+ WebCommonReturnValue web_common_return_value;
- const auto target = target_map.find(scene->second);
- if (target == target_map.end()) {
- LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second);
- status = RESULT_UNKNOWN;
- return;
- }
+ web_common_return_value.exit_reason = exit_reason;
+ std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size());
+ web_common_return_value.last_url_size = last_url.size();
- shop_web_target = target->second;
+ LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}",
+ exit_reason, last_url, last_url.size());
- const auto title_id_data = shop_query.find("dst_app_id");
- if (title_id_data != shop_query.end()) {
- title_id = std::stoull(title_id_data->second, nullptr, 0x10);
- }
+ complete = true;
+ std::vector<u8> out_data(sizeof(WebCommonReturnValue));
+ std::memcpy(out_data.data(), &web_common_return_value, out_data.size());
+ broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
+ broker.SignalStateChanged();
+}
- const auto mode_data = shop_query.find("mode");
- if (mode_data != shop_query.end()) {
- shop_full_display = mode_data->second == "full";
- }
+bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const {
+ return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end();
}
-void WebBrowser::InitializeOffline() {
- if (args.find(WebArgTLVType::DocumentPath) == args.end() ||
- args.find(WebArgTLVType::DocumentKind) == args.end() ||
- args.find(WebArgTLVType::ApplicationID) == args.end()) {
- status = RESULT_UNKNOWN;
- LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!");
+std::optional<std::vector<u8>> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) {
+ const auto map_it = web_arg_input_tlv_map.find(input_tlv_type);
+
+ if (map_it == web_arg_input_tlv_map.end()) {
+ return std::nullopt;
}
- const auto url_data = args[WebArgTLVType::DocumentPath];
- filename = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(url_data.data()), url_data.size());
+ return map_it->second;
+}
- OfflineWebSource source;
- ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4);
- std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource));
+void WebBrowser::InitializeShop() {}
- constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{
- "manual",
- "legal",
- "system",
- };
+void WebBrowser::InitializeLogin() {}
+
+void WebBrowser::InitializeOffline() {
+ const auto document_path =
+ ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value());
+
+ const auto document_kind =
+ ParseRawValue<DocumentKind>(GetInputTLVData(WebArgInputTLVType::DocumentKind).value());
+
+ std::string additional_paths;
- temporary_dir =
- Common::FS::SanitizePath(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
- "web_applet_" + WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
- Common::FS::DirectorySeparator::PlatformDefault);
- Common::FS::DeleteDirRecursively(temporary_dir);
-
- u64 title_id = 0; // 0 corresponds to current process
- ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
- std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64));
- FileSys::ContentRecordType type = FileSys::ContentRecordType::Data;
-
- switch (source) {
- case OfflineWebSource::OfflineHtmlPage:
- // While there is an AppID TLV field, in official SW this is always ignored.
- title_id = 0;
- type = FileSys::ContentRecordType::HtmlDocument;
+ switch (document_kind) {
+ case DocumentKind::OfflineHtmlPage:
+ default:
+ title_id = system.CurrentProcess()->GetTitleID();
+ nca_type = FileSys::ContentRecordType::HtmlDocument;
+ additional_paths = "html-document";
break;
- case OfflineWebSource::ApplicationLegalInformation:
- type = FileSys::ContentRecordType::LegalInformation;
+ case DocumentKind::ApplicationLegalInformation:
+ title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::ApplicationID).value());
+ nca_type = FileSys::ContentRecordType::LegalInformation;
break;
- case OfflineWebSource::SystemDataPage:
- type = FileSys::ContentRecordType::Data;
+ case DocumentKind::SystemDataPage:
+ title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::SystemDataID).value());
+ nca_type = FileSys::ContentRecordType::Data;
break;
}
- if (title_id == 0) {
- title_id = system.CurrentProcess()->GetTitleID();
- }
+ static constexpr std::array<const char*, 3> RESOURCE_TYPES{
+ "manual",
+ "legal_information",
+ "system_data",
+ };
- offline_romfs = GetApplicationRomFS(system, title_id, type);
- if (offline_romfs == nullptr) {
- status = RESULT_UNKNOWN;
- LOG_ERROR(Service_AM, "Failed to find offline data for request!");
- }
+ offline_cache_dir = Common::FS::SanitizePath(
+ fmt::format("{}/offline_web_applet_{}/{:016X}",
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir),
+ RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id),
+ Common::FS::DirectorySeparator::PlatformDefault);
- std::string path_additional_directory;
- if (source == OfflineWebSource::OfflineHtmlPage) {
- path_additional_directory = std::string(DIR_SEP).append("html-document");
- }
+ offline_document = Common::FS::SanitizePath(
+ fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path),
+ Common::FS::DirectorySeparator::PlatformDefault);
+}
+
+void WebBrowser::InitializeShare() {}
- filename =
- Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
- Common::FS::DirectorySeparator::PlatformDefault);
+void WebBrowser::InitializeWeb() {
+ external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value());
}
+void WebBrowser::InitializeWifi() {}
+
+void WebBrowser::InitializeLobby() {}
+
void WebBrowser::ExecuteShop() {
- const auto callback = [this]() { Finalize(); };
+ LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented");
+ WebBrowserExit(WebExitReason::EndButtonPressed);
+}
- const auto check_optional_parameter = [this](const auto& p) {
- if (!p.has_value()) {
- LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!");
- status = RESULT_UNKNOWN;
- return false;
- }
+void WebBrowser::ExecuteLogin() {
+ LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented");
+ WebBrowserExit(WebExitReason::EndButtonPressed);
+}
- return true;
- };
+void WebBrowser::ExecuteOffline() {
+ const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document),
+ Common::FS::DirectorySeparator::PlatformDefault);
- switch (shop_web_target) {
- case ShopWebTarget::ApplicationInfo:
- if (!check_optional_parameter(title_id))
- return;
- frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id,
- shop_full_display, shop_extra_parameter);
- break;
- case ShopWebTarget::AddOnContentList:
- if (!check_optional_parameter(title_id))
- return;
- frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display);
- break;
- case ShopWebTarget::ConsumableItemList:
- if (!check_optional_parameter(title_id))
- return;
- frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id);
- break;
- case ShopWebTarget::Home:
- if (!check_optional_parameter(user_id))
- return;
- if (!check_optional_parameter(shop_full_display))
- return;
- frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display);
- break;
- case ShopWebTarget::Settings:
- if (!check_optional_parameter(user_id))
- return;
- if (!check_optional_parameter(shop_full_display))
- return;
- frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display);
- break;
- case ShopWebTarget::SubscriptionList:
- if (!check_optional_parameter(title_id))
+ if (!Common::FS::Exists(main_url)) {
+ offline_romfs = GetOfflineRomFS(system, title_id, nca_type);
+
+ if (offline_romfs == nullptr) {
+ LOG_ERROR(Service_AM,
+ "RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id,
+ nca_type);
+ WebBrowserExit(WebExitReason::WindowClosed);
return;
- frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id);
- break;
- default:
- UNREACHABLE();
+ }
}
+
+ LOG_INFO(Service_AM, "Opening offline document at {}", offline_document);
+
+ frontend.OpenLocalWebPage(
+ offline_document, [this] { ExtractOfflineRomFS(); },
+ [this](WebExitReason exit_reason, std::string last_url) {
+ WebBrowserExit(exit_reason, last_url);
+ });
}
-void WebBrowser::ExecuteOffline() {
- frontend.OpenPageLocal(
- filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
+void WebBrowser::ExecuteShare() {
+ LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented");
+ WebBrowserExit(WebExitReason::EndButtonPressed);
+}
+
+void WebBrowser::ExecuteWeb() {
+ LOG_INFO(Service_AM, "Opening external URL at {}", external_url);
+
+ frontend.OpenExternalWebPage(external_url,
+ [this](WebExitReason exit_reason, std::string last_url) {
+ WebBrowserExit(exit_reason, last_url);
+ });
}
+void WebBrowser::ExecuteWifi() {
+ LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented");
+ WebBrowserExit(WebExitReason::EndButtonPressed);
+}
+
+void WebBrowser::ExecuteLobby() {
+ LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented");
+ WebBrowserExit(WebExitReason::EndButtonPressed);
+}
} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h
index 8d4027411..04c274754 100644
--- a/src/core/hle/service/am/applets/web_browser.h
+++ b/src/core/hle/service/am/applets/web_browser.h
@@ -1,28 +1,31 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
-#include <map>
+#include <optional>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
-#include "core/hle/service/am/am.h"
+#include "core/hle/result.h"
#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/am/applets/web_types.h"
namespace Core {
class System;
}
-namespace Service::AM::Applets {
+namespace FileSys {
+enum class ContentRecordType : u8;
+}
-enum class ShimKind : u32;
-enum class ShopWebTarget;
-enum class WebArgTLVType : u16;
+namespace Service::AM::Applets {
class WebBrowser final : public Applet {
public:
- WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
- Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr);
+ WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_);
~WebBrowser() override;
@@ -33,49 +36,50 @@ public:
void ExecuteInteractive() override;
void Execute() override;
- // Callback to be fired when the frontend needs the manual RomFS unpacked to temporary
- // directory. This is a blocking call and may take a while as some manuals can be up to 100MB in
- // size. Attempting to access files at filename before invocation is likely to not work.
- void UnpackRomFS();
+ void ExtractOfflineRomFS();
- // Callback to be fired when the frontend is finished browsing. This will delete the temporary
- // manual RomFS extracted files, so ensure this is only called at actual finalization.
- void Finalize();
+ void WebBrowserExit(WebExitReason exit_reason, std::string last_url = "");
private:
- void InitializeInternal();
- void ExecuteInternal();
+ bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const;
- // Specific initializers for the types of web applets
+ std::optional<std::vector<u8>> GetInputTLVData(WebArgInputTLVType input_tlv_type);
+
+ // Initializers for the various types of browser applets
void InitializeShop();
+ void InitializeLogin();
void InitializeOffline();
+ void InitializeShare();
+ void InitializeWeb();
+ void InitializeWifi();
+ void InitializeLobby();
- // Specific executors for the types of web applets
+ // Executors for the various types of browser applets
void ExecuteShop();
+ void ExecuteLogin();
void ExecuteOffline();
+ void ExecuteShare();
+ void ExecuteWeb();
+ void ExecuteWifi();
+ void ExecuteLobby();
- Core::Frontend::WebBrowserApplet& frontend;
-
- // Extra frontends for specialized functions
- Core::Frontend::ECommerceApplet* frontend_e_commerce;
+ const Core::Frontend::WebBrowserApplet& frontend;
- bool complete = false;
- bool unpacked = false;
- ResultCode status = RESULT_SUCCESS;
+ bool complete{false};
+ ResultCode status{RESULT_SUCCESS};
- ShimKind kind;
- std::map<WebArgTLVType, std::vector<u8>> args;
+ WebAppletVersion web_applet_version;
+ WebExitReason web_exit_reason;
+ WebArgHeader web_arg_header;
+ WebArgInputTLVMap web_arg_input_tlv_map;
+ u64 title_id;
+ FileSys::ContentRecordType nca_type;
+ std::string offline_cache_dir;
+ std::string offline_document;
FileSys::VirtualFile offline_romfs;
- std::string temporary_dir;
- std::string filename;
-
- ShopWebTarget shop_web_target;
- std::map<std::string, std::string, std::less<>> shop_query;
- std::optional<u64> title_id = 0;
- std::optional<u128> user_id;
- std::optional<bool> shop_full_display;
- std::string shop_extra_parameter;
+
+ std::string external_url;
Core::System& system;
};
diff --git a/src/core/hle/service/am/applets/web_types.h b/src/core/hle/service/am/applets/web_types.h
new file mode 100644
index 000000000..419c2bf79
--- /dev/null
+++ b/src/core/hle/service/am/applets/web_types.h
@@ -0,0 +1,178 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <unordered_map>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace Service::AM::Applets {
+
+enum class WebAppletVersion : u32_le {
+ Version0 = 0x0, // Only used by WifiWebAuthApplet
+ Version131072 = 0x20000, // 1.0.0 - 2.3.0
+ Version196608 = 0x30000, // 3.0.0 - 4.1.0
+ Version327680 = 0x50000, // 5.0.0 - 5.1.0
+ Version393216 = 0x60000, // 6.0.0 - 7.0.1
+ Version524288 = 0x80000, // 8.0.0+
+};
+
+enum class ShimKind : u32 {
+ Shop = 1,
+ Login = 2,
+ Offline = 3,
+ Share = 4,
+ Web = 5,
+ Wifi = 6,
+ Lobby = 7,
+};
+
+enum class WebExitReason : u32 {
+ EndButtonPressed = 0,
+ BackButtonPressed = 1,
+ ExitRequested = 2,
+ CallbackURL = 3,
+ WindowClosed = 4,
+ ErrorDialog = 7,
+};
+
+enum class WebArgInputTLVType : u16 {
+ InitialURL = 0x1,
+ CallbackURL = 0x3,
+ CallbackableURL = 0x4,
+ ApplicationID = 0x5,
+ DocumentPath = 0x6,
+ DocumentKind = 0x7,
+ SystemDataID = 0x8,
+ ShareStartPage = 0x9,
+ Whitelist = 0xA,
+ News = 0xB,
+ UserID = 0xE,
+ AlbumEntry0 = 0xF,
+ ScreenShotEnabled = 0x10,
+ EcClientCertEnabled = 0x11,
+ PlayReportEnabled = 0x13,
+ BootDisplayKind = 0x17,
+ BackgroundKind = 0x18,
+ FooterEnabled = 0x19,
+ PointerEnabled = 0x1A,
+ LeftStickMode = 0x1B,
+ KeyRepeatFrame1 = 0x1C,
+ KeyRepeatFrame2 = 0x1D,
+ BootAsMediaPlayerInverted = 0x1E,
+ DisplayURLKind = 0x1F,
+ BootAsMediaPlayer = 0x21,
+ ShopJumpEnabled = 0x22,
+ MediaAutoPlayEnabled = 0x23,
+ LobbyParameter = 0x24,
+ ApplicationAlbumEntry = 0x26,
+ JsExtensionEnabled = 0x27,
+ AdditionalCommentText = 0x28,
+ TouchEnabledOnContents = 0x29,
+ UserAgentAdditionalString = 0x2A,
+ AdditionalMediaData0 = 0x2B,
+ MediaPlayerAutoCloseEnabled = 0x2C,
+ PageCacheEnabled = 0x2D,
+ WebAudioEnabled = 0x2E,
+ YouTubeVideoWhitelist = 0x31,
+ FooterFixedKind = 0x32,
+ PageFadeEnabled = 0x33,
+ MediaCreatorApplicationRatingAge = 0x34,
+ BootLoadingIconEnabled = 0x35,
+ PageScrollIndicatorEnabled = 0x36,
+ MediaPlayerSpeedControlEnabled = 0x37,
+ AlbumEntry1 = 0x38,
+ AlbumEntry2 = 0x39,
+ AlbumEntry3 = 0x3A,
+ AdditionalMediaData1 = 0x3B,
+ AdditionalMediaData2 = 0x3C,
+ AdditionalMediaData3 = 0x3D,
+ BootFooterButton = 0x3E,
+ OverrideWebAudioVolume = 0x3F,
+ OverrideMediaAudioVolume = 0x40,
+ BootMode = 0x41,
+ WebSessionEnabled = 0x42,
+ MediaPlayerOfflineEnabled = 0x43,
+};
+
+enum class WebArgOutputTLVType : u16 {
+ ShareExitReason = 0x1,
+ LastURL = 0x2,
+ LastURLSize = 0x3,
+ SharePostResult = 0x4,
+ PostServiceName = 0x5,
+ PostServiceNameSize = 0x6,
+ PostID = 0x7,
+ PostIDSize = 0x8,
+ MediaPlayerAutoClosedByCompletion = 0x9,
+};
+
+enum class DocumentKind : u32 {
+ OfflineHtmlPage = 1,
+ ApplicationLegalInformation = 2,
+ SystemDataPage = 3,
+};
+
+enum class ShareStartPage : u32 {
+ Default,
+ Settings,
+};
+
+enum class BootDisplayKind : u32 {
+ Default,
+ White,
+ Black,
+};
+
+enum class BackgroundKind : u32 {
+ Default,
+};
+
+enum class LeftStickMode : u32 {
+ Pointer,
+ Cursor,
+};
+
+enum class WebSessionBootMode : u32 {
+ AllForeground,
+ AllForegroundInitiallyHidden,
+};
+
+struct WebArgHeader {
+ u16 total_tlv_entries{};
+ INSERT_PADDING_BYTES(2);
+ ShimKind shim_kind{};
+};
+static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
+
+struct WebArgInputTLV {
+ WebArgInputTLVType input_tlv_type{};
+ u16 arg_data_size{};
+ INSERT_PADDING_WORDS(1);
+};
+static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size.");
+
+struct WebArgOutputTLV {
+ WebArgOutputTLVType output_tlv_type{};
+ u16 arg_data_size{};
+ INSERT_PADDING_WORDS(1);
+};
+static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size.");
+
+struct WebCommonReturnValue {
+ WebExitReason exit_reason{};
+ INSERT_PADDING_WORDS(1);
+ std::array<char, 0x1000> last_url{};
+ u64 last_url_size{};
+};
+static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
+
+using WebArgInputTLVMap = std::unordered_map<WebArgInputTLVType, std::vector<u8>>;
+
+} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/idle.cpp b/src/core/hle/service/am/idle.cpp
index d256d57c8..6196773d5 100644
--- a/src/core/hle/service/am/idle.cpp
+++ b/src/core/hle/service/am/idle.cpp
@@ -6,7 +6,7 @@
namespace Service::AM {
-IdleSys::IdleSys() : ServiceFramework{"idle:sys"} {
+IdleSys::IdleSys(Core::System& system_) : ServiceFramework{system_, "idle:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAutoPowerDownEvent"},
diff --git a/src/core/hle/service/am/idle.h b/src/core/hle/service/am/idle.h
index c44e856b1..e290c30b1 100644
--- a/src/core/hle/service/am/idle.h
+++ b/src/core/hle/service/am/idle.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::AM {
class IdleSys final : public ServiceFramework<IdleSys> {
public:
- explicit IdleSys();
+ explicit IdleSys(Core::System& system_);
~IdleSys() override;
};
diff --git a/src/core/hle/service/am/omm.cpp b/src/core/hle/service/am/omm.cpp
index 37389ccda..55de67e1d 100644
--- a/src/core/hle/service/am/omm.cpp
+++ b/src/core/hle/service/am/omm.cpp
@@ -6,7 +6,7 @@
namespace Service::AM {
-OMM::OMM() : ServiceFramework{"omm"} {
+OMM::OMM(Core::System& system_) : ServiceFramework{system_, "omm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetOperationMode"},
diff --git a/src/core/hle/service/am/omm.h b/src/core/hle/service/am/omm.h
index 59dc91b72..3766150fe 100644
--- a/src/core/hle/service/am/omm.h
+++ b/src/core/hle/service/am/omm.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::AM {
class OMM final : public ServiceFramework<OMM> {
public:
- explicit OMM();
+ explicit OMM(Core::System& system_);
~OMM() override;
};
diff --git a/src/core/hle/service/am/spsm.cpp b/src/core/hle/service/am/spsm.cpp
index f27729ce7..95218d9ee 100644
--- a/src/core/hle/service/am/spsm.cpp
+++ b/src/core/hle/service/am/spsm.cpp
@@ -6,7 +6,7 @@
namespace Service::AM {
-SPSM::SPSM() : ServiceFramework{"spsm"} {
+SPSM::SPSM(Core::System& system_) : ServiceFramework{system_, "spsm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetState"},
diff --git a/src/core/hle/service/am/spsm.h b/src/core/hle/service/am/spsm.h
index 3a0b979fa..04bbf9e68 100644
--- a/src/core/hle/service/am/spsm.h
+++ b/src/core/hle/service/am/spsm.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::AM {
class SPSM final : public ServiceFramework<SPSM> {
public:
- explicit SPSM();
+ explicit SPSM(Core::System& system_);
~SPSM() override;
};
diff --git a/src/core/hle/service/am/tcap.cpp b/src/core/hle/service/am/tcap.cpp
index a75cbdda8..4d0971c03 100644
--- a/src/core/hle/service/am/tcap.cpp
+++ b/src/core/hle/service/am/tcap.cpp
@@ -6,7 +6,7 @@
namespace Service::AM {
-TCAP::TCAP() : ServiceFramework{"tcap"} {
+TCAP::TCAP(Core::System& system_) : ServiceFramework{system_, "tcap"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetContinuousHighSkinTemperatureEvent"},
diff --git a/src/core/hle/service/am/tcap.h b/src/core/hle/service/am/tcap.h
index 2021b55d1..e9578f16e 100644
--- a/src/core/hle/service/am/tcap.h
+++ b/src/core/hle/service/am/tcap.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::AM {
class TCAP final : public ServiceFramework<TCAP> {
public:
- explicit TCAP();
+ explicit TCAP(Core::System& system_);
~TCAP() override;
};
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 8e79f707b..8d657c0bf 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -5,28 +5,28 @@
#include <algorithm>
#include <numeric>
#include <vector>
+
#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/aoc/aoc_u.h"
#include "core/loader/loader.h"
#include "core/settings.h"
namespace Service::AOC {
-constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
-constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
-
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
- return (title_id & DLC_BASE_TITLE_ID_MASK) == base;
+ return FileSys::GetBaseTitleID(title_id) == base;
}
static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
@@ -47,8 +47,65 @@ static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
return add_on_content;
}
-AOC_U::AOC_U(Core::System& system)
- : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs(system)), system(system) {
+class IPurchaseEventManager final : public ServiceFramework<IPurchaseEventManager> {
+public:
+ explicit IPurchaseEventManager(Core::System& system_)
+ : ServiceFramework{system_, "IPurchaseEventManager"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
+ {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
+ {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
+ {3, nullptr, "PopPurchasedProductInfo"},
+ {4, nullptr, "PopPurchasedProductInfoWithUid"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ purchased_event =
+ Kernel::KEvent::Create(system.Kernel(), "IPurchaseEventManager:PurchasedEvent");
+ purchased_event->Initialize();
+ }
+
+private:
+ void SetDefaultDeliveryTarget(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const auto unknown_1 = rp.Pop<u64>();
+ [[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer();
+
+ LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetDeliveryTarget(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const auto unknown_1 = rp.Pop<u64>();
+ [[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer();
+
+ LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetPurchasedEventReadableHandle(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AOC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(purchased_event->GetReadableEvent());
+ }
+
+ std::shared_ptr<Kernel::KEvent> purchased_event;
+};
+
+AOC_U::AOC_U(Core::System& system_)
+ : ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CountAddOnContentByApplicationId"},
@@ -61,16 +118,16 @@ AOC_U::AOC_U(Core::System& system)
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
{8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},
{9, nullptr, "GetAddOnContentLostErrorCode"},
- {100, nullptr, "CreateEcPurchasedEventManager"},
- {101, nullptr, "CreatePermanentEcPurchasedEventManager"},
+ {100, &AOC_U::CreateEcPurchasedEventManager, "CreateEcPurchasedEventManager"},
+ {101, &AOC_U::CreatePermanentEcPurchasedEventManager, "CreatePermanentEcPurchasedEventManager"},
};
// clang-format on
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- aoc_change_event =
- Kernel::WritableEvent::CreateEventPair(kernel, "GetAddOnContentListChanged:Event");
+ aoc_change_event = Kernel::KEvent::Create(kernel, "GetAddOnContentListChanged:Event");
+ aoc_change_event->Initialize();
}
AOC_U::~AOC_U() = default;
@@ -122,11 +179,11 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
for (u64 content_id : add_on_content) {
- if ((content_id & DLC_BASE_TITLE_ID_MASK) != current) {
+ if (FileSys::GetBaseTitleID(content_id) != current) {
continue;
}
- out.push_back(static_cast<u32>(content_id & 0x7FF));
+ out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id)));
}
}
@@ -163,11 +220,12 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
const auto title_id = system.CurrentProcess()->GetTitleID();
- FileSys::PatchManager pm{title_id};
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto res = pm.GetControlMetadata();
if (res.first == nullptr) {
- rb.Push(title_id + DLC_BASE_TO_AOC_ID);
+ rb.Push(FileSys::GetAOCBaseTitleID(title_id));
return;
}
@@ -196,7 +254,23 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(aoc_change_event.readable);
+ rb.PushCopyObjects(aoc_change_event->GetReadableEvent());
+}
+
+void AOC_U::CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AOC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IPurchaseEventManager>(system);
+}
+
+void AOC_U::CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AOC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IPurchaseEventManager>(system);
}
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h
index 848b2f416..1aa23529e 100644
--- a/src/core/hle/service/aoc/aoc_u.h
+++ b/src/core/hle/service/aoc/aoc_u.h
@@ -6,8 +6,12 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
-class WritableEvent;
+class KEvent;
}
namespace Service::AOC {
@@ -23,10 +27,11 @@ private:
void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx);
void PrepareAddOnContent(Kernel::HLERequestContext& ctx);
void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx);
+ void CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
+ void CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
std::vector<u64> add_on_content;
- Kernel::EventPair aoc_change_event;
- Core::System& system;
+ std::shared_ptr<Kernel::KEvent> aoc_change_event;
};
/// Registers all AOC services with the specified service manager.
diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp
index 85bbf5988..97d6619dd 100644
--- a/src/core/hle/service/apm/apm.cpp
+++ b/src/core/hle/service/apm/apm.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/apm/interface.h"
@@ -13,13 +14,14 @@ Module::~Module() = default;
void InstallInterfaces(Core::System& system) {
auto module_ = std::make_shared<Module>();
- std::make_shared<APM>(module_, system.GetAPMController(), "apm")
+ std::make_shared<APM>(system, module_, system.GetAPMController(), "apm")
->InstallAsService(system.ServiceManager());
- std::make_shared<APM>(module_, system.GetAPMController(), "apm:p")
+ std::make_shared<APM>(system, module_, system.GetAPMController(), "apm:p")
->InstallAsService(system.ServiceManager());
- std::make_shared<APM>(module_, system.GetAPMController(), "apm:am")
+ std::make_shared<APM>(system, module_, system.GetAPMController(), "apm:am")
+ ->InstallAsService(system.ServiceManager());
+ std::make_shared<APM_Sys>(system, system.GetAPMController())
->InstallAsService(system.ServiceManager());
- std::make_shared<APM_Sys>(system.GetAPMController())->InstallAsService(system.ServiceManager());
}
} // namespace Service::APM
diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h
index cf4c2bb11..691fe6c16 100644
--- a/src/core/hle/service/apm/apm.h
+++ b/src/core/hle/service/apm/apm.h
@@ -4,7 +4,9 @@
#pragma once
-#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
namespace Service::APM {
diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp
index 25a886238..03636642b 100644
--- a/src/core/hle/service/apm/controller.cpp
+++ b/src/core/hle/service/apm/controller.cpp
@@ -48,8 +48,7 @@ void Controller::SetPerformanceConfiguration(PerformanceMode mode,
[config](const auto& entry) { return entry.first == config; });
if (iter == config_to_speed.cend()) {
- LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}",
- static_cast<u32>(config));
+ LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", config);
return;
}
@@ -69,7 +68,8 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
}
PerformanceMode Controller::GetCurrentPerformanceMode() const {
- return Settings::values.use_docked_mode ? PerformanceMode::Docked : PerformanceMode::Handheld;
+ return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked
+ : PerformanceMode::Handheld;
}
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {
diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp
index 06f0f8edd..0bff97a37 100644
--- a/src/core/hle/service/apm/interface.cpp
+++ b/src/core/hle/service/apm/interface.cpp
@@ -12,7 +12,8 @@ namespace Service::APM {
class ISession final : public ServiceFramework<ISession> {
public:
- ISession(Controller& controller) : ServiceFramework("ISession"), controller(controller) {
+ explicit ISession(Core::System& system_, Controller& controller_)
+ : ServiceFramework{system_, "ISession"}, controller{controller_} {
static const FunctionInfo functions[] = {
{0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"},
{1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"},
@@ -27,8 +28,7 @@ private:
const auto mode = rp.PopEnum<PerformanceMode>();
const auto config = rp.PopEnum<PerformanceConfiguration>();
- LOG_DEBUG(Service_APM, "called mode={} config={}", static_cast<u32>(mode),
- static_cast<u32>(config));
+ LOG_DEBUG(Service_APM, "called mode={} config={}", mode, config);
controller.SetPerformanceConfiguration(mode, config);
@@ -40,7 +40,7 @@ private:
IPC::RequestParser rp{ctx};
const auto mode = rp.PopEnum<PerformanceMode>();
- LOG_DEBUG(Service_APM, "called mode={}", static_cast<u32>(mode));
+ LOG_DEBUG(Service_APM, "called mode={}", mode);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
@@ -50,12 +50,13 @@ private:
Controller& controller;
};
-APM::APM(std::shared_ptr<Module> apm, Controller& controller, const char* name)
- : ServiceFramework(name), apm(std::move(apm)), controller(controller) {
+APM::APM(Core::System& system_, std::shared_ptr<Module> apm_, Controller& controller_,
+ const char* name)
+ : ServiceFramework{system_, name}, apm(std::move(apm_)), controller{controller_} {
static const FunctionInfo functions[] = {
{0, &APM::OpenSession, "OpenSession"},
{1, &APM::GetPerformanceMode, "GetPerformanceMode"},
- {6, nullptr, "IsCpuOverclockEnabled"},
+ {6, &APM::IsCpuOverclockEnabled, "IsCpuOverclockEnabled"},
};
RegisterHandlers(functions);
}
@@ -67,7 +68,7 @@ void APM::OpenSession(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISession>(controller);
+ rb.PushIpcInterface<ISession>(system, controller);
}
void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
@@ -77,7 +78,16 @@ void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
rb.PushEnum(controller.GetCurrentPerformanceMode());
}
-APM_Sys::APM_Sys(Controller& controller) : ServiceFramework{"apm:sys"}, controller(controller) {
+void APM::IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_APM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(false);
+}
+
+APM_Sys::APM_Sys(Core::System& system_, Controller& controller_)
+ : ServiceFramework{system_, "apm:sys"}, controller{controller_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestPerformanceMode"},
@@ -101,14 +111,14 @@ void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISession>(controller);
+ rb.PushIpcInterface<ISession>(system, controller);
}
void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto mode = rp.PopEnum<CpuBoostMode>();
- LOG_DEBUG(Service_APM, "called, mode={:08X}", static_cast<u32>(mode));
+ LOG_DEBUG(Service_APM, "called, mode={:08X}", mode);
controller.SetFromCpuBoostMode(mode);
diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h
index de1b89437..063ad5308 100644
--- a/src/core/hle/service/apm/interface.h
+++ b/src/core/hle/service/apm/interface.h
@@ -13,12 +13,14 @@ class Module;
class APM final : public ServiceFramework<APM> {
public:
- explicit APM(std::shared_ptr<Module> apm, Controller& controller, const char* name);
+ explicit APM(Core::System& system_, std::shared_ptr<Module> apm_, Controller& controller_,
+ const char* name);
~APM() override;
private:
void OpenSession(Kernel::HLERequestContext& ctx);
void GetPerformanceMode(Kernel::HLERequestContext& ctx);
+ void IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx);
std::shared_ptr<Module> apm;
Controller& controller;
@@ -26,7 +28,7 @@ private:
class APM_Sys final : public ServiceFramework<APM_Sys> {
public:
- explicit APM_Sys(Controller& controller);
+ explicit APM_Sys(Core::System& system_, Controller& controller);
~APM_Sys() override;
void SetCpuBoostMode(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp
index 6ddb547fb..84890be72 100644
--- a/src/core/hle/service/audio/audctl.cpp
+++ b/src/core/hle/service/audio/audctl.cpp
@@ -8,7 +8,7 @@
namespace Service::Audio {
-AudCtl::AudCtl() : ServiceFramework{"audctl"} {
+AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetTargetVolume"},
diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h
index c7fafc02e..15f6c77a0 100644
--- a/src/core/hle/service/audio/audctl.h
+++ b/src/core/hle/service/audio/audctl.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Audio {
class AudCtl final : public ServiceFramework<AudCtl> {
public:
- explicit AudCtl();
+ explicit AudCtl(Core::System& system_);
~AudCtl() override;
private:
diff --git a/src/core/hle/service/audio/auddbg.cpp b/src/core/hle/service/audio/auddbg.cpp
index 8fff3e4b4..6264e4bda 100644
--- a/src/core/hle/service/audio/auddbg.cpp
+++ b/src/core/hle/service/audio/auddbg.cpp
@@ -6,7 +6,7 @@
namespace Service::Audio {
-AudDbg::AudDbg(const char* name) : ServiceFramework{name} {
+AudDbg::AudDbg(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendForDebug"},
diff --git a/src/core/hle/service/audio/auddbg.h b/src/core/hle/service/audio/auddbg.h
index 6689f4759..d1653eedd 100644
--- a/src/core/hle/service/audio/auddbg.h
+++ b/src/core/hle/service/audio/auddbg.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Audio {
class AudDbg final : public ServiceFramework<AudDbg> {
public:
- explicit AudDbg(const char* name);
+ explicit AudDbg(Core::System& system_, const char* name);
~AudDbg() override;
};
diff --git a/src/core/hle/service/audio/audin_a.cpp b/src/core/hle/service/audio/audin_a.cpp
index ddd12f35e..79c3aa920 100644
--- a/src/core/hle/service/audio/audin_a.cpp
+++ b/src/core/hle/service/audio/audin_a.cpp
@@ -6,7 +6,7 @@
namespace Service::Audio {
-AudInA::AudInA() : ServiceFramework{"audin:a"} {
+AudInA::AudInA(Core::System& system_) : ServiceFramework{system_, "audin:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendAudioIns"},
diff --git a/src/core/hle/service/audio/audin_a.h b/src/core/hle/service/audio/audin_a.h
index e7623bc29..15120a4b6 100644
--- a/src/core/hle/service/audio/audin_a.h
+++ b/src/core/hle/service/audio/audin_a.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Audio {
class AudInA final : public ServiceFramework<AudInA> {
public:
- explicit AudInA();
+ explicit AudInA(Core::System& system_);
~AudInA() override;
};
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 3e2299426..26a6deddf 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -11,7 +11,7 @@ namespace Service::Audio {
class IAudioIn final : public ServiceFramework<IAudioIn> {
public:
- IAudioIn() : ServiceFramework("IAudioIn") {
+ explicit IAudioIn(Core::System& system_) : ServiceFramework{system_, "IAudioIn"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAudioInState"},
@@ -36,7 +36,7 @@ public:
}
};
-AudInU::AudInU() : ServiceFramework("audin:u") {
+AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudInU::ListAudioIns, "ListAudioIns"},
@@ -96,7 +96,7 @@ void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<AudInOutParams>(params);
- rb.PushIpcInterface<IAudioIn>();
+ rb.PushIpcInterface<IAudioIn>(system);
}
void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h
index a599f4a64..0d75ae5ac 100644
--- a/src/core/hle/service/audio/audin_u.h
+++ b/src/core/hle/service/audio/audin_u.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -14,7 +18,7 @@ namespace Service::Audio {
class AudInU final : public ServiceFramework<AudInU> {
public:
- explicit AudInU();
+ explicit AudInU(Core::System& system_);
~AudInU() override;
private:
diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp
index 1781bec83..b3f24f9bb 100644
--- a/src/core/hle/service/audio/audio.cpp
+++ b/src/core/hle/service/audio/audio.cpp
@@ -20,22 +20,22 @@
namespace Service::Audio {
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
- std::make_shared<AudCtl>()->InstallAsService(service_manager);
- std::make_shared<AudOutA>()->InstallAsService(service_manager);
+ std::make_shared<AudCtl>(system)->InstallAsService(service_manager);
+ std::make_shared<AudOutA>(system)->InstallAsService(service_manager);
std::make_shared<AudOutU>(system)->InstallAsService(service_manager);
- std::make_shared<AudInA>()->InstallAsService(service_manager);
- std::make_shared<AudInU>()->InstallAsService(service_manager);
- std::make_shared<AudRecA>()->InstallAsService(service_manager);
- std::make_shared<AudRecU>()->InstallAsService(service_manager);
- std::make_shared<AudRenA>()->InstallAsService(service_manager);
+ std::make_shared<AudInA>(system)->InstallAsService(service_manager);
+ std::make_shared<AudInU>(system)->InstallAsService(service_manager);
+ std::make_shared<AudRecA>(system)->InstallAsService(service_manager);
+ std::make_shared<AudRecU>(system)->InstallAsService(service_manager);
+ std::make_shared<AudRenA>(system)->InstallAsService(service_manager);
std::make_shared<AudRenU>(system)->InstallAsService(service_manager);
- std::make_shared<CodecCtl>()->InstallAsService(service_manager);
- std::make_shared<HwOpus>()->InstallAsService(service_manager);
+ std::make_shared<CodecCtl>(system)->InstallAsService(service_manager);
+ std::make_shared<HwOpus>(system)->InstallAsService(service_manager);
- std::make_shared<AudDbg>("audin:d")->InstallAsService(service_manager);
- std::make_shared<AudDbg>("audout:d")->InstallAsService(service_manager);
- std::make_shared<AudDbg>("audrec:d")->InstallAsService(service_manager);
- std::make_shared<AudDbg>("audren:d")->InstallAsService(service_manager);
+ std::make_shared<AudDbg>(system, "audin:d")->InstallAsService(service_manager);
+ std::make_shared<AudDbg>(system, "audout:d")->InstallAsService(service_manager);
+ std::make_shared<AudDbg>(system, "audrec:d")->InstallAsService(service_manager);
+ std::make_shared<AudDbg>(system, "audren:d")->InstallAsService(service_manager);
}
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_a.cpp b/src/core/hle/service/audio/audout_a.cpp
index 85febbca3..19825fd5d 100644
--- a/src/core/hle/service/audio/audout_a.cpp
+++ b/src/core/hle/service/audio/audout_a.cpp
@@ -6,7 +6,7 @@
namespace Service::Audio {
-AudOutA::AudOutA() : ServiceFramework{"audout:a"} {
+AudOutA::AudOutA(Core::System& system_) : ServiceFramework{system_, "audout:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendAudioOuts"},
diff --git a/src/core/hle/service/audio/audout_a.h b/src/core/hle/service/audio/audout_a.h
index d65b66e8e..2043dfb77 100644
--- a/src/core/hle/service/audio/audout_a.h
+++ b/src/core/hle/service/audio/audout_a.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Audio {
class AudOutA final : public ServiceFramework<AudOutA> {
public:
- explicit AudOutA();
+ explicit AudOutA(Core::System& system_);
~AudOutA() override;
};
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 9b4910e53..5ed9cb20e 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -14,9 +14,10 @@
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/audio/audout_u.h"
#include "core/hle/service/audio/errors.h"
#include "core/memory.h"
@@ -29,7 +30,7 @@ constexpr int DefaultSampleRate{48000};
struct AudoutParams {
s32_le sample_rate;
u16_le channel_count;
- INSERT_PADDING_BYTES(2);
+ INSERT_PADDING_BYTES_NOINIT(2);
};
static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size");
@@ -40,11 +41,11 @@ enum class AudioState : u32 {
class IAudioOut final : public ServiceFramework<IAudioOut> {
public:
- IAudioOut(Core::System& system, AudoutParams audio_params, AudioCore::AudioOut& audio_core,
- std::string&& device_name, std::string&& unique_name)
- : ServiceFramework("IAudioOut"), audio_core(audio_core),
- device_name(std::move(device_name)),
- audio_params(audio_params), main_memory{system.Memory()} {
+ IAudioOut(Core::System& system_, AudoutParams audio_params_, AudioCore::AudioOut& audio_core_,
+ std::string&& device_name_, std::string&& unique_name)
+ : ServiceFramework{system_, "IAudioOut"}, audio_core{audio_core_},
+ device_name{std::move(device_name_)}, audio_params{audio_params_}, main_memory{
+ system.Memory()} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
@@ -58,7 +59,7 @@ public:
{8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"},
{9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
{10, nullptr, "GetAudioOutPlayedSampleCount"},
- {11, nullptr, "FlushAudioOutBuffers"},
+ {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"},
{12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"},
{13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"},
};
@@ -66,12 +67,14 @@ public:
RegisterHandlers(functions);
// This is the event handle used to check if the audio buffer was released
- buffer_event =
- Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioOutBufferReleased");
+ buffer_event = Kernel::KEvent::Create(system.Kernel(), "IAudioOutBufferReleased");
+ buffer_event->Initialize();
stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
- audio_params.channel_count, std::move(unique_name),
- [this] { buffer_event.writable->Signal(); });
+ audio_params.channel_count, std::move(unique_name), [this] {
+ const auto guard = LockService();
+ buffer_event->GetWritableEvent()->Signal();
+ });
}
private:
@@ -123,7 +126,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(buffer_event.readable);
+ rb.PushCopyObjects(buffer_event->GetReadableEvent());
}
void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
@@ -183,6 +186,14 @@ private:
rb.Push(static_cast<u32>(stream->GetQueueSize()));
}
+ void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Audio, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(stream->Flush());
+ }
+
void SetAudioOutVolume(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const float volume = rp.Pop<float>();
@@ -209,11 +220,11 @@ private:
[[maybe_unused]] AudoutParams audio_params{};
/// This is the event handle used to check if the audio buffer was released
- Kernel::EventPair buffer_event;
+ std::shared_ptr<Kernel::KEvent> buffer_event;
Core::Memory::Memory& main_memory;
};
-AudOutU::AudOutU(Core::System& system_) : ServiceFramework("audout:u"), system{system_} {
+AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"},
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h
index c9f532ccd..f7ae2f2bf 100644
--- a/src/core/hle/service/audio/audout_u.h
+++ b/src/core/hle/service/audio/audout_u.h
@@ -34,8 +34,6 @@ private:
std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces;
std::unique_ptr<AudioCore::AudioOut> audio_core;
-
- Core::System& system;
};
} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audrec_a.cpp b/src/core/hle/service/audio/audrec_a.cpp
index ce1bfb48d..c5ab7cad4 100644
--- a/src/core/hle/service/audio/audrec_a.cpp
+++ b/src/core/hle/service/audio/audrec_a.cpp
@@ -6,7 +6,7 @@
namespace Service::Audio {
-AudRecA::AudRecA() : ServiceFramework{"audrec:a"} {
+AudRecA::AudRecA(Core::System& system_) : ServiceFramework{system_, "audrec:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendFinalOutputRecorders"},
diff --git a/src/core/hle/service/audio/audrec_a.h b/src/core/hle/service/audio/audrec_a.h
index 384d24c69..2cce90b1d 100644
--- a/src/core/hle/service/audio/audrec_a.h
+++ b/src/core/hle/service/audio/audrec_a.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Audio {
class AudRecA final : public ServiceFramework<AudRecA> {
public:
- explicit AudRecA();
+ explicit AudRecA(Core::System& system_);
~AudRecA() override;
};
diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp
index 1a5aed9ed..eb5c63c62 100644
--- a/src/core/hle/service/audio/audrec_u.cpp
+++ b/src/core/hle/service/audio/audrec_u.cpp
@@ -8,7 +8,8 @@ namespace Service::Audio {
class IFinalOutputRecorder final : public ServiceFramework<IFinalOutputRecorder> {
public:
- IFinalOutputRecorder() : ServiceFramework("IFinalOutputRecorder") {
+ explicit IFinalOutputRecorder(Core::System& system_)
+ : ServiceFramework{system_, "IFinalOutputRecorder"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetFinalOutputRecorderState"},
@@ -29,7 +30,7 @@ public:
}
};
-AudRecU::AudRecU() : ServiceFramework("audrec:u") {
+AudRecU::AudRecU(Core::System& system_) : ServiceFramework{system_, "audrec:u"} {
static const FunctionInfo functions[] = {
{0, nullptr, "OpenFinalOutputRecorder"},
};
diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h
index ca3d638e8..f79d49e5c 100644
--- a/src/core/hle/service/audio/audrec_u.h
+++ b/src/core/hle/service/audio/audrec_u.h
@@ -6,15 +6,15 @@
#include "core/hle/service/service.h"
-namespace Kernel {
-class HLERequestContext;
+namespace Core {
+class System;
}
namespace Service::Audio {
class AudRecU final : public ServiceFramework<AudRecU> {
public:
- explicit AudRecU();
+ explicit AudRecU(Core::System& system_);
~AudRecU() override;
};
diff --git a/src/core/hle/service/audio/audren_a.cpp b/src/core/hle/service/audio/audren_a.cpp
index edb66d985..5e9f866f0 100644
--- a/src/core/hle/service/audio/audren_a.cpp
+++ b/src/core/hle/service/audio/audren_a.cpp
@@ -6,7 +6,7 @@
namespace Service::Audio {
-AudRenA::AudRenA() : ServiceFramework{"audren:a"} {
+AudRenA::AudRenA(Core::System& system_) : ServiceFramework{system_, "audren:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestSuspendAudioRenderers"},
diff --git a/src/core/hle/service/audio/audren_a.h b/src/core/hle/service/audio/audren_a.h
index 81fef0ffe..5d0a626ad 100644
--- a/src/core/hle/service/audio/audren_a.h
+++ b/src/core/hle/service/audio/audren_a.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Audio {
class AudRenA final : public ServiceFramework<AudRenA> {
public:
- explicit AudRenA();
+ explicit AudRenA(Core::System& system_);
~AudRenA() override;
};
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index d8359abaa..b2b2ffc5a 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -16,9 +16,10 @@
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/audio/audren_u.h"
#include "core/hle/service/audio/errors.h"
@@ -26,9 +27,9 @@ namespace Service::Audio {
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
- explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params,
+ explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params,
const std::size_t instance_number)
- : ServiceFramework("IAudioRenderer") {
+ : ServiceFramework{system, "IAudioRenderer"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioRenderer::GetSampleRate, "GetSampleRate"},
@@ -47,18 +48,18 @@ public:
// clang-format on
RegisterHandlers(functions);
- system_event =
- Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioRenderer:SystemEvent");
- renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), system.Memory(),
- audren_params, system_event.writable,
- instance_number);
+ system_event = Kernel::KEvent::Create(system.Kernel(), "IAudioRenderer:SystemEvent");
+ system_event->Initialize();
+ renderer = std::make_unique<AudioCore::AudioRenderer>(
+ system.CoreTiming(), system.Memory(), audren_params,
+ [this]() {
+ const auto guard = LockService();
+ system_event->GetWritableEvent()->Signal();
+ },
+ instance_number);
}
private:
- void UpdateAudioCallback() {
- system_event.writable->Signal();
- }
-
void GetSampleRate(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called");
@@ -94,14 +95,15 @@ private:
void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "(STUBBED) called");
- auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer());
+ std::vector<u8> output_params(ctx.GetWriteBufferSize());
+ auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
- if (result.Succeeded()) {
- ctx.WriteBuffer(result.Unwrap());
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(output_params);
}
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result.Code());
+ rb.Push(result);
}
void Start(Kernel::HLERequestContext& ctx) {
@@ -125,7 +127,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(system_event.readable);
+ rb.PushCopyObjects(system_event->GetReadableEvent());
}
void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) {
@@ -159,15 +161,15 @@ private:
rb.Push(ERR_NOT_SUPPORTED);
}
- Kernel::EventPair system_event;
+ std::shared_ptr<Kernel::KEvent> system_event;
std::unique_ptr<AudioCore::AudioRenderer> renderer;
u32 rendering_time_limit_percent = 100;
};
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
public:
- explicit IAudioDevice(Core::System& system, u32_le revision_num)
- : ServiceFramework("IAudioDevice"), revision{revision_num} {
+ explicit IAudioDevice(Core::System& system_, u32_le revision_num)
+ : ServiceFramework{system_, "IAudioDevice"}, revision{revision_num} {
static const FunctionInfo functions[] = {
{0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
{1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
@@ -186,17 +188,19 @@ public:
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- buffer_event =
- Kernel::WritableEvent::CreateEventPair(kernel, "IAudioOutBufferReleasedEvent");
+ buffer_event = Kernel::KEvent::Create(kernel, "IAudioOutBufferReleasedEvent");
+ buffer_event->Initialize();
// Should be similar to audio_output_device_switch_event
- audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair(
- kernel, "IAudioDevice:AudioInputDeviceSwitchedEvent");
+ audio_input_device_switch_event =
+ Kernel::KEvent::Create(kernel, "IAudioDevice:AudioInputDeviceSwitchedEvent");
+ audio_input_device_switch_event->Initialize();
// Should only be signalled when an audio output device has been changed, example: speaker
// to headset
- audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair(
- kernel, "IAudioDevice:AudioOutputDeviceSwitchedEvent");
+ audio_output_device_switch_event =
+ Kernel::KEvent::Create(kernel, "IAudioDevice:AudioOutputDeviceSwitchedEvent");
+ audio_output_device_switch_event->Initialize();
}
private:
@@ -285,11 +289,11 @@ private:
void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
- buffer_event.writable->Signal();
+ buffer_event->GetWritableEvent()->Signal();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(buffer_event.readable);
+ rb.PushCopyObjects(buffer_event->GetReadableEvent());
}
void GetActiveChannelCount(Kernel::HLERequestContext& ctx) {
@@ -306,7 +310,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(audio_input_device_switch_event.readable);
+ rb.PushCopyObjects(audio_input_device_switch_event->GetReadableEvent());
}
void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
@@ -314,17 +318,17 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(audio_output_device_switch_event.readable);
+ rb.PushCopyObjects(audio_output_device_switch_event->GetReadableEvent());
}
u32_le revision = 0;
- Kernel::EventPair buffer_event;
- Kernel::EventPair audio_input_device_switch_event;
- Kernel::EventPair audio_output_device_switch_event;
+ std::shared_ptr<Kernel::KEvent> buffer_event;
+ std::shared_ptr<Kernel::KEvent> audio_input_device_switch_event;
+ std::shared_ptr<Kernel::KEvent> audio_output_device_switch_event;
}; // namespace Audio
-AudRenU::AudRenU(Core::System& system_) : ServiceFramework("audren:u"), system{system_} {
+AudRenU::AudRenU(Core::System& system_) : ServiceFramework{system_, "audren:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
@@ -346,7 +350,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
OpenAudioRendererImpl(ctx);
}
-static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) {
+static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
// +1 represents the final mix.
return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
1;
@@ -375,7 +379,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
constexpr u64 upsampler_manager_size = 0x48;
// Calculates the part of the size that relates to mix buffers.
- const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
// As of 8.0.0 this is the maximum on voice channels.
constexpr u64 max_voice_channels = 6;
@@ -397,7 +401,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the portion of the size related to the mix data (and the sorting thereof).
- const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
// The size of the mixing info data structure.
constexpr u64 mix_info_size = 0x940;
@@ -447,7 +451,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to voice channel info.
- const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 voice_info_size = 0x220;
constexpr u64 voice_resource_size = 0xD0;
@@ -461,7 +465,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to memory pools.
- const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
const u64 memory_pool_info_size = 0x20;
return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
@@ -469,7 +473,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
// Calculates the part of the size related to the splitter context.
const auto calculate_splitter_context_size =
- [](const AudioCore::AudioRendererParameter& params) -> u64 {
+ [](const AudioCommon::AudioRendererParameter& params) -> u64 {
if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
return 0;
}
@@ -488,27 +492,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to the upsampler info.
- const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) {
- constexpr u64 upsampler_info_size = 0x280;
- // Yes, using the buffer size over info alignment size is intentional here.
- return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count),
- buffer_alignment_size);
- };
+ const auto calculate_upsampler_info_size =
+ [](const AudioCommon::AudioRendererParameter& params) {
+ constexpr u64 upsampler_info_size = 0x280;
+ // Yes, using the buffer size over info alignment size is intentional here.
+ return Common::AlignUp(upsampler_info_size *
+ (u64{params.submix_count} + params.sink_count),
+ buffer_alignment_size);
+ };
// Calculates the part of the size related to effect info.
- const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 effect_info_size = 0x2B0;
return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
};
// Calculates the part of the size related to audio sink info.
- const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 sink_info_size = 0x170;
return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
};
// Calculates the part of the size related to voice state info.
- const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 voice_state_size = 0x100;
const u64 additional_size = buffer_alignment_size - 1;
return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
@@ -516,7 +522,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to performance statistics.
- const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) {
+ const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
// Extra size value appended to the end of the calculation.
constexpr u64 appended = 128;
@@ -543,79 +549,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size that relates to the audio command buffer.
- const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) {
- constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
+ const auto calculate_command_buffer_size =
+ [](const AudioCommon::AudioRendererParameter& params) {
+ constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
- if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
- constexpr u64 command_buffer_size = 0x18000;
+ if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
+ constexpr u64 command_buffer_size = 0x18000;
- return command_buffer_size + alignment;
- }
+ return command_buffer_size + alignment;
+ }
- // When the variadic command buffer is supported, this means
- // the command generator for the audio renderer can issue commands
- // that are (as one would expect), variable in size. So what we need to do
- // is determine the maximum possible size for a few command data structures
- // then multiply them by the amount of present commands indicated by the given
- // respective audio parameters.
+ // When the variadic command buffer is supported, this means
+ // the command generator for the audio renderer can issue commands
+ // that are (as one would expect), variable in size. So what we need to do
+ // is determine the maximum possible size for a few command data structures
+ // then multiply them by the amount of present commands indicated by the given
+ // respective audio parameters.
- constexpr u64 max_biquad_filters = 2;
- constexpr u64 max_mix_buffers = 24;
+ constexpr u64 max_biquad_filters = 2;
+ constexpr u64 max_mix_buffers = 24;
- constexpr u64 biquad_filter_command_size = 0x2C;
+ constexpr u64 biquad_filter_command_size = 0x2C;
- constexpr u64 depop_mix_command_size = 0x24;
- constexpr u64 depop_setup_command_size = 0x50;
+ constexpr u64 depop_mix_command_size = 0x24;
+ constexpr u64 depop_setup_command_size = 0x50;
- constexpr u64 effect_command_max_size = 0x540;
+ constexpr u64 effect_command_max_size = 0x540;
- constexpr u64 mix_command_size = 0x1C;
- constexpr u64 mix_ramp_command_size = 0x24;
- constexpr u64 mix_ramp_grouped_command_size = 0x13C;
+ constexpr u64 mix_command_size = 0x1C;
+ constexpr u64 mix_ramp_command_size = 0x24;
+ constexpr u64 mix_ramp_grouped_command_size = 0x13C;
- constexpr u64 perf_command_size = 0x28;
+ constexpr u64 perf_command_size = 0x28;
- constexpr u64 sink_command_size = 0x130;
+ constexpr u64 sink_command_size = 0x130;
- constexpr u64 submix_command_max_size =
- depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
+ constexpr u64 submix_command_max_size =
+ depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
- constexpr u64 volume_command_size = 0x1C;
- constexpr u64 volume_ramp_command_size = 0x20;
+ constexpr u64 volume_command_size = 0x1C;
+ constexpr u64 volume_ramp_command_size = 0x20;
- constexpr u64 voice_biquad_filter_command_size =
- biquad_filter_command_size * max_biquad_filters;
- constexpr u64 voice_data_command_size = 0x9C;
- const u64 voice_command_max_size =
- (params.splitter_count * depop_setup_command_size) +
- (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size +
- mix_ramp_grouped_command_size);
+ constexpr u64 voice_biquad_filter_command_size =
+ biquad_filter_command_size * max_biquad_filters;
+ constexpr u64 voice_data_command_size = 0x9C;
+ const u64 voice_command_max_size =
+ (params.splitter_count * depop_setup_command_size) +
+ (voice_data_command_size + voice_biquad_filter_command_size +
+ volume_ramp_command_size + mix_ramp_grouped_command_size);
- // Now calculate the individual elements that comprise the size and add them together.
- const u64 effect_commands_size = params.effect_count * effect_command_max_size;
+ // Now calculate the individual elements that comprise the size and add them together.
+ const u64 effect_commands_size = params.effect_count * effect_command_max_size;
- const u64 final_mix_commands_size =
- depop_mix_command_size + volume_command_size * max_mix_buffers;
+ const u64 final_mix_commands_size =
+ depop_mix_command_size + volume_command_size * max_mix_buffers;
- const u64 perf_commands_size =
- perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
+ const u64 perf_commands_size =
+ perf_command_size *
+ (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
- const u64 sink_commands_size = params.sink_count * sink_command_size;
+ const u64 sink_commands_size = params.sink_count * sink_command_size;
- const u64 splitter_commands_size =
- params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
+ const u64 splitter_commands_size =
+ params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
- const u64 submix_commands_size = params.submix_count * submix_command_max_size;
+ const u64 submix_commands_size = params.submix_count * submix_command_max_size;
- const u64 voice_commands_size = params.voice_count * voice_command_max_size;
+ const u64 voice_commands_size = params.voice_count * voice_command_max_size;
- return effect_commands_size + final_mix_commands_size + perf_commands_size +
- sink_commands_size + splitter_commands_size + submix_commands_size +
- voice_commands_size + alignment;
- };
+ return effect_commands_size + final_mix_commands_size + perf_commands_size +
+ sink_commands_size + splitter_commands_size + submix_commands_size +
+ voice_commands_size + alignment;
+ };
IPC::RequestParser rp{ctx};
- const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
+ const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
u64 size = 0;
size += calculate_mix_buffer_sizes(params);
@@ -681,7 +689,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c
void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
+ const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index 4e0ccc792..d693dc406 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -31,7 +31,6 @@ private:
void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx);
std::size_t audren_instance_count = 0;
- Core::System& system;
};
// Describes a particular audio feature that may be supported in a particular revision.
diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp
index c6864146d..94afec1b6 100644
--- a/src/core/hle/service/audio/codecctl.cpp
+++ b/src/core/hle/service/audio/codecctl.cpp
@@ -2,14 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/audio/codecctl.h"
namespace Service::Audio {
-CodecCtl::CodecCtl() : ServiceFramework("codecctl") {
+CodecCtl::CodecCtl(Core::System& system_) : ServiceFramework{system_, "codecctl"} {
static const FunctionInfo functions[] = {
{0, nullptr, "InitializeCodecController"},
{1, nullptr, "FinalizeCodecController"},
diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h
index 2fe75b6e2..58e53259e 100644
--- a/src/core/hle/service/audio/codecctl.h
+++ b/src/core/hle/service/audio/codecctl.h
@@ -6,15 +6,15 @@
#include "core/hle/service/service.h"
-namespace Kernel {
-class HLERequestContext;
+namespace Core {
+class System;
}
namespace Service::Audio {
class CodecCtl final : public ServiceFramework<CodecCtl> {
public:
- explicit CodecCtl();
+ explicit CodecCtl(Core::System& system_);
~CodecCtl() override;
};
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index f1d81602c..ea3414fd2 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -160,8 +160,9 @@ private:
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
public:
- explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state)
- : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} {
+ explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state)
+ : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{
+ std::move(decoder_state)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
@@ -287,10 +288,10 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
- OpusDecoderState{std::move(decoder), sample_rate, channel_count});
+ system, OpusDecoderState{std::move(decoder), sample_rate, channel_count});
}
-HwOpus::HwOpus() : ServiceFramework("hwopus") {
+HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
static const FunctionInfo functions[] = {
{0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"},
{1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"},
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h
index 602ede8ba..4f921f18e 100644
--- a/src/core/hle/service/audio/hwopus.h
+++ b/src/core/hle/service/audio/hwopus.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Audio {
class HwOpus final : public ServiceFramework<HwOpus> {
public:
- explicit HwOpus();
+ explicit HwOpus(Core::System& system_);
~HwOpus() override;
private:
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index def3410cc..92d25dbe4 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -5,6 +5,9 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/lock.h"
#include "core/hle/service/bcat/backend/backend.h"
@@ -12,12 +15,13 @@ namespace Service::BCAT {
ProgressServiceBackend::ProgressServiceBackend(Kernel::KernelCore& kernel,
std::string_view event_name) {
- event = Kernel::WritableEvent::CreateEventPair(
- kernel, std::string("ProgressServiceBackend:UpdateEvent:").append(event_name));
+ event = Kernel::KEvent::Create(kernel,
+ "ProgressServiceBackend:UpdateEvent:" + std::string(event_name));
+ event->Initialize();
}
-std::shared_ptr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() const {
- return event.readable;
+std::shared_ptr<Kernel::KReadableEvent> ProgressServiceBackend::GetEvent() const {
+ return event->GetReadableEvent();
}
DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
@@ -84,10 +88,10 @@ void ProgressServiceBackend::FinishDownload(ResultCode result) {
void ProgressServiceBackend::SignalUpdate() const {
if (need_hle_lock) {
- std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
- event.writable->Signal();
+ std::lock_guard lock(HLE::g_hle_lock);
+ event->GetWritableEvent()->Signal();
} else {
- event.writable->Signal();
+ event->GetWritableEvent()->Signal();
}
}
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
index 48bbbe66f..db585b069 100644
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -11,8 +11,6 @@
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/result.h"
namespace Core {
@@ -21,7 +19,9 @@ class System;
namespace Kernel {
class KernelCore;
-}
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
namespace Service::BCAT {
@@ -98,13 +98,13 @@ public:
private:
explicit ProgressServiceBackend(Kernel::KernelCore& kernel, std::string_view event_name);
- std::shared_ptr<Kernel::ReadableEvent> GetEvent() const;
+ std::shared_ptr<Kernel::KReadableEvent> GetEvent() const;
DeliveryCacheProgressImpl& GetImpl();
void SignalUpdate() const;
DeliveryCacheProgressImpl impl{};
- Kernel::EventPair event;
+ std::shared_ptr<Kernel::KEvent> event;
bool need_hle_lock = false;
};
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index ca021a99f..e43f3f47f 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -196,7 +196,9 @@ private:
const std::string& content_type_name) {
if (client == nullptr) {
client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT);
- client->set_timeout_sec(timeout_seconds);
+ client->set_connection_timeout(timeout_seconds);
+ client->set_read_timeout(timeout_seconds);
+ client->set_write_timeout(timeout_seconds);
}
httplib::Headers headers{
@@ -255,7 +257,7 @@ private:
return out;
}
- std::unique_ptr<httplib::Client> client;
+ std::unique_ptr<httplib::SSLClient> client;
std::string path;
u64 title_id;
u64 build_id;
@@ -443,13 +445,25 @@ std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title)
Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
std::map<std::string, EventStatus>& games) {
httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT)};
- client.set_timeout_sec(static_cast<int>(TIMEOUT_SECONDS));
+ client.set_connection_timeout(static_cast<int>(TIMEOUT_SECONDS));
+ client.set_read_timeout(static_cast<int>(TIMEOUT_SECONDS));
+ client.set_write_timeout(static_cast<int>(TIMEOUT_SECONDS));
httplib::Headers headers{
{std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)},
{std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)},
};
+ if (!client.is_valid()) {
+ LOG_ERROR(Service_BCAT, "Client is invalid, going offline!");
+ return StatusResult::Offline;
+ }
+
+ if (!client.is_socket_open()) {
+ LOG_ERROR(Service_BCAT, "Failed to open socket, going offline!");
+ return StatusResult::Offline;
+ }
+
const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers);
if (response == nullptr)
return StatusResult::Offline;
@@ -469,7 +483,7 @@ Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global,
global = json["global"].get<std::string>();
if (json["games"].is_array()) {
- for (const auto object : json["games"]) {
+ for (const auto& object : json["games"]) {
if (object.is_object() && object.find("name") != object.end()) {
EventStatus detail{};
if (object["header"].is_string()) {
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index db0e06ca1..503109fdd 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -8,11 +8,12 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/bcat/backend/backend.h"
#include "core/hle/service/bcat/bcat.h"
#include "core/hle/service/bcat/module.h"
@@ -87,9 +88,11 @@ struct DeliveryCacheDirectoryEntry {
class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> {
public:
- IDeliveryCacheProgressService(std::shared_ptr<Kernel::ReadableEvent> event,
- const DeliveryCacheProgressImpl& impl)
- : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) {
+ explicit IDeliveryCacheProgressService(Core::System& system_,
+ std::shared_ptr<Kernel::KReadableEvent> event_,
+ const DeliveryCacheProgressImpl& impl_)
+ : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{std::move(event_)},
+ impl{impl_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"},
@@ -118,14 +121,14 @@ private:
rb.Push(RESULT_SUCCESS);
}
- std::shared_ptr<Kernel::ReadableEvent> event;
+ std::shared_ptr<Kernel::KReadableEvent> event;
const DeliveryCacheProgressImpl& impl;
};
class IBcatService final : public ServiceFramework<IBcatService> {
public:
explicit IBcatService(Core::System& system_, Backend& backend_)
- : ServiceFramework("IBcatService"), system{system_}, backend{backend_},
+ : ServiceFramework{system_, "IBcatService"}, backend{backend_},
progress{{
ProgressServiceBackend{system_.Kernel(), "Normal"},
ProgressServiceBackend{system_.Kernel(), "Directory"},
@@ -170,7 +173,7 @@ private:
std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
auto& backend{progress.at(static_cast<std::size_t>(type))};
- return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(),
+ return std::make_shared<IDeliveryCacheProgressService>(system, backend.GetEvent(),
backend.GetImpl());
}
@@ -260,7 +263,6 @@ private:
rb.Push(RESULT_SUCCESS);
}
- Core::System& system;
Backend& backend;
std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress;
@@ -276,8 +278,8 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {
class IDeliveryCacheFileService final : public ServiceFramework<IDeliveryCacheFileService> {
public:
- IDeliveryCacheFileService(FileSys::VirtualDir root_)
- : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) {
+ explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_)
+ : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDeliveryCacheFileService::Open, "Open"},
@@ -393,8 +395,8 @@ private:
class IDeliveryCacheDirectoryService final
: public ServiceFramework<IDeliveryCacheDirectoryService> {
public:
- IDeliveryCacheDirectoryService(FileSys::VirtualDir root_)
- : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
+ explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_)
+ : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDeliveryCacheDirectoryService::Open, "Open"},
@@ -491,8 +493,8 @@ private:
class IDeliveryCacheStorageService final : public ServiceFramework<IDeliveryCacheStorageService> {
public:
- IDeliveryCacheStorageService(FileSys::VirtualDir root_)
- : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) {
+ explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_)
+ : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"},
@@ -517,7 +519,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDeliveryCacheFileService>(root);
+ rb.PushIpcInterface<IDeliveryCacheFileService>(system, root);
}
void CreateDirectoryService(Kernel::HLERequestContext& ctx) {
@@ -525,7 +527,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDeliveryCacheDirectoryService>(root);
+ rb.PushIpcInterface<IDeliveryCacheDirectoryService>(system, root);
}
void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
@@ -550,10 +552,10 @@ private:
void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_BCAT, "called");
+ const auto title_id = system.CurrentProcess()->GetTitleID();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDeliveryCacheStorageService>(
- fsc.GetBCATDirectory(system.CurrentProcess()->GetTitleID()));
+ rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
}
void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
@@ -565,7 +567,7 @@ void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId(
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDeliveryCacheStorageService>(fsc.GetBCATDirectory(title_id));
+ rb.PushIpcInterface<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
}
std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System& system,
@@ -581,10 +583,9 @@ std::unique_ptr<Backend> CreateBackendFromSettings([[maybe_unused]] Core::System
Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
FileSystem::FileSystemController& fsc_, const char* name)
- : ServiceFramework(name), fsc{fsc_}, module{std::move(module_)},
+ : ServiceFramework{system_, name}, fsc{fsc_}, module{std::move(module_)},
backend{CreateBackendFromSettings(system_,
- [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })},
- system{system_} {}
+ [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })} {}
Module::Interface::~Interface() = default;
diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h
index e4ba23ba0..738731c06 100644
--- a/src/core/hle/service/bcat/module.h
+++ b/src/core/hle/service/bcat/module.h
@@ -37,9 +37,6 @@ public:
std::shared_ptr<Module> module;
std::unique_ptr<Backend> backend;
-
- private:
- Core::System& system;
};
};
diff --git a/src/core/hle/service/bpc/bpc.cpp b/src/core/hle/service/bpc/bpc.cpp
index fac6b2f9c..e4630320e 100644
--- a/src/core/hle/service/bpc/bpc.cpp
+++ b/src/core/hle/service/bpc/bpc.cpp
@@ -12,7 +12,7 @@ namespace Service::BPC {
class BPC final : public ServiceFramework<BPC> {
public:
- explicit BPC() : ServiceFramework{"bpc"} {
+ explicit BPC(Core::System& system_) : ServiceFramework{system_, "bpc"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "ShutdownSystem"},
@@ -40,7 +40,7 @@ public:
class BPC_R final : public ServiceFramework<BPC_R> {
public:
- explicit BPC_R() : ServiceFramework{"bpc:r"} {
+ explicit BPC_R(Core::System& system_) : ServiceFramework{system_, "bpc:r"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetRtcTime"},
@@ -55,9 +55,9 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<BPC>()->InstallAsService(sm);
- std::make_shared<BPC_R>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<BPC>(system)->InstallAsService(sm);
+ std::make_shared<BPC_R>(system)->InstallAsService(sm);
}
} // namespace Service::BPC
diff --git a/src/core/hle/service/bpc/bpc.h b/src/core/hle/service/bpc/bpc.h
index eaa37be8d..6ec25aa9b 100644
--- a/src/core/hle/service/bpc/bpc.h
+++ b/src/core/hle/service/bpc/bpc.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::BPC {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::BPC
diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp
index f311afa2f..17a2ac899 100644
--- a/src/core/hle/service/btdrv/btdrv.cpp
+++ b/src/core/hle/service/btdrv/btdrv.cpp
@@ -3,11 +3,12 @@
// Refer to the license.txt file included.
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/btdrv/btdrv.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
@@ -16,7 +17,7 @@ namespace Service::BtDrv {
class Bt final : public ServiceFramework<Bt> {
public:
- explicit Bt(Core::System& system) : ServiceFramework{"bt"} {
+ explicit Bt(Core::System& system_) : ServiceFramework{system_, "bt"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LeClientReadCharacteristic"},
@@ -34,7 +35,8 @@ public:
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- register_event = Kernel::WritableEvent::CreateEventPair(kernel, "BT:RegisterEvent");
+ register_event = Kernel::KEvent::Create(kernel, "BT:RegisterEvent");
+ register_event->Initialize();
}
private:
@@ -43,15 +45,15 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(register_event.readable);
+ rb.PushCopyObjects(register_event->GetReadableEvent());
}
- Kernel::EventPair register_event;
+ std::shared_ptr<Kernel::KEvent> register_event;
};
class BtDrv final : public ServiceFramework<BtDrv> {
public:
- explicit BtDrv() : ServiceFramework{"btdrv"} {
+ explicit BtDrv(Core::System& system_) : ServiceFramework{system_, "btdrv"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "InitializeBluetoothDriver"},
@@ -165,7 +167,7 @@ public:
};
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
- std::make_shared<BtDrv>()->InstallAsService(sm);
+ std::make_shared<BtDrv>(system)->InstallAsService(sm);
std::make_shared<Bt>(system)->InstallAsService(sm);
}
diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp
index 0d251c6d0..9cf2ee92a 100644
--- a/src/core/hle/service/btm/btm.cpp
+++ b/src/core/hle/service/btm/btm.cpp
@@ -5,11 +5,12 @@
#include <memory>
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/btm/btm.h"
#include "core/hle/service/service.h"
@@ -17,7 +18,7 @@ namespace Service::BTM {
class IBtmUserCore final : public ServiceFramework<IBtmUserCore> {
public:
- explicit IBtmUserCore(Core::System& system) : ServiceFramework{"IBtmUserCore"} {
+ explicit IBtmUserCore(Core::System& system_) : ServiceFramework{system_, "IBtmUserCore"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IBtmUserCore::AcquireBleScanEvent, "AcquireBleScanEvent"},
@@ -57,12 +58,14 @@ public:
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- scan_event = Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ScanEvent");
- connection_event =
- Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ConnectionEvent");
- service_discovery =
- Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:Discovery");
- config_event = Kernel::WritableEvent::CreateEventPair(kernel, "IBtmUserCore:ConfigEvent");
+ scan_event = Kernel::KEvent::Create(kernel, "IBtmUserCore:ScanEvent");
+ scan_event->Initialize();
+ connection_event = Kernel::KEvent::Create(kernel, "IBtmUserCore:ConnectionEvent");
+ connection_event->Initialize();
+ service_discovery = Kernel::KEvent::Create(kernel, "IBtmUserCore:Discovery");
+ service_discovery->Initialize();
+ config_event = Kernel::KEvent::Create(kernel, "IBtmUserCore:ConfigEvent");
+ config_event->Initialize();
}
private:
@@ -71,7 +74,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(scan_event.readable);
+ rb.PushCopyObjects(scan_event->GetReadableEvent());
}
void AcquireBleConnectionEvent(Kernel::HLERequestContext& ctx) {
@@ -79,7 +82,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(connection_event.readable);
+ rb.PushCopyObjects(connection_event->GetReadableEvent());
}
void AcquireBleServiceDiscoveryEvent(Kernel::HLERequestContext& ctx) {
@@ -87,7 +90,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(service_discovery.readable);
+ rb.PushCopyObjects(service_discovery->GetReadableEvent());
}
void AcquireBleMtuConfigEvent(Kernel::HLERequestContext& ctx) {
@@ -95,18 +98,18 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(config_event.readable);
+ rb.PushCopyObjects(config_event->GetReadableEvent());
}
- Kernel::EventPair scan_event;
- Kernel::EventPair connection_event;
- Kernel::EventPair service_discovery;
- Kernel::EventPair config_event;
+ std::shared_ptr<Kernel::KEvent> scan_event;
+ std::shared_ptr<Kernel::KEvent> connection_event;
+ std::shared_ptr<Kernel::KEvent> service_discovery;
+ std::shared_ptr<Kernel::KEvent> config_event;
};
class BTM_USR final : public ServiceFramework<BTM_USR> {
public:
- explicit BTM_USR(Core::System& system) : ServiceFramework{"btm:u"}, system(system) {
+ explicit BTM_USR(Core::System& system_) : ServiceFramework{system_, "btm:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BTM_USR::GetCore, "GetCore"},
@@ -123,13 +126,11 @@ private:
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IBtmUserCore>(system);
}
-
- Core::System& system;
};
class BTM final : public ServiceFramework<BTM> {
public:
- explicit BTM() : ServiceFramework{"btm"} {
+ explicit BTM(Core::System& system_) : ServiceFramework{system_, "btm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetState"},
@@ -206,7 +207,7 @@ public:
class BTM_DBG final : public ServiceFramework<BTM_DBG> {
public:
- explicit BTM_DBG() : ServiceFramework{"btm:dbg"} {
+ explicit BTM_DBG(Core::System& system_) : ServiceFramework{system_, "btm:dbg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "AcquireDiscoveryEvent"},
@@ -231,7 +232,7 @@ public:
class IBtmSystemCore final : public ServiceFramework<IBtmSystemCore> {
public:
- explicit IBtmSystemCore() : ServiceFramework{"IBtmSystemCore"} {
+ explicit IBtmSystemCore(Core::System& system_) : ServiceFramework{system_, "IBtmSystemCore"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "StartGamepadPairing"},
@@ -253,7 +254,7 @@ public:
class BTM_SYS final : public ServiceFramework<BTM_SYS> {
public:
- explicit BTM_SYS() : ServiceFramework{"btm:sys"} {
+ explicit BTM_SYS(Core::System& system_) : ServiceFramework{system_, "btm:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BTM_SYS::GetCore, "GetCore"},
@@ -269,14 +270,14 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IBtmSystemCore>();
+ rb.PushIpcInterface<IBtmSystemCore>(system);
}
};
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
- std::make_shared<BTM>()->InstallAsService(sm);
- std::make_shared<BTM_DBG>()->InstallAsService(sm);
- std::make_shared<BTM_SYS>()->InstallAsService(sm);
+ std::make_shared<BTM>(system)->InstallAsService(sm);
+ std::make_shared<BTM_DBG>(system)->InstallAsService(sm);
+ std::make_shared<BTM_SYS>(system)->InstallAsService(sm);
std::make_shared<BTM_USR>(system)->InstallAsService(sm);
}
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index ba5749b84..5b7fe8e9b 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -13,13 +13,13 @@
namespace Service::Capture {
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<CAPS_A>()->InstallAsService(sm);
- std::make_shared<CAPS_C>()->InstallAsService(sm);
- std::make_shared<CAPS_U>()->InstallAsService(sm);
- std::make_shared<CAPS_SC>()->InstallAsService(sm);
- std::make_shared<CAPS_SS>()->InstallAsService(sm);
- std::make_shared<CAPS_SU>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<CAPS_A>(system)->InstallAsService(sm);
+ std::make_shared<CAPS_C>(system)->InstallAsService(sm);
+ std::make_shared<CAPS_U>(system)->InstallAsService(sm);
+ std::make_shared<CAPS_SC>(system)->InstallAsService(sm);
+ std::make_shared<CAPS_SS>(system)->InstallAsService(sm);
+ std::make_shared<CAPS_SU>(system)->InstallAsService(sm);
}
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h
index b8c67b6e2..3c4290c88 100644
--- a/src/core/hle/service/caps/caps.h
+++ b/src/core/hle/service/caps/caps.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
@@ -87,6 +91,6 @@ static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
"ApplicationAlbumFileEntry has incorrect size.");
/// Registers all Capture services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index a0a3b2ae3..1fe4f0e14 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -8,7 +8,8 @@ namespace Service::Capture {
class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> {
public:
- explicit IAlbumAccessorSession() : ServiceFramework{"IAlbumAccessorSession"} {
+ explicit IAlbumAccessorSession(Core::System& system_)
+ : ServiceFramework{system_, "IAlbumAccessorSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{2001, nullptr, "OpenAlbumMovieReadStream"},
@@ -26,7 +27,7 @@ public:
}
};
-CAPS_A::CAPS_A() : ServiceFramework("caps:a") {
+CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAlbumFileCount"},
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index cb93aad5b..389cc6dbe 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -14,7 +18,7 @@ namespace Service::Capture {
class CAPS_A final : public ServiceFramework<CAPS_A> {
public:
- explicit CAPS_A();
+ explicit CAPS_A(Core::System& system_);
~CAPS_A() override;
};
diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp
index ab17a187e..45c1c9d30 100644
--- a/src/core/hle/service/caps/caps_c.cpp
+++ b/src/core/hle/service/caps/caps_c.cpp
@@ -2,13 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/service/caps/caps_c.h"
namespace Service::Capture {
class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> {
public:
- explicit IAlbumControlSession() : ServiceFramework{"IAlbumControlSession"} {
+ explicit IAlbumControlSession(Core::System& system_)
+ : ServiceFramework{system_, "IAlbumControlSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{2001, nullptr, "OpenAlbumMovieReadStream"},
@@ -42,12 +45,12 @@ public:
}
};
-CAPS_C::CAPS_C() : ServiceFramework("caps:c") {
+CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
{2, nullptr, "CaptureRawImageWithTimeout"},
- {33, nullptr, "Unknown33"},
+ {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"},
{1001, nullptr, "RequestTakingScreenShot"},
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
{1011, nullptr, "NotifyTakingScreenShotRefused"},
@@ -72,4 +75,16 @@ CAPS_C::CAPS_C() : ServiceFramework("caps:c") {
CAPS_C::~CAPS_C() = default;
+void CAPS_C::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto library_version{rp.Pop<u64>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}",
+ library_version, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h
index a9d028689..c6d1dfdce 100644
--- a/src/core/hle/service/caps/caps_c.h
+++ b/src/core/hle/service/caps/caps_c.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -14,8 +18,11 @@ namespace Service::Capture {
class CAPS_C final : public ServiceFramework<CAPS_C> {
public:
- explicit CAPS_C();
+ explicit CAPS_C(Core::System& system_);
~CAPS_C() override;
+
+private:
+ void SetShimLibraryVersion(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp
index 822ee96c8..d91e18e80 100644
--- a/src/core/hle/service/caps/caps_sc.cpp
+++ b/src/core/hle/service/caps/caps_sc.cpp
@@ -6,7 +6,7 @@
namespace Service::Capture {
-CAPS_SC::CAPS_SC() : ServiceFramework("caps:sc") {
+CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h
index ac3e929ca..e79a33ee5 100644
--- a/src/core/hle/service/caps/caps_sc.h
+++ b/src/core/hle/service/caps/caps_sc.h
@@ -6,15 +6,15 @@
#include "core/hle/service/service.h"
-namespace Kernel {
-class HLERequestContext;
+namespace Core {
+class System;
}
namespace Service::Capture {
class CAPS_SC final : public ServiceFramework<CAPS_SC> {
public:
- explicit CAPS_SC();
+ explicit CAPS_SC(Core::System& system_);
~CAPS_SC() override;
};
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index 24dc716e7..2b5314691 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -6,7 +6,7 @@
namespace Service::Capture {
-CAPS_SS::CAPS_SS() : ServiceFramework("caps:ss") {
+CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
// clang-format off
static const FunctionInfo functions[] = {
{201, nullptr, "SaveScreenShot"},
diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h
index 450686e4f..1816f7885 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -6,15 +6,15 @@
#include "core/hle/service/service.h"
-namespace Kernel {
-class HLERequestContext;
+namespace Core {
+class System;
}
namespace Service::Capture {
class CAPS_SS final : public ServiceFramework<CAPS_SS> {
public:
- explicit CAPS_SS();
+ explicit CAPS_SS(Core::System& system_);
~CAPS_SS() override;
};
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index fffb2ecf9..eae39eb7b 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -8,7 +8,7 @@
namespace Service::Capture {
-CAPS_SU::CAPS_SU() : ServiceFramework("caps:su") {
+CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
// clang-format off
static const FunctionInfo functions[] = {
{32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"},
@@ -25,7 +25,12 @@ CAPS_SU::CAPS_SU() : ServiceFramework("caps:su") {
CAPS_SU::~CAPS_SU() = default;
void CAPS_SU::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Capture, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const auto library_version{rp.Pop<u64>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}",
+ library_version, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index 62c9603a9..b366fdb13 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -14,7 +18,7 @@ namespace Service::Capture {
class CAPS_SU final : public ServiceFramework<CAPS_SU> {
public:
- explicit CAPS_SU();
+ explicit CAPS_SU(Core::System& system_);
~CAPS_SU() override;
private:
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index f36d8de2d..842316a2e 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -12,8 +12,8 @@ namespace Service::Capture {
class IAlbumAccessorApplicationSession final
: public ServiceFramework<IAlbumAccessorApplicationSession> {
public:
- explicit IAlbumAccessorApplicationSession()
- : ServiceFramework{"IAlbumAccessorApplicationSession"} {
+ explicit IAlbumAccessorApplicationSession(Core::System& system_)
+ : ServiceFramework{system_, "IAlbumAccessorApplicationSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{2001, nullptr, "OpenAlbumMovieReadStream"},
@@ -28,11 +28,10 @@ public:
}
};
-CAPS_U::CAPS_U() : ServiceFramework("caps:u") {
+CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
// clang-format off
static const FunctionInfo functions[] = {
- {31, nullptr, "GetShimLibraryVersion"},
- {32, nullptr, "SetShimLibraryVersion"},
+ {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"},
{102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"},
{103, nullptr, "DeleteAlbumContentsFileForApplication"},
{104, nullptr, "GetAlbumContentsFileSizeForApplication"},
@@ -42,7 +41,7 @@ CAPS_U::CAPS_U() : ServiceFramework("caps:u") {
{130, nullptr, "PrecheckToCreateContentsForApplication"},
{140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
{141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
- {142, nullptr, "GetAlbumFileList3AaeAruid"},
+ {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
{143, nullptr, "GetAlbumFileList4AaeUidAruid"},
{60002, nullptr, "OpenAccessorSessionForApplication"},
};
@@ -53,6 +52,18 @@ CAPS_U::CAPS_U() : ServiceFramework("caps:u") {
CAPS_U::~CAPS_U() = default;
+void CAPS_U::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto library_version{rp.Pop<u64>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}",
+ library_version, applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
void CAPS_U::GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx) {
// Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an
// u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total
@@ -66,17 +77,24 @@ void CAPS_U::GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& c
// TODO: Update this when we implement the album.
// Currently we do not have a method of accessing album entries, set this to 0 for now.
- constexpr s32 total_entries{0};
+ constexpr u32 total_entries_1{};
+ constexpr u32 total_entries_2{};
- LOG_WARNING(Service_Capture,
- "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
- "end_posix_time={}, applet_resource_user_id={}, total_entries={}",
- pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id,
- total_entries);
+ LOG_WARNING(
+ Service_Capture,
+ "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
+ "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}",
+ pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id,
+ total_entries_1, total_entries_2);
- IPC::ResponseBuilder rb{ctx, 3};
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push(total_entries);
+ rb.Push(total_entries_1);
+ rb.Push(total_entries_2);
+}
+
+void CAPS_U::GetAlbumFileList3AaeAruid(Kernel::HLERequestContext& ctx) {
+ GetAlbumContentsFileListForApplication(ctx);
}
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h
index 689364de4..e7e0d8775 100644
--- a/src/core/hle/service/caps/caps_u.h
+++ b/src/core/hle/service/caps/caps_u.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -14,11 +18,13 @@ namespace Service::Capture {
class CAPS_U final : public ServiceFramework<CAPS_U> {
public:
- explicit CAPS_U();
+ explicit CAPS_U(Core::System& system_);
~CAPS_U() override;
private:
+ void SetShimLibraryVersion(Kernel::HLERequestContext& ctx);
void GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx);
+ void GetAlbumFileList3AaeAruid(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/erpt/erpt.cpp b/src/core/hle/service/erpt/erpt.cpp
index 4ec8c3093..4924c61c3 100644
--- a/src/core/hle/service/erpt/erpt.cpp
+++ b/src/core/hle/service/erpt/erpt.cpp
@@ -12,7 +12,7 @@ namespace Service::ERPT {
class ErrorReportContext final : public ServiceFramework<ErrorReportContext> {
public:
- explicit ErrorReportContext() : ServiceFramework{"erpt:c"} {
+ explicit ErrorReportContext(Core::System& system_) : ServiceFramework{system_, "erpt:c"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SubmitContext"},
@@ -35,7 +35,7 @@ public:
class ErrorReportSession final : public ServiceFramework<ErrorReportSession> {
public:
- explicit ErrorReportSession() : ServiceFramework{"erpt:r"} {
+ explicit ErrorReportSession(Core::System& system_) : ServiceFramework{system_, "erpt:r"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "OpenReport"},
@@ -48,9 +48,9 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<ErrorReportContext>()->InstallAsService(sm);
- std::make_shared<ErrorReportSession>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<ErrorReportContext>(system)->InstallAsService(sm);
+ std::make_shared<ErrorReportSession>(system)->InstallAsService(sm);
}
} // namespace Service::ERPT
diff --git a/src/core/hle/service/erpt/erpt.h b/src/core/hle/service/erpt/erpt.h
index de439ab6d..8cd5c081f 100644
--- a/src/core/hle/service/erpt/erpt.h
+++ b/src/core/hle/service/erpt/erpt.h
@@ -4,6 +4,10 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
@@ -11,6 +15,6 @@ class ServiceManager;
namespace Service::ERPT {
/// Registers all ERPT services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::ERPT
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index c2737a365..26d1e3306 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -14,7 +14,7 @@ constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
class ETicket final : public ServiceFramework<ETicket> {
public:
- explicit ETicket() : ServiceFramework{"es"} {
+ explicit ETicket(Core::System& system_) : ServiceFramework{system_, "es"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &ETicket::ImportTicket, "ImportTicket"},
@@ -305,8 +305,8 @@ private:
Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
};
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<ETicket>()->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<ETicket>(system)->InstallAsService(service_manager);
}
} // namespace Service::ES
diff --git a/src/core/hle/service/es/es.h b/src/core/hle/service/es/es.h
index afe70465b..2a7b27d12 100644
--- a/src/core/hle/service/es/es.h
+++ b/src/core/hle/service/es/es.h
@@ -4,6 +4,10 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
@@ -11,6 +15,6 @@ class ServiceManager;
namespace Service::ES {
/// Registers all ES services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::ES
diff --git a/src/core/hle/service/eupld/eupld.cpp b/src/core/hle/service/eupld/eupld.cpp
index 0d6d244f4..2d650b1b7 100644
--- a/src/core/hle/service/eupld/eupld.cpp
+++ b/src/core/hle/service/eupld/eupld.cpp
@@ -12,7 +12,7 @@ namespace Service::EUPLD {
class ErrorUploadContext final : public ServiceFramework<ErrorUploadContext> {
public:
- explicit ErrorUploadContext() : ServiceFramework{"eupld:c"} {
+ explicit ErrorUploadContext(Core::System& system_) : ServiceFramework{system_, "eupld:c"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SetUrl"},
@@ -29,7 +29,7 @@ public:
class ErrorUploadRequest final : public ServiceFramework<ErrorUploadRequest> {
public:
- explicit ErrorUploadRequest() : ServiceFramework{"eupld:r"} {
+ explicit ErrorUploadRequest(Core::System& system_) : ServiceFramework{system_, "eupld:r"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -45,9 +45,9 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<ErrorUploadContext>()->InstallAsService(sm);
- std::make_shared<ErrorUploadRequest>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<ErrorUploadContext>(system)->InstallAsService(sm);
+ std::make_shared<ErrorUploadRequest>(system)->InstallAsService(sm);
}
} // namespace Service::EUPLD
diff --git a/src/core/hle/service/eupld/eupld.h b/src/core/hle/service/eupld/eupld.h
index 6eef2c15f..539993a9d 100644
--- a/src/core/hle/service/eupld/eupld.h
+++ b/src/core/hle/service/eupld/eupld.h
@@ -4,6 +4,10 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
@@ -11,6 +15,6 @@ class ServiceManager;
namespace Service::EUPLD {
/// Registers all EUPLD services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::EUPLD
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index 2546d7595..13147472e 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -20,8 +20,9 @@
namespace Service::Fatal {
-Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
- : ServiceFramework(name), module(std::move(module)), system(system) {}
+Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
+ const char* name)
+ : ServiceFramework{system_, name}, module{std::move(module_)} {}
Module::Interface::~Interface() = default;
@@ -110,8 +111,9 @@ static void GenerateErrorReport(Core::System& system, ResultCode error_code,
static void ThrowFatalError(Core::System& system, ResultCode error_code, FatalType fatal_type,
const FatalInfo& info) {
- LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}",
- static_cast<u32>(fatal_type), error_code.raw);
+ LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}", fatal_type,
+ error_code.raw);
+
switch (fatal_type) {
case FatalType::ErrorReportAndScreen:
GenerateErrorReport(system, error_code, info);
diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h
index bd9339dfc..2095bf89f 100644
--- a/src/core/hle/service/fatal/fatal.h
+++ b/src/core/hle/service/fatal/fatal.h
@@ -16,7 +16,8 @@ class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name);
+ explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
+ const char* name);
~Interface() override;
void ThrowFatal(Kernel::HLERequestContext& ctx);
@@ -25,7 +26,6 @@ public:
protected:
std::shared_ptr<Module> module;
- Core::System& system;
};
};
diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp
index 066ccf6b0..8672b85dc 100644
--- a/src/core/hle/service/fatal/fatal_p.cpp
+++ b/src/core/hle/service/fatal/fatal_p.cpp
@@ -6,8 +6,8 @@
namespace Service::Fatal {
-Fatal_P::Fatal_P(std::shared_ptr<Module> module, Core::System& system)
- : Module::Interface(std::move(module), system, "fatal:p") {}
+Fatal_P::Fatal_P(std::shared_ptr<Module> module_, Core::System& system_)
+ : Interface(std::move(module_), system_, "fatal:p") {}
Fatal_P::~Fatal_P() = default;
diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h
index c6d953cb5..ffa5b7b98 100644
--- a/src/core/hle/service/fatal/fatal_p.h
+++ b/src/core/hle/service/fatal/fatal_p.h
@@ -10,7 +10,7 @@ namespace Service::Fatal {
class Fatal_P final : public Module::Interface {
public:
- explicit Fatal_P(std::shared_ptr<Module> module, Core::System& system);
+ explicit Fatal_P(std::shared_ptr<Module> module_, Core::System& system_);
~Fatal_P() override;
};
diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp
index 8d72ed485..82993938a 100644
--- a/src/core/hle/service/fatal/fatal_u.cpp
+++ b/src/core/hle/service/fatal/fatal_u.cpp
@@ -6,8 +6,8 @@
namespace Service::Fatal {
-Fatal_U::Fatal_U(std::shared_ptr<Module> module, Core::System& system)
- : Module::Interface(std::move(module), system, "fatal:u") {
+Fatal_U::Fatal_U(std::shared_ptr<Module> module_, Core::System& system_)
+ : Interface(std::move(module_), system_, "fatal:u") {
static const FunctionInfo functions[] = {
{0, &Fatal_U::ThrowFatal, "ThrowFatal"},
{1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"},
diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h
index 34c5c7f95..0b58c9112 100644
--- a/src/core/hle/service/fatal/fatal_u.h
+++ b/src/core/hle/service/fatal/fatal_u.h
@@ -10,7 +10,7 @@ namespace Service::Fatal {
class Fatal_U final : public Module::Interface {
public:
- explicit Fatal_U(std::shared_ptr<Module> module, Core::System& system);
+ explicit Fatal_U(std::shared_ptr<Module> module_, Core::System& system_);
~Fatal_U() override;
};
diff --git a/src/core/hle/service/fgm/fgm.cpp b/src/core/hle/service/fgm/fgm.cpp
index e461274c1..9dc1bc52e 100644
--- a/src/core/hle/service/fgm/fgm.cpp
+++ b/src/core/hle/service/fgm/fgm.cpp
@@ -14,7 +14,7 @@ namespace Service::FGM {
class IRequest final : public ServiceFramework<IRequest> {
public:
- explicit IRequest() : ServiceFramework{"IRequest"} {
+ explicit IRequest(Core::System& system_) : ServiceFramework{system_, "IRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -30,7 +30,7 @@ public:
class FGM final : public ServiceFramework<FGM> {
public:
- explicit FGM(const char* name) : ServiceFramework{name} {
+ explicit FGM(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &FGM::Initialize, "Initialize"},
@@ -46,13 +46,13 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IRequest>();
+ rb.PushIpcInterface<IRequest>(system);
}
};
class FGM_DBG final : public ServiceFramework<FGM_DBG> {
public:
- explicit FGM_DBG() : ServiceFramework{"fgm:dbg"} {
+ explicit FGM_DBG(Core::System& system_) : ServiceFramework{system_, "fgm:dbg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -65,11 +65,11 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<FGM>("fgm")->InstallAsService(sm);
- std::make_shared<FGM>("fgm:0")->InstallAsService(sm);
- std::make_shared<FGM>("fgm:9")->InstallAsService(sm);
- std::make_shared<FGM_DBG>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<FGM>(system, "fgm")->InstallAsService(sm);
+ std::make_shared<FGM>(system, "fgm:0")->InstallAsService(sm);
+ std::make_shared<FGM>(system, "fgm:9")->InstallAsService(sm);
+ std::make_shared<FGM_DBG>(system)->InstallAsService(sm);
}
} // namespace Service::FGM
diff --git a/src/core/hle/service/fgm/fgm.h b/src/core/hle/service/fgm/fgm.h
index e59691264..75978f2ed 100644
--- a/src/core/hle/service/fgm/fgm.h
+++ b/src/core/hle/service/fgm/fgm.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::FGM {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::FGM
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 2cee1193c..b15c737e1 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -79,7 +79,7 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons
}
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
- if (dir->GetFile(Common::FS::GetFilename(path)) == nullptr) {
+ if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) {
return FileSys::ERROR_PATH_NOT_FOUND;
}
if (!dir->DeleteFile(Common::FS::GetFilename(path))) {
@@ -93,8 +93,9 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons
ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const {
std::string path(Common::FS::SanitizePath(path_));
auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path));
- if (dir == nullptr && Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty())
+ if (dir == nullptr || Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty()) {
dir = backing;
+ }
auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path));
if (new_dir == nullptr) {
// TODO(DarkLordZach): Find a better error code for this
@@ -297,10 +298,35 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess()
return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID());
}
+ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFS(
+ u64 title_id, FileSys::ContentRecordType type) const {
+ LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id);
+
+ if (romfs_factory == nullptr) {
+ // TODO: Find a better error code for this
+ return RESULT_UNKNOWN;
+ }
+
+ return romfs_factory->OpenPatchedRomFS(title_id, type);
+}
+
+ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFSWithProgramIndex(
+ u64 title_id, u8 program_index, FileSys::ContentRecordType type) const {
+ LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id,
+ program_index);
+
+ if (romfs_factory == nullptr) {
+ // TODO: Find a better error code for this
+ return RESULT_UNKNOWN;
+ }
+
+ return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type);
+}
+
ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
- title_id, static_cast<u8>(storage_id), static_cast<u8>(type));
+ title_id, storage_id, type);
if (romfs_factory == nullptr) {
// TODO(bunnei): Find a better error code for this
@@ -312,8 +338,8 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const {
- LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}",
- static_cast<u8>(space), save_struct.DebugInfo());
+ LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space,
+ save_struct.DebugInfo());
if (save_data_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
@@ -324,8 +350,8 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData(
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const {
- LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}",
- static_cast<u8>(space), attribute.DebugInfo());
+ LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", space,
+ attribute.DebugInfo());
if (save_data_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
@@ -336,7 +362,7 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData(
ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace(
FileSys::SaveDataSpaceId space) const {
- LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", static_cast<u8>(space));
+ LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", space);
if (save_data_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
@@ -357,7 +383,7 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const {
ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition(
FileSys::BisPartitionId id) const {
- LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", static_cast<u32>(id));
+ LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id);
if (bis_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
@@ -373,13 +399,13 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition(
ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
FileSys::BisPartitionId id) const {
- LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", static_cast<u32>(id));
+ LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id);
if (bis_factory == nullptr) {
return FileSys::ERROR_ENTITY_NOT_FOUND;
}
- auto part = bis_factory->OpenPartitionStorage(id);
+ auto part = bis_factory->OpenPartitionStorage(id, system.GetFilesystem());
if (part == nullptr) {
return FileSys::ERROR_INVALID_ARGUMENT;
}
@@ -454,7 +480,9 @@ FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataTy
const auto res = system.GetAppLoader().ReadControlData(nacp);
if (res != Loader::ResultStatus::Success) {
- FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
+ const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(),
+ system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto metadata = pm.GetControlMetadata();
const auto& nacp_unique = metadata.first;
@@ -714,7 +742,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
}
if (save_data_factory == nullptr) {
- save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
+ save_data_factory =
+ std::make_unique<FileSys::SaveDataFactory>(system, std::move(nand_directory));
}
if (sdmc_factory == nullptr) {
@@ -725,10 +754,9 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
}
void InstallInterfaces(Core::System& system) {
- std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager());
- std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager());
- std::make_shared<FSP_SRV>(system.GetFileSystemController(), system.GetReporter())
- ->InstallAsService(system.ServiceManager());
+ std::make_shared<FSP_LDR>(system)->InstallAsService(system.ServiceManager());
+ std::make_shared<FSP_PR>(system)->InstallAsService(system.ServiceManager());
+ std::make_shared<FSP_SRV>(system)->InstallAsService(system.ServiceManager());
}
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 6dbbf0b2b..7102d3f9a 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -66,6 +66,10 @@ public:
void SetPackedUpdate(FileSys::VirtualFile update_raw);
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const;
+ ResultVal<FileSys::VirtualFile> OpenPatchedRomFS(u64 title_id,
+ FileSys::ContentRecordType type) const;
+ ResultVal<FileSys::VirtualFile> OpenPatchedRomFSWithProgramIndex(
+ u64 title_id, u8 program_index, FileSys::ContentRecordType type) const;
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type) const;
ResultVal<FileSys::VirtualDir> CreateSaveData(
diff --git a/src/core/hle/service/filesystem/fsp_ldr.cpp b/src/core/hle/service/filesystem/fsp_ldr.cpp
index fb487d5bc..1f6c17ba5 100644
--- a/src/core/hle/service/filesystem/fsp_ldr.cpp
+++ b/src/core/hle/service/filesystem/fsp_ldr.cpp
@@ -7,7 +7,7 @@
namespace Service::FileSystem {
-FSP_LDR::FSP_LDR() : ServiceFramework{"fsp:ldr"} {
+FSP_LDR::FSP_LDR(Core::System& system_) : ServiceFramework{system_, "fsp:ldr"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "OpenCodeFileSystem"},
diff --git a/src/core/hle/service/filesystem/fsp_ldr.h b/src/core/hle/service/filesystem/fsp_ldr.h
index 8210b7729..d6432a0e1 100644
--- a/src/core/hle/service/filesystem/fsp_ldr.h
+++ b/src/core/hle/service/filesystem/fsp_ldr.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::FileSystem {
class FSP_LDR final : public ServiceFramework<FSP_LDR> {
public:
- explicit FSP_LDR();
+ explicit FSP_LDR(Core::System& system_);
~FSP_LDR() override;
};
diff --git a/src/core/hle/service/filesystem/fsp_pr.cpp b/src/core/hle/service/filesystem/fsp_pr.cpp
index 378201610..00e4d1662 100644
--- a/src/core/hle/service/filesystem/fsp_pr.cpp
+++ b/src/core/hle/service/filesystem/fsp_pr.cpp
@@ -7,7 +7,7 @@
namespace Service::FileSystem {
-FSP_PR::FSP_PR() : ServiceFramework{"fsp:pr"} {
+FSP_PR::FSP_PR(Core::System& system_) : ServiceFramework{system_, "fsp:pr"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RegisterProgram"},
diff --git a/src/core/hle/service/filesystem/fsp_pr.h b/src/core/hle/service/filesystem/fsp_pr.h
index 556ae5ce9..9e622518c 100644
--- a/src/core/hle/service/filesystem/fsp_pr.h
+++ b/src/core/hle/service/filesystem/fsp_pr.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::FileSystem {
class FSP_PR final : public ServiceFramework<FSP_PR> {
public:
- explicit FSP_PR();
+ explicit FSP_PR(Core::System& system_);
~FSP_PR() override;
};
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 26fd87f58..9cc260515 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -14,6 +14,7 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/file_sys/directory.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/mode.h"
@@ -56,8 +57,8 @@ enum class FileSystemType : u8 {
class IStorage final : public ServiceFramework<IStorage> {
public:
- explicit IStorage(FileSys::VirtualFile backend_)
- : ServiceFramework("IStorage"), backend(std::move(backend_)) {
+ explicit IStorage(Core::System& system_, FileSys::VirtualFile backend_)
+ : ServiceFramework{system_, "IStorage"}, backend(std::move(backend_)) {
static const FunctionInfo functions[] = {
{0, &IStorage::Read, "Read"},
{1, nullptr, "Write"},
@@ -114,8 +115,8 @@ private:
class IFile final : public ServiceFramework<IFile> {
public:
- explicit IFile(FileSys::VirtualFile backend_)
- : ServiceFramework("IFile"), backend(std::move(backend_)) {
+ explicit IFile(Core::System& system_, FileSys::VirtualFile backend_)
+ : ServiceFramework{system_, "IFile"}, backend(std::move(backend_)) {
static const FunctionInfo functions[] = {
{0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"},
{2, &IFile::Flush, "Flush"}, {3, &IFile::SetSize, "SetSize"},
@@ -246,8 +247,8 @@ static void BuildEntryIndex(std::vector<FileSys::Entry>& entries, const std::vec
class IDirectory final : public ServiceFramework<IDirectory> {
public:
- explicit IDirectory(FileSys::VirtualDir backend_)
- : ServiceFramework("IDirectory"), backend(std::move(backend_)) {
+ explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_)
+ : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) {
static const FunctionInfo functions[] = {
{0, &IDirectory::Read, "Read"},
{1, &IDirectory::GetEntryCount, "GetEntryCount"},
@@ -302,8 +303,9 @@ private:
class IFileSystem final : public ServiceFramework<IFileSystem> {
public:
- explicit IFileSystem(FileSys::VirtualDir backend, SizeGetter size)
- : ServiceFramework("IFileSystem"), backend(std::move(backend)), size(std::move(size)) {
+ explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_)
+ : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, size{std::move(
+ size_)} {
static const FunctionInfo functions[] = {
{0, &IFileSystem::CreateFile, "CreateFile"},
{1, &IFileSystem::DeleteFile, "DeleteFile"},
@@ -411,7 +413,7 @@ public:
const auto mode = static_cast<FileSys::Mode>(rp.Pop<u32>());
- LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, static_cast<u32>(mode));
+ LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode);
auto result = backend.OpenFile(name, mode);
if (result.Failed()) {
@@ -420,7 +422,7 @@ public:
return;
}
- auto file = std::make_shared<IFile>(result.Unwrap());
+ auto file = std::make_shared<IFile>(system, result.Unwrap());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -445,7 +447,7 @@ public:
return;
}
- auto directory = std::make_shared<IDirectory>(result.Unwrap());
+ auto directory = std::make_shared<IDirectory>(system, result.Unwrap());
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -500,8 +502,9 @@ private:
class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
public:
- explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space, FileSystemController& fsc)
- : ServiceFramework("ISaveDataInfoReader"), fsc(fsc) {
+ explicit ISaveDataInfoReader(Core::System& system_, FileSys::SaveDataSpaceId space,
+ FileSystemController& fsc_)
+ : ServiceFramework{system_, "ISaveDataInfoReader"}, fsc{fsc_} {
static const FunctionInfo functions[] = {
{0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
};
@@ -550,8 +553,7 @@ private:
const auto save_root = fsc.OpenSaveDataSpace(space);
if (save_root.Failed() || *save_root == nullptr) {
- LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!",
- static_cast<u8>(space));
+ LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space);
return;
}
@@ -650,8 +652,9 @@ private:
u64 next_entry_index = 0;
};
-FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
- : ServiceFramework("fsp-srv"), fsc(fsc), reporter(reporter) {
+FSP_SRV::FSP_SRV(Core::System& system_)
+ : ServiceFramework{system_, "fsp-srv"}, fsc{system.GetFileSystemController()},
+ content_provider{system.GetContentProvider()}, reporter{system.GetReporter()} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "OpenFileSystem"},
@@ -714,7 +717,7 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
{202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"},
{203, &FSP_SRV::OpenPatchDataStorageByCurrentProcess, "OpenPatchDataStorageByCurrentProcess"},
{204, nullptr, "OpenDataFileSystemByProgramIndex"},
- {205, nullptr, "OpenDataStorageByProgramIndex"},
+ {205, &FSP_SRV::OpenDataStorageWithProgramIndex, "OpenDataStorageWithProgramIndex"},
{400, nullptr, "OpenDeviceOperator"},
{500, nullptr, "OpenSdCardDetectionEventNotifier"},
{501, nullptr, "OpenGameCardDetectionEventNotifier"},
@@ -791,8 +794,7 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) {
const auto type = rp.PopRaw<FileSystemType>();
const auto title_id = rp.PopRaw<u64>();
- LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}",
- static_cast<u8>(type), title_id);
+ LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}", type, title_id);
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
rb.Push(RESULT_UNKNOWN);
@@ -801,8 +803,9 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) {
void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_FS, "called");
- auto filesystem = std::make_shared<IFileSystem>(
- fsc.OpenSDMC().Unwrap(), SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
+ auto filesystem =
+ std::make_shared<IFileSystem>(system, fsc.OpenSDMC().Unwrap(),
+ SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -844,8 +847,7 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
return;
}
- FileSys::StorageId id;
-
+ FileSys::StorageId id{};
switch (parameters.space_id) {
case FileSys::SaveDataSpaceId::NandUser:
id = FileSys::StorageId::NandUser;
@@ -857,10 +859,14 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
case FileSys::SaveDataSpaceId::NandSystem:
id = FileSys::StorageId::NandSystem;
break;
+ case FileSys::SaveDataSpaceId::TemporaryStorage:
+ case FileSys::SaveDataSpaceId::ProperSystem:
+ case FileSys::SaveDataSpaceId::SafeMode:
+ UNREACHABLE();
}
- auto filesystem =
- std::make_shared<IFileSystem>(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id));
+ auto filesystem = std::make_shared<IFileSystem>(system, std::move(dir.Unwrap()),
+ SizeGetter::FromStorageId(fsc, id));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -875,11 +881,12 @@ void FSP_SRV::OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx) {
void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto space = rp.PopRaw<FileSys::SaveDataSpaceId>();
- LOG_INFO(Service_FS, "called, space={}", static_cast<u8>(space));
+ LOG_INFO(Service_FS, "called, space={}", space);
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc));
+ rb.PushIpcInterface<ISaveDataInfoReader>(
+ std::make_shared<ISaveDataInfoReader>(system, space, fsc));
}
void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx) {
@@ -902,7 +909,14 @@ void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(
// Stub this to None for now, backend needs an impl to read/write the SaveDataExtraData
constexpr auto flags = static_cast<u32>(FileSys::SaveDataFlags::None);
- LOG_WARNING(Service_FS, "(STUBBED) called, flags={}", flags);
+ LOG_WARNING(Service_FS,
+ "(STUBBED) called, flags={}, space_id={}, attribute.title_id={:016X}\n"
+ "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n"
+ "attribute.type={}, attribute.rank={}, attribute.index={}",
+ flags, parameters.space_id, parameters.attribute.title_id,
+ parameters.attribute.user_id[1], parameters.attribute.user_id[0],
+ parameters.attribute.save_id, parameters.attribute.type, parameters.attribute.rank,
+ parameters.attribute.index);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
@@ -921,7 +935,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
return;
}
- auto storage = std::make_shared<IStorage>(std::move(romfs.Unwrap()));
+ auto storage = std::make_shared<IStorage>(system, std::move(romfs.Unwrap()));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -935,7 +949,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
const auto title_id = rp.PopRaw<u64>();
LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}",
- static_cast<u8>(storage_id), unknown, title_id);
+ storage_id, unknown, title_id);
auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
@@ -945,23 +959,23 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
if (archive != nullptr) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface(std::make_shared<IStorage>(archive));
+ rb.PushIpcInterface(std::make_shared<IStorage>(system, archive));
return;
}
// TODO(DarkLordZach): Find the right error code to use here
LOG_ERROR(Service_FS,
"could not open data storage with title_id={:016X}, storage_id={:02X}", title_id,
- static_cast<u8>(storage_id));
+ storage_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_UNKNOWN);
return;
}
- FileSys::PatchManager pm{title_id};
+ const FileSys::PatchManager pm{title_id, fsc, content_provider};
auto storage = std::make_shared<IStorage>(
- pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
+ system, pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
@@ -971,21 +985,46 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- auto storage_id = rp.PopRaw<FileSys::StorageId>();
- auto title_id = rp.PopRaw<u64>();
+ const auto storage_id = rp.PopRaw<FileSys::StorageId>();
+ const auto title_id = rp.PopRaw<u64>();
- LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}",
- static_cast<u8>(storage_id), title_id);
+ LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", storage_id, title_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
}
+void FSP_SRV::OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const auto program_index = rp.PopRaw<u8>();
+
+ LOG_DEBUG(Service_FS, "called, program_index={}", program_index);
+
+ auto romfs = fsc.OpenPatchedRomFSWithProgramIndex(
+ system.CurrentProcess()->GetTitleID(), program_index, FileSys::ContentRecordType::Program);
+
+ if (romfs.Failed()) {
+ // TODO: Find the right error code to use here
+ LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_UNKNOWN);
+ return;
+ }
+
+ auto storage = std::make_shared<IStorage>(system, std::move(romfs.Unwrap()));
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IStorage>(std::move(storage));
+}
+
void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
log_mode = rp.PopEnum<LogMode>();
- LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast<u32>(log_mode));
+ LOG_DEBUG(Service_FS, "called, log_mode={:08X}", log_mode);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -1023,7 +1062,8 @@ void FSP_SRV::GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx) {
class IMultiCommitManager final : public ServiceFramework<IMultiCommitManager> {
public:
- explicit IMultiCommitManager() : ServiceFramework("IMultiCommitManager") {
+ explicit IMultiCommitManager(Core::System& system_)
+ : ServiceFramework{system_, "IMultiCommitManager"} {
static const FunctionInfo functions[] = {
{1, &IMultiCommitManager::Add, "Add"},
{2, &IMultiCommitManager::Commit, "Commit"},
@@ -1054,7 +1094,7 @@ void FSP_SRV::OpenMultiCommitManager(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IMultiCommitManager>(std::make_shared<IMultiCommitManager>());
+ rb.PushIpcInterface<IMultiCommitManager>(std::make_shared<IMultiCommitManager>(system));
}
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 4964e874e..8ed933279 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -12,8 +12,9 @@ class Reporter;
}
namespace FileSys {
+class ContentProvider;
class FileSystemBackend;
-}
+} // namespace FileSys
namespace Service::FileSystem {
@@ -32,7 +33,7 @@ enum class LogMode : u32 {
class FSP_SRV final : public ServiceFramework<FSP_SRV> {
public:
- explicit FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter);
+ explicit FSP_SRV(Core::System& system_);
~FSP_SRV() override;
private:
@@ -48,6 +49,7 @@ private:
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
+ void OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx);
void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
@@ -55,6 +57,7 @@ private:
void OpenMultiCommitManager(Kernel::HLERequestContext& ctx);
FileSystemController& fsc;
+ const FileSys::ContentProvider& content_provider;
FileSys::VirtualFile romfs;
u64 current_process_id = 0;
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index b7adaffc7..72a877d68 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -5,9 +5,11 @@
#include <queue>
#include "common/logging/log.h"
#include "common/uuid.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/friend/errors.h"
#include "core/hle/service/friend/friend.h"
#include "core/hle/service/friend/interface.h"
@@ -16,7 +18,7 @@ namespace Service::Friend {
class IFriendService final : public ServiceFramework<IFriendService> {
public:
- IFriendService() : ServiceFramework("IFriendService") {
+ explicit IFriendService(Core::System& system_) : ServiceFramework{system_, "IFriendService"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetCompletionEvent"},
@@ -170,8 +172,8 @@ private:
class INotificationService final : public ServiceFramework<INotificationService> {
public:
- INotificationService(Common::UUID uuid, Core::System& system)
- : ServiceFramework("INotificationService"), uuid(uuid) {
+ explicit INotificationService(Common::UUID uuid_, Core::System& system_)
+ : ServiceFramework{system_, "INotificationService"}, uuid{uuid_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &INotificationService::GetEvent, "GetEvent"},
@@ -182,8 +184,9 @@ public:
RegisterHandlers(functions);
- notification_event = Kernel::WritableEvent::CreateEventPair(
- system.Kernel(), "INotificationService:NotifyEvent");
+ notification_event =
+ Kernel::KEvent::Create(system.Kernel(), "INotificationService:NotifyEvent");
+ notification_event->Initialize();
}
private:
@@ -192,7 +195,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(notification_event.readable);
+ rb.PushCopyObjects(notification_event->GetReadableEvent());
}
void Clear(Kernel::HLERequestContext& ctx) {
@@ -228,8 +231,7 @@ private:
break;
default:
// HOS seems not have an error case for an unknown notification
- LOG_WARNING(Service_ACC, "Unknown notification {:08X}",
- static_cast<u32>(notification.notification_type));
+ LOG_WARNING(Service_ACC, "Unknown notification {:08X}", notification.notification_type);
break;
}
@@ -258,7 +260,7 @@ private:
};
Common::UUID uuid{Common::INVALID_UUID};
- Kernel::EventPair notification_event;
+ std::shared_ptr<Kernel::KEvent> notification_event;
std::queue<SizedNotificationInfo> notifications;
States states{};
};
@@ -266,7 +268,7 @@ private:
void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IFriendService>();
+ rb.PushIpcInterface<IFriendService>(system);
LOG_DEBUG(Service_ACC, "called");
}
@@ -281,8 +283,9 @@ void Module::Interface::CreateNotificationService(Kernel::HLERequestContext& ctx
rb.PushIpcInterface<INotificationService>(uuid, system);
}
-Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
- : ServiceFramework(name), module(std::move(module)), system(system) {}
+Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
+ const char* name)
+ : ServiceFramework{system_, name}, module{std::move(module_)} {}
Module::Interface::~Interface() = default;
diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h
index 24f3fc969..8be3321db 100644
--- a/src/core/hle/service/friend/friend.h
+++ b/src/core/hle/service/friend/friend.h
@@ -16,7 +16,8 @@ class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name);
+ explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
+ const char* name);
~Interface() override;
void CreateFriendService(Kernel::HLERequestContext& ctx);
@@ -24,7 +25,6 @@ public:
protected:
std::shared_ptr<Module> module;
- Core::System& system;
};
};
diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp
index 58155f652..7368ccec2 100644
--- a/src/core/hle/service/friend/interface.cpp
+++ b/src/core/hle/service/friend/interface.cpp
@@ -6,8 +6,8 @@
namespace Service::Friend {
-Friend::Friend(std::shared_ptr<Module> module, Core::System& system, const char* name)
- : Interface(std::move(module), system, name) {
+Friend::Friend(std::shared_ptr<Module> module_, Core::System& system_, const char* name)
+ : Interface(std::move(module_), system_, name) {
static const FunctionInfo functions[] = {
{0, &Friend::CreateFriendService, "CreateFriendService"},
{1, &Friend::CreateNotificationService, "CreateNotificationService"},
diff --git a/src/core/hle/service/friend/interface.h b/src/core/hle/service/friend/interface.h
index 465a35770..43d914b32 100644
--- a/src/core/hle/service/friend/interface.h
+++ b/src/core/hle/service/friend/interface.h
@@ -10,7 +10,7 @@ namespace Service::Friend {
class Friend final : public Module::Interface {
public:
- explicit Friend(std::shared_ptr<Module> module, Core::System& system, const char* name);
+ explicit Friend(std::shared_ptr<Module> module_, Core::System& system_, const char* name);
~Friend() override;
};
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
index b591ce31b..fc77e7286 100644
--- a/src/core/hle/service/glue/arp.cpp
+++ b/src/core/hle/service/glue/arp.cpp
@@ -5,6 +5,7 @@
#include <memory>
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
@@ -32,8 +33,8 @@ std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 proces
}
} // Anonymous namespace
-ARP_R::ARP_R(const Core::System& system, const ARPManager& manager)
- : ServiceFramework{"arp:r"}, system(system), manager(manager) {
+ARP_R::ARP_R(Core::System& system_, const ARPManager& manager_)
+ : ServiceFramework{system_, "arp:r"}, manager{manager_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"},
@@ -151,8 +152,9 @@ class IRegistrar final : public ServiceFramework<IRegistrar> {
public:
explicit IRegistrar(
+ Core::System& system_,
std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issuer)
- : ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) {
+ : ServiceFramework{system_, "IRegistrar"}, issue_process_id{std::move(issuer)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IRegistrar::Issue, "Issue"},
@@ -236,8 +238,8 @@ private:
std::vector<u8> control;
};
-ARP_W::ARP_W(const Core::System& system, ARPManager& manager)
- : ServiceFramework{"arp:w"}, system(system), manager(manager) {
+ARP_W::ARP_W(Core::System& system_, ARPManager& manager_)
+ : ServiceFramework{system_, "arp:w"}, manager{manager_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"},
@@ -254,7 +256,7 @@ void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_ARP, "called");
registrar = std::make_shared<IRegistrar>(
- [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) {
+ system, [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) {
const auto res = GetTitleIDForProcessID(system, process_id);
if (!res.has_value()) {
return ERR_NOT_REGISTERED;
diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h
index d5f8a7e7a..34b412e26 100644
--- a/src/core/hle/service/glue/arp.h
+++ b/src/core/hle/service/glue/arp.h
@@ -13,7 +13,7 @@ class IRegistrar;
class ARP_R final : public ServiceFramework<ARP_R> {
public:
- explicit ARP_R(const Core::System& system, const ARPManager& manager);
+ explicit ARP_R(Core::System& system_, const ARPManager& manager_);
~ARP_R() override;
private:
@@ -22,20 +22,18 @@ private:
void GetApplicationControlProperty(Kernel::HLERequestContext& ctx);
void GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx);
- const Core::System& system;
const ARPManager& manager;
};
class ARP_W final : public ServiceFramework<ARP_W> {
public:
- explicit ARP_W(const Core::System& system, ARPManager& manager);
+ explicit ARP_W(Core::System& system_, ARPManager& manager_);
~ARP_W() override;
private:
void AcquireRegistrar(Kernel::HLERequestContext& ctx);
void DeleteProperties(Kernel::HLERequestContext& ctx);
- const Core::System& system;
ARPManager& manager;
std::shared_ptr<IRegistrar> registrar;
};
diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp
index cd89d088f..a478b68e1 100644
--- a/src/core/hle/service/glue/bgtc.cpp
+++ b/src/core/hle/service/glue/bgtc.cpp
@@ -6,7 +6,7 @@
namespace Service::Glue {
-BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} {
+BGTC_T::BGTC_T(Core::System& system_) : ServiceFramework{system_, "bgtc:t"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "NotifyTaskStarting"},
@@ -31,7 +31,7 @@ BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} {
BGTC_T::~BGTC_T() = default;
-BGTC_SC::BGTC_SC() : ServiceFramework{"bgtc:sc"} {
+BGTC_SC::BGTC_SC(Core::System& system_) : ServiceFramework{system_, "bgtc:sc"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "GetState"},
diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h
index 81844f03e..906116ba6 100644
--- a/src/core/hle/service/glue/bgtc.h
+++ b/src/core/hle/service/glue/bgtc.h
@@ -6,17 +6,21 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Glue {
class BGTC_T final : public ServiceFramework<BGTC_T> {
public:
- BGTC_T();
+ explicit BGTC_T(Core::System& system_);
~BGTC_T() override;
};
class BGTC_SC final : public ServiceFramework<BGTC_SC> {
public:
- BGTC_SC();
+ explicit BGTC_SC(Core::System& system_);
~BGTC_SC() override;
};
diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp
index c728e815c..4eafbe5fa 100644
--- a/src/core/hle/service/glue/glue.cpp
+++ b/src/core/hle/service/glue/glue.cpp
@@ -18,8 +18,8 @@ void InstallInterfaces(Core::System& system) {
->InstallAsService(system.ServiceManager());
// BackGround Task Controller
- std::make_shared<BGTC_T>()->InstallAsService(system.ServiceManager());
- std::make_shared<BGTC_SC>()->InstallAsService(system.ServiceManager());
+ std::make_shared<BGTC_T>(system)->InstallAsService(system.ServiceManager());
+ std::make_shared<BGTC_SC>(system)->InstallAsService(system.ServiceManager());
}
} // namespace Service::Glue
diff --git a/src/core/hle/service/grc/grc.cpp b/src/core/hle/service/grc/grc.cpp
index 401e0b208..a502ab47f 100644
--- a/src/core/hle/service/grc/grc.cpp
+++ b/src/core/hle/service/grc/grc.cpp
@@ -12,7 +12,7 @@ namespace Service::GRC {
class GRC final : public ServiceFramework<GRC> {
public:
- explicit GRC() : ServiceFramework{"grc:c"} {
+ explicit GRC(Core::System& system) : ServiceFramework{system, "grc:c"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "OpenContinuousRecorder"},
@@ -27,8 +27,8 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<GRC>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<GRC>(system)->InstallAsService(sm);
}
} // namespace Service::GRC
diff --git a/src/core/hle/service/grc/grc.h b/src/core/hle/service/grc/grc.h
index e0d29e70d..9069fe756 100644
--- a/src/core/hle/service/grc/grc.h
+++ b/src/core/hle/service/grc/grc.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::GRC {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::GRC
diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h
index 8bc69c372..f47a9e61c 100644
--- a/src/core/hle/service/hid/controllers/controller_base.h
+++ b/src/core/hle/service/hid/controllers/controller_base.h
@@ -31,6 +31,10 @@ public:
virtual void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
std::size_t size) = 0;
+ // When the controller is requesting a motion update for the shared memory
+ virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
+ std::size_t size) {}
+
// Called when input devices should be loaded
virtual void OnLoadInputDevices() = 0;
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
index 0b896d5ad..c4a59147d 100644
--- a/src/core/hle/service/hid/controllers/keyboard.cpp
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -39,16 +39,25 @@ void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing,
cur_entry.sampling_number2 = cur_entry.sampling_number;
cur_entry.key.fill(0);
- cur_entry.modifier = 0;
if (Settings::values.keyboard_enabled) {
for (std::size_t i = 0; i < keyboard_keys.size(); ++i) {
- cur_entry.key[i / KEYS_PER_BYTE] |=
- (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE));
+ auto& entry = cur_entry.key[i / KEYS_PER_BYTE];
+ entry = static_cast<u8>(entry | (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE)));
}
- for (std::size_t i = 0; i < keyboard_mods.size(); ++i) {
- cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i);
- }
+ using namespace Settings::NativeKeyboard;
+
+ // TODO: Assign the correct key to all modifiers
+ cur_entry.modifier.control.Assign(keyboard_mods[LeftControl]->GetStatus());
+ cur_entry.modifier.shift.Assign(keyboard_mods[LeftShift]->GetStatus());
+ cur_entry.modifier.left_alt.Assign(keyboard_mods[LeftAlt]->GetStatus());
+ cur_entry.modifier.right_alt.Assign(keyboard_mods[RightAlt]->GetStatus());
+ cur_entry.modifier.gui.Assign(0);
+ cur_entry.modifier.caps_lock.Assign(keyboard_mods[CapsLock]->GetStatus());
+ cur_entry.modifier.scroll_lock.Assign(keyboard_mods[ScrollLock]->GetStatus());
+ cur_entry.modifier.num_lock.Assign(keyboard_mods[NumLock]->GetStatus());
+ cur_entry.modifier.katakana.Assign(0);
+ cur_entry.modifier.hiragana.Assign(0);
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
}
diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h
index f3eef5936..b5b281752 100644
--- a/src/core/hle/service/hid/controllers/keyboard.h
+++ b/src/core/hle/service/hid/controllers/keyboard.h
@@ -5,6 +5,7 @@
#pragma once
#include <array>
+#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -31,12 +32,28 @@ public:
void OnLoadInputDevices() override;
private:
+ struct Modifiers {
+ union {
+ u32_le raw{};
+ BitField<0, 1, u32> control;
+ BitField<1, 1, u32> shift;
+ BitField<2, 1, u32> left_alt;
+ BitField<3, 1, u32> right_alt;
+ BitField<4, 1, u32> gui;
+ BitField<8, 1, u32> caps_lock;
+ BitField<9, 1, u32> scroll_lock;
+ BitField<10, 1, u32> num_lock;
+ BitField<11, 1, u32> katakana;
+ BitField<12, 1, u32> hiragana;
+ };
+ };
+ static_assert(sizeof(Modifiers) == 0x4, "Modifiers is an invalid size");
+
struct KeyboardState {
s64_le sampling_number;
s64_le sampling_number2;
- s32_le modifier;
- s32_le attribute;
+ Modifiers modifier;
std::array<u8, 32> key;
};
static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size");
diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp
index ac40989c5..2e7457604 100644
--- a/src/core/hle/service/hid/controllers/mouse.cpp
+++ b/src/core/hle/service/hid/controllers/mouse.cpp
@@ -36,6 +36,7 @@ void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
+ cur_entry.attribute.raw = 0;
if (Settings::values.mouse_enabled) {
const auto [px, py, sx, sy] = mouse_device->GetStatus();
const auto x = static_cast<s32>(px * Layout::ScreenUndocked::Width);
@@ -46,10 +47,14 @@ void Controller_Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
cur_entry.delta_y = y - last_entry.y;
cur_entry.mouse_wheel_x = sx;
cur_entry.mouse_wheel_y = sy;
+ cur_entry.attribute.is_connected.Assign(1);
- for (std::size_t i = 0; i < mouse_button_devices.size(); ++i) {
- cur_entry.button |= (mouse_button_devices[i]->GetStatus() << i);
- }
+ using namespace Settings::NativeMouseButton;
+ cur_entry.button.left.Assign(mouse_button_devices[Left]->GetStatus());
+ cur_entry.button.right.Assign(mouse_button_devices[Right]->GetStatus());
+ cur_entry.button.middle.Assign(mouse_button_devices[Middle]->GetStatus());
+ cur_entry.button.forward.Assign(mouse_button_devices[Forward]->GetStatus());
+ cur_entry.button.back.Assign(mouse_button_devices[Back]->GetStatus());
}
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h
index 357ab7107..3b432a36e 100644
--- a/src/core/hle/service/hid/controllers/mouse.h
+++ b/src/core/hle/service/hid/controllers/mouse.h
@@ -5,6 +5,7 @@
#pragma once
#include <array>
+#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/frontend/input.h"
@@ -30,6 +31,27 @@ public:
void OnLoadInputDevices() override;
private:
+ struct Buttons {
+ union {
+ u32_le raw{};
+ BitField<0, 1, u32> left;
+ BitField<1, 1, u32> right;
+ BitField<2, 1, u32> middle;
+ BitField<3, 1, u32> forward;
+ BitField<4, 1, u32> back;
+ };
+ };
+ static_assert(sizeof(Buttons) == 0x4, "Buttons is an invalid size");
+
+ struct Attributes {
+ union {
+ u32_le raw{};
+ BitField<0, 1, u32> transferable;
+ BitField<1, 1, u32> is_connected;
+ };
+ };
+ static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
+
struct MouseState {
s64_le sampling_number;
s64_le sampling_number2;
@@ -39,8 +61,8 @@ private:
s32_le delta_y;
s32_le mouse_wheel_x;
s32_le mouse_wheel_y;
- s32_le button;
- s32_le attribute;
+ Buttons button;
+ Attributes attribute;
};
static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size");
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 45fde8df2..70b9f3824 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -12,14 +12,16 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/input.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/settings.h"
namespace Service::HID {
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
+constexpr s32 HID_TRIGGER_MAX = 0x7fff;
[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
constexpr std::size_t NPAD_OFFSET = 0x9A00;
constexpr u32 BATTERY_FULL = 2;
@@ -47,6 +49,8 @@ Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad(
return NPadControllerType::JoyRight;
case Settings::ControllerType::Handheld:
return NPadControllerType::Handheld;
+ case Settings::ControllerType::GameCube:
+ return NPadControllerType::GameCube;
default:
UNREACHABLE();
return NPadControllerType::ProController;
@@ -66,6 +70,8 @@ Settings::ControllerType Controller_NPad::MapNPadToSettingsType(
return Settings::ControllerType::RightJoycon;
case NPadControllerType::Handheld:
return Settings::ControllerType::Handheld;
+ case NPadControllerType::GameCube:
+ return Settings::ControllerType::GameCube;
default:
UNREACHABLE();
return Settings::ControllerType::ProController;
@@ -116,113 +122,180 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) {
}
}
-Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {}
-Controller_NPad::~Controller_NPad() = default;
+bool Controller_NPad::IsNpadIdValid(u32 npad_id) {
+ switch (npad_id) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case NPAD_UNKNOWN:
+ case NPAD_HANDHELD:
+ return true;
+ default:
+ LOG_ERROR(Service_HID, "Invalid npad id {}", npad_id);
+ return false;
+ }
+}
+
+bool Controller_NPad::IsDeviceHandleValid(const DeviceHandle& device_handle) {
+ return IsNpadIdValid(device_handle.npad_id) &&
+ device_handle.npad_type < NpadType::MaxNpadType &&
+ device_handle.device_index < DeviceIndex::MaxDeviceIndex;
+}
+
+Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {
+ latest_vibration_values.fill({DEFAULT_VIBRATION_VALUE, DEFAULT_VIBRATION_VALUE});
+}
+
+Controller_NPad::~Controller_NPad() {
+ OnRelease();
+}
void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
const auto controller_type = connected_controllers[controller_idx].type;
auto& controller = shared_memory_entries[controller_idx];
if (controller_type == NPadControllerType::None) {
- styleset_changed_events[controller_idx].writable->Signal();
+ styleset_changed_events[controller_idx]->GetWritableEvent()->Signal();
return;
}
- controller.joy_styles.raw = 0; // Zero out
+ controller.style_set.raw = 0; // Zero out
controller.device_type.raw = 0;
- controller.properties.raw = 0;
+ controller.system_properties.raw = 0;
switch (controller_type) {
case NPadControllerType::None:
UNREACHABLE();
break;
case NPadControllerType::ProController:
- controller.joy_styles.pro_controller.Assign(1);
- controller.device_type.pro_controller.Assign(1);
- controller.properties.is_vertical.Assign(1);
- controller.properties.use_plus.Assign(1);
- controller.properties.use_minus.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
+ controller.style_set.fullkey.Assign(1);
+ controller.device_type.fullkey.Assign(1);
+ controller.system_properties.is_vertical.Assign(1);
+ controller.system_properties.use_plus.Assign(1);
+ controller.system_properties.use_minus.Assign(1);
+ controller.assignment_mode = NpadAssignments::Single;
+ controller.footer_type = AppletFooterUiType::SwitchProController;
break;
case NPadControllerType::Handheld:
- controller.joy_styles.handheld.Assign(1);
- controller.device_type.handheld.Assign(1);
- controller.properties.is_vertical.Assign(1);
- controller.properties.use_plus.Assign(1);
- controller.properties.use_minus.Assign(1);
- controller.pad_assignment = NPadAssignments::Dual;
+ controller.style_set.handheld.Assign(1);
+ controller.device_type.handheld_left.Assign(1);
+ controller.device_type.handheld_right.Assign(1);
+ controller.system_properties.is_vertical.Assign(1);
+ controller.system_properties.use_plus.Assign(1);
+ controller.system_properties.use_minus.Assign(1);
+ controller.assignment_mode = NpadAssignments::Dual;
+ controller.footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
break;
case NPadControllerType::JoyDual:
- controller.joy_styles.joycon_dual.Assign(1);
+ controller.style_set.joycon_dual.Assign(1);
controller.device_type.joycon_left.Assign(1);
controller.device_type.joycon_right.Assign(1);
- controller.properties.is_vertical.Assign(1);
- controller.properties.use_plus.Assign(1);
- controller.properties.use_minus.Assign(1);
- controller.pad_assignment = NPadAssignments::Dual;
+ controller.system_properties.is_vertical.Assign(1);
+ controller.system_properties.use_plus.Assign(1);
+ controller.system_properties.use_minus.Assign(1);
+ controller.assignment_mode = NpadAssignments::Dual;
+ controller.footer_type = AppletFooterUiType::JoyDual;
break;
case NPadControllerType::JoyLeft:
- controller.joy_styles.joycon_left.Assign(1);
+ controller.style_set.joycon_left.Assign(1);
controller.device_type.joycon_left.Assign(1);
- controller.properties.is_horizontal.Assign(1);
- controller.properties.use_minus.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
+ controller.system_properties.is_horizontal.Assign(1);
+ controller.system_properties.use_minus.Assign(1);
+ controller.assignment_mode = NpadAssignments::Single;
+ controller.footer_type = AppletFooterUiType::JoyLeftHorizontal;
break;
case NPadControllerType::JoyRight:
- controller.joy_styles.joycon_right.Assign(1);
+ controller.style_set.joycon_right.Assign(1);
controller.device_type.joycon_right.Assign(1);
- controller.properties.is_horizontal.Assign(1);
- controller.properties.use_plus.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
+ controller.system_properties.is_horizontal.Assign(1);
+ controller.system_properties.use_plus.Assign(1);
+ controller.assignment_mode = NpadAssignments::Single;
+ controller.footer_type = AppletFooterUiType::JoyRightHorizontal;
+ break;
+ case NPadControllerType::GameCube:
+ controller.style_set.gamecube.Assign(1);
+ // The GC Controller behaves like a wired Pro Controller
+ controller.device_type.fullkey.Assign(1);
+ controller.system_properties.is_vertical.Assign(1);
+ controller.system_properties.use_plus.Assign(1);
break;
case NPadControllerType::Pokeball:
- controller.joy_styles.pokeball.Assign(1);
- controller.device_type.pokeball.Assign(1);
- controller.pad_assignment = NPadAssignments::Single;
+ controller.style_set.palma.Assign(1);
+ controller.device_type.palma.Assign(1);
+ controller.assignment_mode = NpadAssignments::Single;
break;
}
- controller.single_color_error = ColorReadError::ReadOk;
- controller.single_color.body_color = 0;
- controller.single_color.button_color = 0;
-
- controller.dual_color_error = ColorReadError::ReadOk;
- controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left;
- controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left;
- controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right;
- controller.right_color.button_color =
- Settings::values.players[controller_idx].button_color_right;
-
- controller.battery_level[0] = BATTERY_FULL;
- controller.battery_level[1] = BATTERY_FULL;
- controller.battery_level[2] = BATTERY_FULL;
- styleset_changed_events[controller_idx].writable->Signal();
+ controller.fullkey_color.attribute = ColorAttributes::Ok;
+ controller.fullkey_color.fullkey.body = 0;
+ controller.fullkey_color.fullkey.button = 0;
+
+ controller.joycon_color.attribute = ColorAttributes::Ok;
+ controller.joycon_color.left.body =
+ Settings::values.players.GetValue()[controller_idx].body_color_left;
+ controller.joycon_color.left.button =
+ Settings::values.players.GetValue()[controller_idx].button_color_left;
+ controller.joycon_color.right.body =
+ Settings::values.players.GetValue()[controller_idx].body_color_right;
+ controller.joycon_color.right.button =
+ Settings::values.players.GetValue()[controller_idx].button_color_right;
+
+ // TODO: Investigate when we should report all batery types
+ controller.battery_level_dual = BATTERY_FULL;
+ controller.battery_level_left = BATTERY_FULL;
+ controller.battery_level_right = BATTERY_FULL;
+
+ SignalStyleSetChangedEvent(IndexToNPad(controller_idx));
}
void Controller_NPad::OnInit() {
auto& kernel = system.Kernel();
- for (std::size_t i = 0; i < styleset_changed_events.size(); i++) {
- styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair(
- kernel, fmt::format("npad:NpadStyleSetChanged_{}", i));
+ for (std::size_t i = 0; i < styleset_changed_events.size(); ++i) {
+ styleset_changed_events[i] =
+ Kernel::KEvent::Create(kernel, fmt::format("npad:NpadStyleSetChanged_{}", i));
+ styleset_changed_events[i]->Initialize();
}
if (!IsControllerActivated()) {
return;
}
+ OnLoadInputDevices();
+
if (style.raw == 0) {
// We want to support all controllers
style.handheld.Assign(1);
style.joycon_left.Assign(1);
style.joycon_right.Assign(1);
style.joycon_dual.Assign(1);
- style.pro_controller.Assign(1);
- style.pokeball.Assign(1);
+ style.fullkey.Assign(1);
+ style.gamecube.Assign(1);
+ style.palma.Assign(1);
}
- std::transform(Settings::values.players.begin(), Settings::values.players.end(),
- connected_controllers.begin(), [](const Settings::PlayerInput& player) {
+ std::transform(Settings::values.players.GetValue().begin(),
+ Settings::values.players.GetValue().end(), connected_controllers.begin(),
+ [](const Settings::PlayerInput& player) {
return ControllerHolder{MapSettingsTypeToNPad(player.controller_type),
player.connected};
});
+ // Connect the Player 1 or Handheld controller if none are connected.
+ if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
+ [](const ControllerHolder& controller) { return controller.is_connected; })) {
+ const auto controller =
+ MapSettingsTypeToNPad(Settings::values.players.GetValue()[0].controller_type);
+ if (controller == NPadControllerType::Handheld) {
+ Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true;
+ connected_controllers[HANDHELD_INDEX] = {controller, true};
+ } else {
+ Settings::values.players.GetValue()[0].connected = true;
+ connected_controllers[0] = {controller, true};
+ }
+ }
+
// Account for handheld
if (connected_controllers[HANDHELD_INDEX].is_connected) {
connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld;
@@ -241,7 +314,7 @@ void Controller_NPad::OnInit() {
}
void Controller_NPad::OnLoadInputDevices() {
- const auto& players = Settings::values.players;
+ const auto& players = Settings::values.players.GetValue();
for (std::size_t i = 0; i < players.size(); ++i) {
std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
@@ -249,20 +322,37 @@ void Controller_NPad::OnLoadInputDevices() {
std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>);
+ std::transform(players[i].vibrations.begin() +
+ Settings::NativeVibration::VIBRATION_HID_BEGIN,
+ players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END,
+ vibrations[i].begin(), Input::CreateDevice<Input::VibrationDevice>);
+ std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
+ players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END,
+ motions[i].begin(), Input::CreateDevice<Input::MotionDevice>);
+ for (std::size_t device_idx = 0; device_idx < vibrations[i].size(); ++device_idx) {
+ InitializeVibrationDeviceAtIndex(i, device_idx);
+ }
}
}
-void Controller_NPad::OnRelease() {}
+void Controller_NPad::OnRelease() {
+ for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) {
+ for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) {
+ VibrateControllerAtIndex(npad_idx, device_idx, {});
+ }
+ }
+}
void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
const auto controller_idx = NPadIdToIndex(npad_id);
- [[maybe_unused]] const auto controller_type = connected_controllers[controller_idx].type;
+ const auto controller_type = connected_controllers[controller_idx].type;
if (!connected_controllers[controller_idx].is_connected) {
return;
}
auto& pad_state = npad_pad_states[controller_idx].pad_states;
auto& lstick_entry = npad_pad_states[controller_idx].l_stick;
auto& rstick_entry = npad_pad_states[controller_idx].r_stick;
+ auto& trigger_entry = npad_trigger_states[controller_idx];
const auto& button_state = buttons[controller_idx];
const auto& analog_state = sticks[controller_idx];
const auto [stick_l_x_f, stick_l_y_f] =
@@ -271,54 +361,74 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
using namespace Settings::NativeButton;
- pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus());
-
- pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus());
-
- pad_state.l_stick_right.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
- Input::AnalogDirection::RIGHT));
- pad_state.l_stick_left.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
- Input::AnalogDirection::LEFT));
- pad_state.l_stick_up.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
- Input::AnalogDirection::UP));
- pad_state.l_stick_down.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus(
- Input::AnalogDirection::DOWN));
-
- pad_state.r_stick_right.Assign(
- analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
- ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT));
- pad_state.r_stick_left.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
- ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT));
- pad_state.r_stick_up.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
- ->GetAnalogDirectionStatus(Input::AnalogDirection::UP));
- pad_state.r_stick_down.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
- ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN));
-
- pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
- pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
-
- lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
- lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
- rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
- rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ if (controller_type != NPadControllerType::JoyLeft) {
+ pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.r_stick_right.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT));
+ pad_state.r_stick_left.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT));
+ pad_state.r_stick_up.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::UP));
+ pad_state.r_stick_down.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN));
+ rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
+ rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ }
+
+ if (controller_type != NPadControllerType::JoyRight) {
+ pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.l_stick_right.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT));
+ pad_state.l_stick_left.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT));
+ pad_state.l_stick_up.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::UP));
+ pad_state.l_stick_down.Assign(
+ analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]
+ ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN));
+ lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
+ lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
+ }
+
+ if (controller_type == NPadControllerType::JoyLeft ||
+ controller_type == NPadControllerType::JoyRight) {
+ pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
+ }
+
+ if (controller_type == NPadControllerType::GameCube) {
+ trigger_entry.l_analog = static_cast<s32>(
+ button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0);
+ trigger_entry.r_analog = static_cast<s32>(
+ button_state[ZR - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0);
+ pad_state.zl.Assign(false);
+ pad_state.zr.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ }
}
void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
@@ -326,15 +436,17 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
if (!IsControllerActivated()) {
return;
}
- for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
+ for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) {
auto& npad = shared_memory_entries[i];
- const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
- &npad.handheld_states,
- &npad.dual_states,
- &npad.left_joy_states,
- &npad.right_joy_states,
- &npad.pokeball_states,
- &npad.libnx};
+ const std::array<NPadGeneric*, 7> controller_npads{
+ &npad.fullkey_states, &npad.handheld_states, &npad.joy_dual_states,
+ &npad.joy_left_states, &npad.joy_right_states, &npad.palma_states,
+ &npad.system_ext_states};
+
+ // There is the posibility to have more controllers with analog triggers
+ const std::array<TriggerGeneric*, 1> controller_triggers{
+ &npad.gc_trigger_states,
+ };
for (auto* main_controller : controller_npads) {
main_controller->common.entry_count = 16;
@@ -353,29 +465,48 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
cur_entry.timestamp2 = cur_entry.timestamp;
}
+ for (auto* analog_trigger : controller_triggers) {
+ analog_trigger->entry_count = 16;
+ analog_trigger->total_entry_count = 17;
+
+ const auto& last_entry = analog_trigger->trigger[analog_trigger->last_entry_index];
+
+ analog_trigger->timestamp = core_timing.GetCPUTicks();
+ analog_trigger->last_entry_index = (analog_trigger->last_entry_index + 1) % 17;
+
+ auto& cur_entry = analog_trigger->trigger[analog_trigger->last_entry_index];
+
+ cur_entry.timestamp = last_entry.timestamp + 1;
+ cur_entry.timestamp2 = cur_entry.timestamp;
+ }
+
const auto& controller_type = connected_controllers[i].type;
if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
continue;
}
const u32 npad_index = static_cast<u32>(i);
+
RequestPadStateUpdate(npad_index);
auto& pad_state = npad_pad_states[npad_index];
+ auto& trigger_state = npad_trigger_states[npad_index];
auto& main_controller =
- npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index];
+ npad.fullkey_states.npad[npad.fullkey_states.common.last_entry_index];
auto& handheld_entry =
npad.handheld_states.npad[npad.handheld_states.common.last_entry_index];
- auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index];
- auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index];
+ auto& dual_entry = npad.joy_dual_states.npad[npad.joy_dual_states.common.last_entry_index];
+ auto& left_entry = npad.joy_left_states.npad[npad.joy_left_states.common.last_entry_index];
auto& right_entry =
- npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index];
- auto& pokeball_entry =
- npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index];
- auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index];
+ npad.joy_right_states.npad[npad.joy_right_states.common.last_entry_index];
+ auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index];
+ auto& libnx_entry =
+ npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index];
+ auto& trigger_entry =
+ npad.gc_trigger_states.trigger[npad.gc_trigger_states.last_entry_index];
libnx_entry.connection_status.raw = 0;
- libnx_entry.connection_status.IsConnected.Assign(1);
+ libnx_entry.connection_status.is_connected.Assign(1);
switch (controller_type) {
case NPadControllerType::None:
@@ -383,67 +514,79 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
break;
case NPadControllerType::ProController:
main_controller.connection_status.raw = 0;
- main_controller.connection_status.IsConnected.Assign(1);
- main_controller.connection_status.IsWired.Assign(1);
+ main_controller.connection_status.is_connected.Assign(1);
+ main_controller.connection_status.is_wired.Assign(1);
main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
main_controller.pad.l_stick = pad_state.l_stick;
main_controller.pad.r_stick = pad_state.r_stick;
- libnx_entry.connection_status.IsWired.Assign(1);
+ libnx_entry.connection_status.is_wired.Assign(1);
break;
case NPadControllerType::Handheld:
handheld_entry.connection_status.raw = 0;
- handheld_entry.connection_status.IsConnected.Assign(1);
- handheld_entry.connection_status.IsWired.Assign(1);
- handheld_entry.connection_status.IsLeftJoyConnected.Assign(1);
- handheld_entry.connection_status.IsRightJoyConnected.Assign(1);
- handheld_entry.connection_status.IsLeftJoyWired.Assign(1);
- handheld_entry.connection_status.IsRightJoyWired.Assign(1);
+ handheld_entry.connection_status.is_connected.Assign(1);
+ handheld_entry.connection_status.is_wired.Assign(1);
+ handheld_entry.connection_status.is_left_connected.Assign(1);
+ handheld_entry.connection_status.is_right_connected.Assign(1);
+ handheld_entry.connection_status.is_left_wired.Assign(1);
+ handheld_entry.connection_status.is_right_wired.Assign(1);
handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw;
handheld_entry.pad.l_stick = pad_state.l_stick;
handheld_entry.pad.r_stick = pad_state.r_stick;
- libnx_entry.connection_status.IsWired.Assign(1);
- libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
- libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
- libnx_entry.connection_status.IsLeftJoyWired.Assign(1);
- libnx_entry.connection_status.IsRightJoyWired.Assign(1);
+ libnx_entry.connection_status.is_wired.Assign(1);
+ libnx_entry.connection_status.is_left_connected.Assign(1);
+ libnx_entry.connection_status.is_right_connected.Assign(1);
+ libnx_entry.connection_status.is_left_wired.Assign(1);
+ libnx_entry.connection_status.is_right_wired.Assign(1);
break;
case NPadControllerType::JoyDual:
dual_entry.connection_status.raw = 0;
- dual_entry.connection_status.IsConnected.Assign(1);
- dual_entry.connection_status.IsLeftJoyConnected.Assign(1);
- dual_entry.connection_status.IsRightJoyConnected.Assign(1);
+ dual_entry.connection_status.is_connected.Assign(1);
+ dual_entry.connection_status.is_left_connected.Assign(1);
+ dual_entry.connection_status.is_right_connected.Assign(1);
dual_entry.pad.pad_states.raw = pad_state.pad_states.raw;
dual_entry.pad.l_stick = pad_state.l_stick;
dual_entry.pad.r_stick = pad_state.r_stick;
- libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
- libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
+ libnx_entry.connection_status.is_left_connected.Assign(1);
+ libnx_entry.connection_status.is_right_connected.Assign(1);
break;
case NPadControllerType::JoyLeft:
left_entry.connection_status.raw = 0;
- left_entry.connection_status.IsConnected.Assign(1);
- left_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ left_entry.connection_status.is_connected.Assign(1);
+ left_entry.connection_status.is_left_connected.Assign(1);
left_entry.pad.pad_states.raw = pad_state.pad_states.raw;
left_entry.pad.l_stick = pad_state.l_stick;
left_entry.pad.r_stick = pad_state.r_stick;
- libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ libnx_entry.connection_status.is_left_connected.Assign(1);
break;
case NPadControllerType::JoyRight:
right_entry.connection_status.raw = 0;
- right_entry.connection_status.IsConnected.Assign(1);
- right_entry.connection_status.IsRightJoyConnected.Assign(1);
+ right_entry.connection_status.is_connected.Assign(1);
+ right_entry.connection_status.is_right_connected.Assign(1);
right_entry.pad.pad_states.raw = pad_state.pad_states.raw;
right_entry.pad.l_stick = pad_state.l_stick;
right_entry.pad.r_stick = pad_state.r_stick;
- libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
+ libnx_entry.connection_status.is_right_connected.Assign(1);
+ break;
+ case NPadControllerType::GameCube:
+ main_controller.connection_status.raw = 0;
+ main_controller.connection_status.is_connected.Assign(1);
+ main_controller.connection_status.is_wired.Assign(1);
+ main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
+ main_controller.pad.l_stick = pad_state.l_stick;
+ main_controller.pad.r_stick = pad_state.r_stick;
+ trigger_entry.l_analog = trigger_state.l_analog;
+ trigger_entry.r_analog = trigger_state.r_analog;
+
+ libnx_entry.connection_status.is_wired.Assign(1);
break;
case NPadControllerType::Pokeball:
pokeball_entry.connection_status.raw = 0;
- pokeball_entry.connection_status.IsConnected.Assign(1);
+ pokeball_entry.connection_status.is_connected.Assign(1);
pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw;
pokeball_entry.pad.l_stick = pad_state.l_stick;
pokeball_entry.pad.r_stick = pad_state.r_stick;
@@ -462,15 +605,153 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
shared_memory_entries.size() * sizeof(NPadEntry));
}
-void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
+void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
+ std::size_t data_len) {
+ if (!IsControllerActivated()) {
+ return;
+ }
+ for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) {
+ auto& npad = shared_memory_entries[i];
+
+ const auto& controller_type = connected_controllers[i].type;
+
+ if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
+ continue;
+ }
+
+ const std::array<SixAxisGeneric*, 6> controller_sixaxes{
+ &npad.sixaxis_fullkey, &npad.sixaxis_handheld, &npad.sixaxis_dual_left,
+ &npad.sixaxis_dual_right, &npad.sixaxis_left, &npad.sixaxis_right,
+ };
+
+ for (auto* sixaxis_sensor : controller_sixaxes) {
+ sixaxis_sensor->common.entry_count = 16;
+ sixaxis_sensor->common.total_entry_count = 17;
+
+ const auto& last_entry =
+ sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index];
+
+ sixaxis_sensor->common.timestamp = core_timing.GetCPUTicks();
+ sixaxis_sensor->common.last_entry_index =
+ (sixaxis_sensor->common.last_entry_index + 1) % 17;
+
+ auto& cur_entry = sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index];
+
+ cur_entry.timestamp = last_entry.timestamp + 1;
+ cur_entry.timestamp2 = cur_entry.timestamp;
+ }
+
+ // Try to read sixaxis sensor states
+ std::array<MotionDevice, 2> motion_devices;
+
+ if (sixaxis_sensors_enabled && Settings::values.motion_enabled.GetValue()) {
+ sixaxis_at_rest = true;
+ for (std::size_t e = 0; e < motion_devices.size(); ++e) {
+ const auto& device = motions[i][e];
+ if (device) {
+ std::tie(motion_devices[e].accel, motion_devices[e].gyro,
+ motion_devices[e].rotation, motion_devices[e].orientation) =
+ device->GetStatus();
+ sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f;
+ }
+ }
+ }
+
+ auto& full_sixaxis_entry =
+ npad.sixaxis_fullkey.sixaxis[npad.sixaxis_fullkey.common.last_entry_index];
+ auto& handheld_sixaxis_entry =
+ npad.sixaxis_handheld.sixaxis[npad.sixaxis_handheld.common.last_entry_index];
+ auto& dual_left_sixaxis_entry =
+ npad.sixaxis_dual_left.sixaxis[npad.sixaxis_dual_left.common.last_entry_index];
+ auto& dual_right_sixaxis_entry =
+ npad.sixaxis_dual_right.sixaxis[npad.sixaxis_dual_right.common.last_entry_index];
+ auto& left_sixaxis_entry =
+ npad.sixaxis_left.sixaxis[npad.sixaxis_left.common.last_entry_index];
+ auto& right_sixaxis_entry =
+ npad.sixaxis_right.sixaxis[npad.sixaxis_right.common.last_entry_index];
+
+ switch (controller_type) {
+ case NPadControllerType::None:
+ UNREACHABLE();
+ break;
+ case NPadControllerType::ProController:
+ full_sixaxis_entry.attribute.raw = 0;
+ if (sixaxis_sensors_enabled && motions[i][0]) {
+ full_sixaxis_entry.attribute.is_connected.Assign(1);
+ full_sixaxis_entry.accel = motion_devices[0].accel;
+ full_sixaxis_entry.gyro = motion_devices[0].gyro;
+ full_sixaxis_entry.rotation = motion_devices[0].rotation;
+ full_sixaxis_entry.orientation = motion_devices[0].orientation;
+ }
+ break;
+ case NPadControllerType::Handheld:
+ handheld_sixaxis_entry.attribute.raw = 0;
+ if (sixaxis_sensors_enabled && motions[i][0]) {
+ handheld_sixaxis_entry.attribute.is_connected.Assign(1);
+ handheld_sixaxis_entry.accel = motion_devices[0].accel;
+ handheld_sixaxis_entry.gyro = motion_devices[0].gyro;
+ handheld_sixaxis_entry.rotation = motion_devices[0].rotation;
+ handheld_sixaxis_entry.orientation = motion_devices[0].orientation;
+ }
+ break;
+ case NPadControllerType::JoyDual:
+ dual_left_sixaxis_entry.attribute.raw = 0;
+ dual_right_sixaxis_entry.attribute.raw = 0;
+ if (sixaxis_sensors_enabled && motions[i][0]) {
+ // Set motion for the left joycon
+ dual_left_sixaxis_entry.attribute.is_connected.Assign(1);
+ dual_left_sixaxis_entry.accel = motion_devices[0].accel;
+ dual_left_sixaxis_entry.gyro = motion_devices[0].gyro;
+ dual_left_sixaxis_entry.rotation = motion_devices[0].rotation;
+ dual_left_sixaxis_entry.orientation = motion_devices[0].orientation;
+ }
+ if (sixaxis_sensors_enabled && motions[i][1]) {
+ // Set motion for the right joycon
+ dual_right_sixaxis_entry.attribute.is_connected.Assign(1);
+ dual_right_sixaxis_entry.accel = motion_devices[1].accel;
+ dual_right_sixaxis_entry.gyro = motion_devices[1].gyro;
+ dual_right_sixaxis_entry.rotation = motion_devices[1].rotation;
+ dual_right_sixaxis_entry.orientation = motion_devices[1].orientation;
+ }
+ break;
+ case NPadControllerType::JoyLeft:
+ left_sixaxis_entry.attribute.raw = 0;
+ if (sixaxis_sensors_enabled && motions[i][0]) {
+ left_sixaxis_entry.attribute.is_connected.Assign(1);
+ left_sixaxis_entry.accel = motion_devices[0].accel;
+ left_sixaxis_entry.gyro = motion_devices[0].gyro;
+ left_sixaxis_entry.rotation = motion_devices[0].rotation;
+ left_sixaxis_entry.orientation = motion_devices[0].orientation;
+ }
+ break;
+ case NPadControllerType::JoyRight:
+ right_sixaxis_entry.attribute.raw = 0;
+ if (sixaxis_sensors_enabled && motions[i][1]) {
+ right_sixaxis_entry.attribute.is_connected.Assign(1);
+ right_sixaxis_entry.accel = motion_devices[1].accel;
+ right_sixaxis_entry.gyro = motion_devices[1].gyro;
+ right_sixaxis_entry.rotation = motion_devices[1].rotation;
+ right_sixaxis_entry.orientation = motion_devices[1].orientation;
+ }
+ break;
+ case NPadControllerType::GameCube:
+ case NPadControllerType::Pokeball:
+ break;
+ }
+ }
+ std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
+ shared_memory_entries.size() * sizeof(NPadEntry));
+}
+
+void Controller_NPad::SetSupportedStyleSet(NpadStyleSet style_set) {
style.raw = style_set.raw;
}
-Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const {
+Controller_NPad::NpadStyleSet Controller_NPad::GetSupportedStyleSet() const {
return style;
}
-void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
+void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) {
ASSERT(length > 0 && (length % sizeof(u32)) == 0);
supported_npad_id_types.clear();
supported_npad_id_types.resize(length / sizeof(u32));
@@ -482,7 +763,7 @@ void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length)
std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size());
}
-std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
+std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const {
return supported_npad_id_types.size();
}
@@ -494,37 +775,190 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
return hold_type;
}
-void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
+void Controller_NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode) {
+ handheld_activation_mode = activation_mode;
+}
+
+Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActivationMode() const {
+ return handheld_activation_mode;
+}
+
+void Controller_NPad::SetNpadCommunicationMode(NpadCommunicationMode communication_mode_) {
+ communication_mode = communication_mode_;
+}
+
+Controller_NPad::NpadCommunicationMode Controller_NPad::GetNpadCommunicationMode() const {
+ return communication_mode;
+}
+
+void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) {
const std::size_t npad_index = NPadIdToIndex(npad_id);
ASSERT(npad_index < shared_memory_entries.size());
- if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) {
- shared_memory_entries[npad_index].pad_assignment = assignment_mode;
+ if (shared_memory_entries[npad_index].assignment_mode != assignment_mode) {
+ shared_memory_entries[npad_index].assignment_mode = assignment_mode;
+ }
+}
+
+bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
+ const VibrationValue& vibration_value) {
+ if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) {
+ return false;
+ }
+
+ const auto& player = Settings::values.players.GetValue()[npad_index];
+
+ if (!player.vibration_enabled) {
+ if (latest_vibration_values[npad_index][device_index].amp_low != 0.0f ||
+ latest_vibration_values[npad_index][device_index].amp_high != 0.0f) {
+ // Send an empty vibration to stop any vibrations.
+ vibrations[npad_index][device_index]->SetRumblePlay(0.0f, 160.0f, 0.0f, 320.0f);
+ // Then reset the vibration value to its default value.
+ latest_vibration_values[npad_index][device_index] = DEFAULT_VIBRATION_VALUE;
+ }
+
+ return false;
+ }
+
+ if (!Settings::values.enable_accurate_vibrations.GetValue()) {
+ using std::chrono::duration_cast;
+ using std::chrono::milliseconds;
+ using std::chrono::steady_clock;
+
+ const auto now = steady_clock::now();
+
+ // Filter out non-zero vibrations that are within 10ms of each other.
+ if ((vibration_value.amp_low != 0.0f || vibration_value.amp_high != 0.0f) &&
+ duration_cast<milliseconds>(now - last_vibration_timepoints[npad_index][device_index]) <
+ milliseconds(10)) {
+ return false;
+ }
+
+ last_vibration_timepoints[npad_index][device_index] = now;
}
+
+ auto& vibration = vibrations[npad_index][device_index];
+ const auto player_vibration_strength = static_cast<f32>(player.vibration_strength);
+ const auto amp_low =
+ std::min(vibration_value.amp_low * player_vibration_strength / 100.0f, 1.0f);
+ const auto amp_high =
+ std::min(vibration_value.amp_high * player_vibration_strength / 100.0f, 1.0f);
+ return vibration->SetRumblePlay(amp_low, vibration_value.freq_low, amp_high,
+ vibration_value.freq_high);
}
-void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
- const std::vector<Vibration>& vibrations) {
- LOG_DEBUG(Service_HID, "(STUBBED) called");
+void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle,
+ const VibrationValue& vibration_value) {
+ if (!IsDeviceHandleValid(vibration_device_handle)) {
+ return;
+ }
- if (!Settings::values.vibration_enabled || !can_controllers_vibrate) {
+ if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
return;
}
- for (std::size_t i = 0; i < controller_ids.size(); i++) {
- std::size_t controller_pos = NPadIdToIndex(static_cast<u32>(i));
- if (connected_controllers[controller_pos].is_connected) {
- // TODO(ogniK): Vibrate the physical controller
- }
+
+ const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+ const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+
+ if (!vibration_devices_mounted[npad_index][device_index] ||
+ !connected_controllers[npad_index].is_connected) {
+ return;
}
- last_processed_vibration = vibrations.back();
+
+ if (vibration_device_handle.device_index == DeviceIndex::None) {
+ UNREACHABLE_MSG("DeviceIndex should never be None!");
+ return;
+ }
+
+ // Some games try to send mismatched parameters in the device handle, block these.
+ if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft &&
+ (vibration_device_handle.npad_type == NpadType::JoyconRight ||
+ vibration_device_handle.device_index == DeviceIndex::Right)) ||
+ (connected_controllers[npad_index].type == NPadControllerType::JoyRight &&
+ (vibration_device_handle.npad_type == NpadType::JoyconLeft ||
+ vibration_device_handle.device_index == DeviceIndex::Left))) {
+ return;
+ }
+
+ // Filter out vibrations with equivalent values to reduce unnecessary state changes.
+ if (vibration_value.amp_low == latest_vibration_values[npad_index][device_index].amp_low &&
+ vibration_value.amp_high == latest_vibration_values[npad_index][device_index].amp_high) {
+ return;
+ }
+
+ if (VibrateControllerAtIndex(npad_index, device_index, vibration_value)) {
+ latest_vibration_values[npad_index][device_index] = vibration_value;
+ }
+}
+
+void Controller_NPad::VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
+ const std::vector<VibrationValue>& vibration_values) {
+ if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
+ return;
+ }
+
+ ASSERT_OR_EXECUTE_MSG(
+ vibration_device_handles.size() == vibration_values.size(), { return; },
+ "The amount of device handles does not match with the amount of vibration values,"
+ "this is undefined behavior!");
+
+ for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) {
+ VibrateController(vibration_device_handles[i], vibration_values[i]);
+ }
+}
+
+Controller_NPad::VibrationValue Controller_NPad::GetLastVibration(
+ const DeviceHandle& vibration_device_handle) const {
+ if (!IsDeviceHandleValid(vibration_device_handle)) {
+ return {};
+ }
+
+ const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+ const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+ return latest_vibration_values[npad_index][device_index];
+}
+
+void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) {
+ if (!IsDeviceHandleValid(vibration_device_handle)) {
+ return;
+ }
+
+ const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+ const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+ InitializeVibrationDeviceAtIndex(npad_index, device_index);
+}
+
+void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index,
+ std::size_t device_index) {
+ if (vibrations[npad_index][device_index]) {
+ vibration_devices_mounted[npad_index][device_index] =
+ vibrations[npad_index][device_index]->GetStatus() == 1;
+ } else {
+ vibration_devices_mounted[npad_index][device_index] = false;
+ }
+}
+
+void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
+ permit_vibration_session_enabled = permit_vibration_session;
}
-std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const {
+bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const {
+ if (!IsDeviceHandleValid(vibration_device_handle)) {
+ return false;
+ }
+
+ const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+ const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+ return vibration_devices_mounted[npad_index][device_index];
+}
+
+std::shared_ptr<Kernel::KReadableEvent> Controller_NPad::GetStyleSetChangedEvent(
+ u32 npad_id) const {
const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)];
- return styleset_event.readable;
+ return styleset_event->GetReadableEvent();
}
-Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
- return last_processed_vibration;
+void Controller_NPad::SignalStyleSetChangedEvent(u32 npad_id) const {
+ styleset_changed_events[NPadIdToIndex(npad_id)]->GetWritableEvent()->Signal();
}
void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) {
@@ -534,36 +968,54 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz
void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index,
bool connected) {
if (!connected) {
- DisconnectNPad(IndexToNPad(npad_index));
+ DisconnectNpadAtIndex(npad_index);
return;
}
- if (controller == NPadControllerType::Handheld) {
- Settings::values.players[HANDHELD_INDEX].controller_type =
+ if (controller == NPadControllerType::Handheld && npad_index == HANDHELD_INDEX) {
+ Settings::values.players.GetValue()[HANDHELD_INDEX].controller_type =
MapNPadToSettingsType(controller);
- Settings::values.players[HANDHELD_INDEX].connected = true;
+ Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true;
connected_controllers[HANDHELD_INDEX] = {controller, true};
InitNewlyAddedController(HANDHELD_INDEX);
return;
}
- Settings::values.players[npad_index].controller_type = MapNPadToSettingsType(controller);
- Settings::values.players[npad_index].connected = true;
+ Settings::values.players.GetValue()[npad_index].controller_type =
+ MapNPadToSettingsType(controller);
+ Settings::values.players.GetValue()[npad_index].connected = true;
connected_controllers[npad_index] = {controller, true};
InitNewlyAddedController(npad_index);
}
-void Controller_NPad::DisconnectNPad(u32 npad_id) {
- const auto npad_index = NPadIdToIndex(npad_id);
+void Controller_NPad::DisconnectNpad(u32 npad_id) {
+ DisconnectNpadAtIndex(NPadIdToIndex(npad_id));
+}
+
+void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) {
+ for (std::size_t device_idx = 0; device_idx < vibrations[npad_index].size(); ++device_idx) {
+ // Send an empty vibration to stop any vibrations.
+ VibrateControllerAtIndex(npad_index, device_idx, {});
+ vibration_devices_mounted[npad_index][device_idx] = false;
+ }
+
+ Settings::values.players.GetValue()[npad_index].connected = false;
connected_controllers[npad_index].is_connected = false;
- Settings::values.players[npad_index].connected = false;
auto& controller = shared_memory_entries[npad_index];
- controller.joy_styles.raw = 0; // Zero out
+ controller.style_set.raw = 0; // Zero out
controller.device_type.raw = 0;
- controller.properties.raw = 0;
-
- styleset_changed_events[npad_index].writable->Signal();
+ controller.system_properties.raw = 0;
+ controller.button_properties.raw = 0;
+ controller.battery_level_dual = 0;
+ controller.battery_level_left = 0;
+ controller.battery_level_right = 0;
+ controller.fullkey_color = {};
+ controller.joycon_color = {};
+ controller.assignment_mode = NpadAssignments::Dual;
+ controller.footer_type = AppletFooterUiType::None;
+
+ SignalStyleSetChangedEvent(IndexToNPad(npad_index));
}
void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) {
@@ -574,6 +1026,47 @@ Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMo
return gyroscope_zero_drift_mode;
}
+bool Controller_NPad::IsSixAxisSensorAtRest() const {
+ return sixaxis_at_rest;
+}
+
+void Controller_NPad::SetSixAxisEnabled(bool six_axis_status) {
+ sixaxis_sensors_enabled = six_axis_status;
+}
+
+void Controller_NPad::SetSixAxisFusionParameters(f32 parameter1, f32 parameter2) {
+ sixaxis_fusion_parameter1 = parameter1;
+ sixaxis_fusion_parameter2 = parameter2;
+}
+
+std::pair<f32, f32> Controller_NPad::GetSixAxisFusionParameters() {
+ return {
+ sixaxis_fusion_parameter1,
+ sixaxis_fusion_parameter2,
+ };
+}
+
+void Controller_NPad::ResetSixAxisFusionParameters() {
+ sixaxis_fusion_parameter1 = 0.0f;
+ sixaxis_fusion_parameter2 = 0.0f;
+}
+
+void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) {
+ const auto npad_index_1 = NPadIdToIndex(npad_id_1);
+ const auto npad_index_2 = NPadIdToIndex(npad_id_2);
+
+ // If the controllers at both npad indices form a pair of left and right joycons, merge them.
+ // Otherwise, do nothing.
+ if ((connected_controllers[npad_index_1].type == NPadControllerType::JoyLeft &&
+ connected_controllers[npad_index_2].type == NPadControllerType::JoyRight) ||
+ (connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft &&
+ connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) {
+ // Disconnect the joycon at the second id and connect the dual joycon at the first index.
+ DisconnectNpad(npad_id_2);
+ AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1);
+ }
+}
+
void Controller_NPad::StartLRAssignmentMode() {
// Nothing internally is used for lr assignment mode. Since we have the ability to set the
// controller types from boot, it doesn't really matter about showing a selection screen
@@ -632,12 +1125,13 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
}
}
-void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
- can_controllers_vibrate = can_vibrate;
+bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const {
+ return unintended_home_button_input_protection[NPadIdToIndex(npad_id)];
}
-bool Controller_NPad::IsVibrationEnabled() const {
- return can_controllers_vibrate;
+void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled,
+ u32 npad_id) {
+ unintended_home_button_input_protection[NPadIdToIndex(npad_id)] = is_protection_enabled;
}
void Controller_NPad::ClearAllConnectedControllers() {
@@ -650,13 +1144,13 @@ void Controller_NPad::ClearAllConnectedControllers() {
}
void Controller_NPad::DisconnectAllConnectedControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
controller.is_connected = false;
}
}
void Controller_NPad::ConnectAllDisconnectedControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
if (controller.type != NPadControllerType::None && !controller.is_connected) {
controller.is_connected = true;
}
@@ -664,14 +1158,14 @@ void Controller_NPad::ConnectAllDisconnectedControllers() {
}
void Controller_NPad::ClearAllControllers() {
- for (ControllerHolder& controller : connected_controllers) {
+ for (auto& controller : connected_controllers) {
controller.type = NPadControllerType::None;
controller.is_connected = false;
}
}
u32 Controller_NPad::GetAndResetPressState() {
- return std::exchange(press_state, 0);
+ return press_state.exchange(0);
}
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {
@@ -684,7 +1178,7 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
return false;
}
// Handheld should not be supported in docked mode
- if (Settings::values.use_docked_mode) {
+ if (Settings::values.use_docked_mode.GetValue()) {
return false;
}
@@ -695,15 +1189,17 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
[](u32 npad_id) { return npad_id <= MAX_NPAD_ID; })) {
switch (controller) {
case NPadControllerType::ProController:
- return style.pro_controller;
+ return style.fullkey;
case NPadControllerType::JoyDual:
return style.joycon_dual;
case NPadControllerType::JoyLeft:
return style.joycon_left;
case NPadControllerType::JoyRight:
return style.joycon_right;
+ case NPadControllerType::GameCube:
+ return style.gamecube;
case NPadControllerType::Pokeball:
- return style.pokeball;
+ return style.palma;
default:
return false;
}
@@ -712,92 +1208,4 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
return false;
}
-Controller_NPad::NPadControllerType Controller_NPad::DecideBestController(
- NPadControllerType priority) const {
- if (IsControllerSupported(priority)) {
- return priority;
- }
- const auto is_docked = Settings::values.use_docked_mode;
- if (is_docked && priority == NPadControllerType::Handheld) {
- priority = NPadControllerType::JoyDual;
- if (IsControllerSupported(priority)) {
- return priority;
- }
- }
- std::vector<NPadControllerType> priority_list;
- switch (priority) {
- case NPadControllerType::ProController:
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::Handheld:
- priority_list.push_back(NPadControllerType::JoyDual);
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyDual:
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyLeft:
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::JoyRight:
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::Pokeball);
- break;
- case NPadControllerType::Pokeball:
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- break;
- default:
- priority_list.push_back(NPadControllerType::JoyDual);
- if (!is_docked) {
- priority_list.push_back(NPadControllerType::Handheld);
- }
- priority_list.push_back(NPadControllerType::ProController);
- priority_list.push_back(NPadControllerType::JoyLeft);
- priority_list.push_back(NPadControllerType::JoyRight);
- priority_list.push_back(NPadControllerType::JoyDual);
- break;
- }
-
- const auto iter = std::find_if(priority_list.begin(), priority_list.end(),
- [this](auto type) { return IsControllerSupported(type); });
- if (iter == priority_list.end()) {
- UNIMPLEMENTED_MSG("Could not find supported controller!");
- return priority;
- }
-
- return *iter;
-}
-
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 75ce5b731..bc2e6779d 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -5,14 +5,19 @@
#pragma once
#include <array>
+#include <atomic>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/frontend/input.h"
#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/hid/controllers/controller_base.h"
#include "core/settings.h"
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
namespace Service::HID {
constexpr u32 NPAD_HANDHELD = 32;
@@ -32,31 +37,41 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override;
+ // When the controller is requesting a motion update for the shared memory
+ void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
+ std::size_t size) override;
+
// Called when input devices should be loaded
void OnLoadInputDevices() override;
- struct NPadType {
- union {
- u32_le raw{};
-
- BitField<0, 1, u32> pro_controller;
- BitField<1, 1, u32> handheld;
- BitField<2, 1, u32> joycon_dual;
- BitField<3, 1, u32> joycon_left;
- BitField<4, 1, u32> joycon_right;
+ enum class NPadControllerType {
+ None,
+ ProController,
+ Handheld,
+ JoyDual,
+ JoyLeft,
+ JoyRight,
+ GameCube,
+ Pokeball,
+ };
- BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
- };
+ enum class NpadType : u8 {
+ ProController = 3,
+ Handheld = 4,
+ JoyconDual = 5,
+ JoyconLeft = 6,
+ JoyconRight = 7,
+ GameCube = 8,
+ Pokeball = 9,
+ MaxNpadType = 10,
};
- static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
- struct Vibration {
- f32 amp_low;
- f32 freq_low;
- f32 amp_high;
- f32 freq_high;
+ enum class DeviceIndex : u8 {
+ Left = 0,
+ Right = 1,
+ None = 2,
+ MaxDeviceIndex = 3,
};
- static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size");
enum class GyroscopeZeroDriftMode : u32 {
Loose = 0,
@@ -69,19 +84,65 @@ public:
Horizontal = 1,
};
- enum class NPadAssignments : u32_le {
+ enum class NpadAssignments : u32 {
Dual = 0,
Single = 1,
};
- enum class NPadControllerType {
- None,
- ProController,
- Handheld,
- JoyDual,
- JoyLeft,
- JoyRight,
- Pokeball,
+ enum class NpadHandheldActivationMode : u64 {
+ Dual = 0,
+ Single = 1,
+ None = 2,
+ };
+
+ enum class NpadCommunicationMode : u64 {
+ Mode_5ms = 0,
+ Mode_10ms = 1,
+ Mode_15ms = 2,
+ Default = 3,
+ };
+
+ struct DeviceHandle {
+ NpadType npad_type;
+ u8 npad_id;
+ DeviceIndex device_index;
+ INSERT_PADDING_BYTES_NOINIT(1);
+ };
+ static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size");
+
+ struct NpadStyleSet {
+ union {
+ u32_le raw{};
+
+ BitField<0, 1, u32> fullkey;
+ BitField<1, 1, u32> handheld;
+ BitField<2, 1, u32> joycon_dual;
+ BitField<3, 1, u32> joycon_left;
+ BitField<4, 1, u32> joycon_right;
+ BitField<5, 1, u32> gamecube;
+ BitField<6, 1, u32> palma;
+ BitField<7, 1, u32> lark;
+ BitField<8, 1, u32> handheld_lark;
+ BitField<9, 1, u32> lucia;
+ BitField<29, 1, u32> system_ext;
+ BitField<30, 1, u32> system;
+ };
+ };
+ static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
+
+ struct VibrationValue {
+ f32 amp_low;
+ f32 freq_low;
+ f32 amp_high;
+ f32 freq_high;
+ };
+ static_assert(sizeof(VibrationValue) == 0x10, "Vibration is an invalid size");
+
+ static constexpr VibrationValue DEFAULT_VIBRATION_VALUE{
+ .amp_low = 0.0f,
+ .freq_low = 160.0f,
+ .amp_high = 0.0f,
+ .freq_high = 320.0f,
};
struct LedPattern {
@@ -100,40 +161,70 @@ public:
};
};
- void SetSupportedStyleSet(NPadType style_set);
- NPadType GetSupportedStyleSet() const;
+ void SetSupportedStyleSet(NpadStyleSet style_set);
+ NpadStyleSet GetSupportedStyleSet() const;
- void SetSupportedNPadIdTypes(u8* data, std::size_t length);
+ void SetSupportedNpadIdTypes(u8* data, std::size_t length);
void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
- std::size_t GetSupportedNPadIdTypesSize() const;
+ std::size_t GetSupportedNpadIdTypesSize() const;
void SetHoldType(NpadHoldType joy_hold_type);
NpadHoldType GetHoldType() const;
- void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
+ void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode);
+ NpadHandheldActivationMode GetNpadHandheldActivationMode() const;
+
+ void SetNpadCommunicationMode(NpadCommunicationMode communication_mode_);
+ NpadCommunicationMode GetNpadCommunicationMode() const;
+
+ void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode);
+
+ bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
+ const VibrationValue& vibration_value);
+
+ void VibrateController(const DeviceHandle& vibration_device_handle,
+ const VibrationValue& vibration_value);
+
+ void VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
+ const std::vector<VibrationValue>& vibration_values);
+
+ VibrationValue GetLastVibration(const DeviceHandle& vibration_device_handle) const;
+
+ void InitializeVibrationDevice(const DeviceHandle& vibration_device_handle);
+
+ void InitializeVibrationDeviceAtIndex(std::size_t npad_index, std::size_t device_index);
- void VibrateController(const std::vector<u32>& controller_ids,
- const std::vector<Vibration>& vibrations);
+ void SetPermitVibrationSession(bool permit_vibration_session);
- std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
- Vibration GetLastVibration() const;
+ bool IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const;
+
+ std::shared_ptr<Kernel::KReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
+ void SignalStyleSetChangedEvent(u32 npad_id) const;
// Adds a new controller at an index.
void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index);
// Adds a new controller at an index with connection status.
void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected);
- void DisconnectNPad(u32 npad_id);
+ void DisconnectNpad(u32 npad_id);
+ void DisconnectNpadAtIndex(std::size_t index);
+
void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode);
GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const;
+ bool IsSixAxisSensorAtRest() const;
+ void SetSixAxisEnabled(bool six_axis_status);
+ void SetSixAxisFusionParameters(f32 parameter1, f32 parameter2);
+ std::pair<f32, f32> GetSixAxisFusionParameters();
+ void ResetSixAxisFusionParameters();
LedPattern GetLedPattern(u32 npad_id);
- void SetVibrationEnabled(bool can_vibrate);
- bool IsVibrationEnabled() const;
+ bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const;
+ void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id);
void ClearAllConnectedControllers();
void DisconnectAllConnectedControllers();
void ConnectAllDisconnectedControllers();
void ClearAllControllers();
+ void MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2);
void StartLRAssignmentMode();
void StopLRAssignmentMode();
bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2);
@@ -146,6 +237,8 @@ public:
static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type);
static std::size_t NPadIdToIndex(u32 npad_id);
static u32 IndexToNPad(std::size_t index);
+ static bool IsNpadIdValid(u32 npad_id);
+ static bool IsDeviceHandleValid(const DeviceHandle& device_handle);
private:
struct CommonHeader {
@@ -156,12 +249,32 @@ private:
};
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
+ enum class ColorAttributes : u32_le {
+ Ok = 0,
+ ReadError = 1,
+ NoController = 2,
+ };
+ static_assert(sizeof(ColorAttributes) == 4, "ColorAttributes is an invalid size");
+
struct ControllerColor {
- u32_le body_color;
- u32_le button_color;
+ u32_le body;
+ u32_le button;
};
static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size");
+ struct FullKeyColor {
+ ColorAttributes attribute;
+ ControllerColor fullkey;
+ };
+ static_assert(sizeof(FullKeyColor) == 0xC, "FullKeyColor is an invalid size");
+
+ struct JoyconColor {
+ ColorAttributes attribute;
+ ControllerColor left;
+ ControllerColor right;
+ };
+ static_assert(sizeof(JoyconColor) == 0x14, "JoyconColor is an invalid size");
+
struct ControllerPadState {
union {
u64_le raw{};
@@ -203,6 +316,9 @@ private:
BitField<26, 1, u64> right_sl;
BitField<27, 1, u64> right_sr;
+
+ BitField<28, 1, u64> palma;
+ BitField<30, 1, u64> handheld_left_b;
};
};
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
@@ -216,12 +332,12 @@ private:
struct ConnectionState {
union {
u32_le raw{};
- BitField<0, 1, u32> IsConnected;
- BitField<1, 1, u32> IsWired;
- BitField<2, 1, u32> IsLeftJoyConnected;
- BitField<3, 1, u32> IsLeftJoyWired;
- BitField<4, 1, u32> IsRightJoyConnected;
- BitField<5, 1, u32> IsRightJoyWired;
+ BitField<0, 1, u32> is_connected;
+ BitField<1, 1, u32> is_wired;
+ BitField<2, 1, u32> is_left_connected;
+ BitField<3, 1, u32> is_left_wired;
+ BitField<4, 1, u32> is_right_connected;
+ BitField<5, 1, u32> is_right_wired;
};
};
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
@@ -247,63 +363,176 @@ private:
};
static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
- enum class ColorReadError : u32_le {
- ReadOk = 0,
- ColorDoesntExist = 1,
- NoController = 2,
+ struct SixAxisAttributes {
+ union {
+ u32_le raw{};
+ BitField<0, 1, u32> is_connected;
+ BitField<1, 1, u32> is_interpolated;
+ };
+ };
+ static_assert(sizeof(SixAxisAttributes) == 4, "SixAxisAttributes is an invalid size");
+
+ struct SixAxisStates {
+ s64_le timestamp{};
+ INSERT_PADDING_WORDS(2);
+ s64_le timestamp2{};
+ Common::Vec3f accel{};
+ Common::Vec3f gyro{};
+ Common::Vec3f rotation{};
+ std::array<Common::Vec3f, 3> orientation{};
+ SixAxisAttributes attribute;
+ INSERT_PADDING_BYTES(4); // Reserved
+ };
+ static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size");
+
+ struct SixAxisGeneric {
+ CommonHeader common{};
+ std::array<SixAxisStates, 17> sixaxis{};
+ };
+ static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
+
+ struct TriggerState {
+ s64_le timestamp{};
+ s64_le timestamp2{};
+ s32_le l_analog{};
+ s32_le r_analog{};
+ };
+ static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size");
+
+ struct TriggerGeneric {
+ INSERT_PADDING_BYTES(0x4);
+ s64_le timestamp;
+ INSERT_PADDING_BYTES(0x4);
+ s64_le total_entry_count;
+ s64_le last_entry_index;
+ s64_le entry_count;
+ std::array<TriggerState, 17> trigger{};
};
+ static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size");
- struct NPadProperties {
+ struct NPadSystemProperties {
union {
s64_le raw{};
+ BitField<0, 1, s64> is_charging_joy_dual;
+ BitField<1, 1, s64> is_charging_joy_left;
+ BitField<2, 1, s64> is_charging_joy_right;
+ BitField<3, 1, s64> is_powered_joy_dual;
+ BitField<4, 1, s64> is_powered_joy_left;
+ BitField<5, 1, s64> is_powered_joy_right;
+ BitField<9, 1, s64> is_system_unsupported_button;
+ BitField<10, 1, s64> is_system_ext_unsupported_button;
BitField<11, 1, s64> is_vertical;
BitField<12, 1, s64> is_horizontal;
BitField<13, 1, s64> use_plus;
BitField<14, 1, s64> use_minus;
+ BitField<15, 1, s64> use_directional_buttons;
};
};
+ static_assert(sizeof(NPadSystemProperties) == 0x8, "NPadSystemProperties is an invalid size");
+
+ struct NPadButtonProperties {
+ union {
+ s32_le raw{};
+ BitField<0, 1, s32> is_home_button_protection_enabled;
+ };
+ };
+ static_assert(sizeof(NPadButtonProperties) == 0x4, "NPadButtonProperties is an invalid size");
struct NPadDevice {
union {
u32_le raw{};
- BitField<0, 1, s32> pro_controller;
- BitField<1, 1, s32> handheld;
+ BitField<0, 1, s32> fullkey;
+ BitField<1, 1, s32> debug_pad;
BitField<2, 1, s32> handheld_left;
BitField<3, 1, s32> handheld_right;
BitField<4, 1, s32> joycon_left;
BitField<5, 1, s32> joycon_right;
- BitField<6, 1, s32> pokeball;
+ BitField<6, 1, s32> palma;
+ BitField<7, 1, s32> lark_hvc_left;
+ BitField<8, 1, s32> lark_hvc_right;
+ BitField<9, 1, s32> lark_nes_left;
+ BitField<10, 1, s32> lark_nes_right;
+ BitField<11, 1, s32> handheld_lark_hvc_left;
+ BitField<12, 1, s32> handheld_lark_hvc_right;
+ BitField<13, 1, s32> handheld_lark_nes_left;
+ BitField<14, 1, s32> handheld_lark_nes_right;
+ BitField<15, 1, s32> lucia;
+ BitField<31, 1, s32> system;
};
};
- struct NPadEntry {
- NPadType joy_styles;
- NPadAssignments pad_assignment;
+ struct MotionDevice {
+ Common::Vec3f accel;
+ Common::Vec3f gyro;
+ Common::Vec3f rotation;
+ std::array<Common::Vec3f, 3> orientation;
+ };
- ColorReadError single_color_error;
- ControllerColor single_color;
+ struct NfcXcdHandle {
+ INSERT_PADDING_BYTES(0x60);
+ };
+
+ struct AppletFooterUiAttributes {
+ INSERT_PADDING_BYTES(0x4);
+ };
- ColorReadError dual_color_error;
- ControllerColor left_color;
- ControllerColor right_color;
+ enum class AppletFooterUiType : u8 {
+ None = 0,
+ HandheldNone = 1,
+ HandheldJoyConLeftOnly = 1,
+ HandheldJoyConRightOnly = 3,
+ HandheldJoyConLeftJoyConRight = 4,
+ JoyDual = 5,
+ JoyDualLeftOnly = 6,
+ JoyDualRightOnly = 7,
+ JoyLeftHorizontal = 8,
+ JoyLeftVertical = 9,
+ JoyRightHorizontal = 10,
+ JoyRightVertical = 11,
+ SwitchProController = 12,
+ CompatibleProController = 13,
+ CompatibleJoyCon = 14,
+ LarkHvc1 = 15,
+ LarkHvc2 = 16,
+ LarkNesLeft = 17,
+ LarkNesRight = 18,
+ Lucia = 19,
+ Verification = 20,
+ };
+
+ struct NPadEntry {
+ NpadStyleSet style_set;
+ NpadAssignments assignment_mode;
+ FullKeyColor fullkey_color;
+ JoyconColor joycon_color;
- NPadGeneric main_controller_states;
+ NPadGeneric fullkey_states;
NPadGeneric handheld_states;
- NPadGeneric dual_states;
- NPadGeneric left_joy_states;
- NPadGeneric right_joy_states;
- NPadGeneric pokeball_states;
- NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
- // relying on this for the time being
- INSERT_PADDING_BYTES(
- 0x708 *
- 6); // TODO(ogniK): SixAxis states, require more information before implementation
+ NPadGeneric joy_dual_states;
+ NPadGeneric joy_left_states;
+ NPadGeneric joy_right_states;
+ NPadGeneric palma_states;
+ NPadGeneric system_ext_states;
+ SixAxisGeneric sixaxis_fullkey;
+ SixAxisGeneric sixaxis_handheld;
+ SixAxisGeneric sixaxis_dual_left;
+ SixAxisGeneric sixaxis_dual_right;
+ SixAxisGeneric sixaxis_left;
+ SixAxisGeneric sixaxis_right;
NPadDevice device_type;
- NPadProperties properties;
- INSERT_PADDING_WORDS(1);
- std::array<u32, 3> battery_level;
- INSERT_PADDING_BYTES(0x5c);
- INSERT_PADDING_BYTES(0xdf8);
+ INSERT_PADDING_BYTES(0x4); // reserved
+ NPadSystemProperties system_properties;
+ NPadButtonProperties button_properties;
+ u32 battery_level_dual;
+ u32 battery_level_left;
+ u32 battery_level_right;
+ AppletFooterUiAttributes footer_attributes;
+ AppletFooterUiType footer_type;
+ // nfc_states needs to be checked switchbrew does not match with HW
+ NfcXcdHandle nfc_states;
+ INSERT_PADDING_BYTES(0x8); // Mutex
+ TriggerGeneric gc_trigger_states;
+ INSERT_PADDING_BYTES(0xc1f);
};
static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
@@ -314,30 +543,47 @@ private:
void InitNewlyAddedController(std::size_t controller_idx);
bool IsControllerSupported(NPadControllerType controller) const;
- NPadControllerType DecideBestController(NPadControllerType priority) const;
void RequestPadStateUpdate(u32 npad_id);
- u32 press_state{};
+ std::atomic<u32> press_state{};
- NPadType style{};
+ NpadStyleSet style{};
std::array<NPadEntry, 10> shared_memory_entries{};
- std::array<
+ using ButtonArray = std::array<
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
- 10>
- buttons;
- std::array<
+ 10>;
+ using StickArray = std::array<
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
- 10>
- sticks;
+ 10>;
+ using VibrationArray = std::array<std::array<std::unique_ptr<Input::VibrationDevice>,
+ Settings::NativeVibration::NUM_VIBRATIONS_HID>,
+ 10>;
+ using MotionArray = std::array<
+ std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,
+ 10>;
+ ButtonArray buttons;
+ StickArray sticks;
+ VibrationArray vibrations;
+ MotionArray motions;
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};
+ NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
+ NpadCommunicationMode communication_mode{NpadCommunicationMode::Default};
// Each controller should have their own styleset changed event
- std::array<Kernel::EventPair, 10> styleset_changed_events;
- Vibration last_processed_vibration{};
+ std::array<std::shared_ptr<Kernel::KEvent>, 10> styleset_changed_events;
+ std::array<std::array<std::chrono::steady_clock::time_point, 2>, 10> last_vibration_timepoints;
+ std::array<std::array<VibrationValue, 2>, 10> latest_vibration_values{};
+ bool permit_vibration_session_enabled{false};
+ std::array<std::array<bool, 2>, 10> vibration_devices_mounted{};
std::array<ControllerHolder, 10> connected_controllers{};
+ std::array<bool, 10> unintended_home_button_input_protection{};
GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
- bool can_controllers_vibrate{true};
+ bool sixaxis_sensors_enabled{true};
+ f32 sixaxis_fusion_parameter1{};
+ f32 sixaxis_fusion_parameter2{};
+ bool sixaxis_at_rest{true};
std::array<ControllerPad, 10> npad_pad_states{};
+ std::array<TriggerState, 10> npad_trigger_states{};
bool is_in_lr_assignment_mode{false};
Core::System& system;
};
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index e326f8f5c..5219f2dad 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <cstring>
#include "common/common_types.h"
#include "core/core_timing.h"
@@ -16,7 +17,13 @@ constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
Controller_Touchscreen::Controller_Touchscreen(Core::System& system) : ControllerBase(system) {}
Controller_Touchscreen::~Controller_Touchscreen() = default;
-void Controller_Touchscreen::OnInit() {}
+void Controller_Touchscreen::OnInit() {
+ for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
+ mouse_finger_id[id] = MAX_FINGERS;
+ keyboard_finger_id[id] = MAX_FINGERS;
+ udp_finger_id[id] = MAX_FINGERS;
+ }
+}
void Controller_Touchscreen::OnRelease() {}
@@ -40,28 +47,106 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
cur_entry.sampling_number = last_entry.sampling_number + 1;
cur_entry.sampling_number2 = cur_entry.sampling_number;
- const auto [x, y, pressed] = touch_device->GetStatus();
- auto& touch_entry = cur_entry.states[0];
- touch_entry.attribute.raw = 0;
- if (pressed && Settings::values.touchscreen.enabled) {
- touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
- touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
- touch_entry.diameter_x = Settings::values.touchscreen.diameter_x;
- touch_entry.diameter_y = Settings::values.touchscreen.diameter_y;
- touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle;
- const u64 tick = core_timing.GetCPUTicks();
- touch_entry.delta_time = tick - last_touch;
- last_touch = tick;
- touch_entry.finger = Settings::values.touchscreen.finger;
- cur_entry.entry_count = 1;
- } else {
- cur_entry.entry_count = 0;
+ const Input::TouchStatus& mouse_status = touch_mouse_device->GetStatus();
+ const Input::TouchStatus& udp_status = touch_udp_device->GetStatus();
+ for (std::size_t id = 0; id < mouse_status.size(); ++id) {
+ mouse_finger_id[id] = UpdateTouchInputEvent(mouse_status[id], mouse_finger_id[id]);
+ udp_finger_id[id] = UpdateTouchInputEvent(udp_status[id], udp_finger_id[id]);
+ }
+
+ if (Settings::values.use_touch_from_button) {
+ const Input::TouchStatus& keyboard_status = touch_btn_device->GetStatus();
+ for (std::size_t id = 0; id < mouse_status.size(); ++id) {
+ keyboard_finger_id[id] =
+ UpdateTouchInputEvent(keyboard_status[id], keyboard_finger_id[id]);
+ }
}
+ std::array<Finger, 16> active_fingers;
+ const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(),
+ [](const auto& finger) { return finger.pressed; });
+ const auto active_fingers_count =
+ static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter));
+
+ const u64 tick = core_timing.GetCPUTicks();
+ cur_entry.entry_count = static_cast<s32_le>(active_fingers_count);
+ for (std::size_t id = 0; id < MAX_FINGERS; ++id) {
+ auto& touch_entry = cur_entry.states[id];
+ if (id < active_fingers_count) {
+ touch_entry.x = static_cast<u16>(active_fingers[id].x * Layout::ScreenUndocked::Width);
+ touch_entry.y = static_cast<u16>(active_fingers[id].y * Layout::ScreenUndocked::Height);
+ touch_entry.diameter_x = Settings::values.touchscreen.diameter_x;
+ touch_entry.diameter_y = Settings::values.touchscreen.diameter_y;
+ touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle;
+ touch_entry.delta_time = tick - active_fingers[id].last_touch;
+ fingers[active_fingers[id].id].last_touch = tick;
+ touch_entry.finger = active_fingers[id].id;
+ touch_entry.attribute.raw = active_fingers[id].attribute.raw;
+ } else {
+ // Clear touch entry
+ touch_entry.attribute.raw = 0;
+ touch_entry.x = 0;
+ touch_entry.y = 0;
+ touch_entry.diameter_x = 0;
+ touch_entry.diameter_y = 0;
+ touch_entry.rotation_angle = 0;
+ touch_entry.delta_time = 0;
+ touch_entry.finger = 0;
+ }
+ }
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory));
}
void Controller_Touchscreen::OnLoadInputDevices() {
- touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device);
+ touch_mouse_device = Input::CreateDevice<Input::TouchDevice>("engine:emu_window");
+ touch_udp_device = Input::CreateDevice<Input::TouchDevice>("engine:cemuhookudp");
+ touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
+}
+
+std::optional<std::size_t> Controller_Touchscreen::GetUnusedFingerID() const {
+ std::size_t first_free_id = 0;
+ while (first_free_id < MAX_FINGERS) {
+ if (!fingers[first_free_id].pressed) {
+ return first_free_id;
+ } else {
+ first_free_id++;
+ }
+ }
+ return std::nullopt;
+}
+
+std::size_t Controller_Touchscreen::UpdateTouchInputEvent(
+ const std::tuple<float, float, bool>& touch_input, std::size_t finger_id) {
+ const auto& [x, y, pressed] = touch_input;
+ if (pressed) {
+ Attributes attribute{};
+ if (finger_id == MAX_FINGERS) {
+ const auto first_free_id = GetUnusedFingerID();
+ if (!first_free_id) {
+ // Invalid finger id do nothing
+ return MAX_FINGERS;
+ }
+ finger_id = first_free_id.value();
+ fingers[finger_id].pressed = true;
+ fingers[finger_id].id = static_cast<u32_le>(finger_id);
+ attribute.start_touch.Assign(1);
+ }
+ fingers[finger_id].x = x;
+ fingers[finger_id].y = y;
+ fingers[finger_id].attribute = attribute;
+ return finger_id;
+ }
+
+ if (finger_id != MAX_FINGERS) {
+ if (!fingers[finger_id].attribute.end_touch) {
+ fingers[finger_id].attribute.end_touch.Assign(1);
+ fingers[finger_id].attribute.start_touch.Assign(0);
+ return finger_id;
+ }
+ fingers[finger_id].pressed = false;
+ }
+
+ return MAX_FINGERS;
}
+
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index a1d97269e..784124e25 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -30,6 +30,18 @@ public:
void OnLoadInputDevices() override;
private:
+ static constexpr std::size_t MAX_FINGERS = 16;
+
+ // Returns an unused finger id, if there is no fingers available std::nullopt will be returned
+ std::optional<std::size_t> GetUnusedFingerID() const;
+
+ // If the touch is new it tries to assing a new finger id, if there is no fingers avaliable no
+ // changes will be made. Updates the coordinates if the finger id it's already set. If the touch
+ // ends delays the output by one frame to set the end_touch flag before finally freeing the
+ // finger id
+ std::size_t UpdateTouchInputEvent(const std::tuple<float, float, bool>& touch_input,
+ std::size_t finger_id);
+
struct Attributes {
union {
u32 raw{};
@@ -55,7 +67,7 @@ private:
s64_le sampling_number;
s64_le sampling_number2;
s32_le entry_count;
- std::array<TouchState, 16> states;
+ std::array<TouchState, MAX_FINGERS> states;
};
static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size");
@@ -66,8 +78,23 @@ private:
};
static_assert(sizeof(TouchScreenSharedMemory) == 0x3000,
"TouchScreenSharedMemory is an invalid size");
+
+ struct Finger {
+ u64_le last_touch{};
+ float x{};
+ float y{};
+ u32_le id{};
+ bool pressed{};
+ Attributes attribute;
+ };
+
TouchScreenSharedMemory shared_memory{};
- std::unique_ptr<Input::TouchDevice> touch_device;
- s64_le last_touch{};
+ std::unique_ptr<Input::TouchDevice> touch_mouse_device;
+ std::unique_ptr<Input::TouchDevice> touch_udp_device;
+ std::unique_ptr<Input::TouchDevice> touch_btn_device;
+ std::array<std::size_t, MAX_FINGERS> mouse_finger_id;
+ std::array<std::size_t, MAX_FINGERS> keyboard_finger_id;
+ std::array<std::size_t, MAX_FINGERS> udp_finger_id;
+ std::array<Finger, MAX_FINGERS> fingers;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h
index ad229787c..5b59961bd 100644
--- a/src/core/hle/service/hid/controllers/xpad.h
+++ b/src/core/hle/service/hid/controllers/xpad.h
@@ -4,6 +4,7 @@
#pragma once
+#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -28,6 +29,67 @@ public:
void OnLoadInputDevices() override;
private:
+ struct Attributes {
+ union {
+ u32_le raw{};
+ BitField<0, 1, u32> is_connected;
+ BitField<1, 1, u32> is_wired;
+ BitField<2, 1, u32> is_left_connected;
+ BitField<3, 1, u32> is_left_wired;
+ BitField<4, 1, u32> is_right_connected;
+ BitField<5, 1, u32> is_right_wired;
+ };
+ };
+ static_assert(sizeof(Attributes) == 4, "Attributes is an invalid size");
+
+ struct Buttons {
+ union {
+ u32_le raw{};
+ // Button states
+ BitField<0, 1, u32> a;
+ BitField<1, 1, u32> b;
+ BitField<2, 1, u32> x;
+ BitField<3, 1, u32> y;
+ BitField<4, 1, u32> l_stick;
+ BitField<5, 1, u32> r_stick;
+ BitField<6, 1, u32> l;
+ BitField<7, 1, u32> r;
+ BitField<8, 1, u32> zl;
+ BitField<9, 1, u32> zr;
+ BitField<10, 1, u32> plus;
+ BitField<11, 1, u32> minus;
+
+ // D-Pad
+ BitField<12, 1, u32> d_left;
+ BitField<13, 1, u32> d_up;
+ BitField<14, 1, u32> d_right;
+ BitField<15, 1, u32> d_down;
+
+ // Left JoyStick
+ BitField<16, 1, u32> l_stick_left;
+ BitField<17, 1, u32> l_stick_up;
+ BitField<18, 1, u32> l_stick_right;
+ BitField<19, 1, u32> l_stick_down;
+
+ // Right JoyStick
+ BitField<20, 1, u32> r_stick_left;
+ BitField<21, 1, u32> r_stick_up;
+ BitField<22, 1, u32> r_stick_right;
+ BitField<23, 1, u32> r_stick_down;
+
+ // Not always active?
+ BitField<24, 1, u32> left_sl;
+ BitField<25, 1, u32> left_sr;
+
+ BitField<26, 1, u32> right_sl;
+ BitField<27, 1, u32> right_sr;
+
+ BitField<28, 1, u32> palma;
+ BitField<30, 1, u32> handheld_left_b;
+ };
+ };
+ static_assert(sizeof(Buttons) == 4, "Buttons is an invalid size");
+
struct AnalogStick {
s32_le x;
s32_le y;
@@ -37,10 +99,10 @@ private:
struct XPadState {
s64_le sampling_number;
s64_le sampling_number2;
- s32_le attributes;
- u32_le pad_states;
- AnalogStick x_stick;
- AnalogStick y_stick;
+ Attributes attributes;
+ Buttons pad_states;
+ AnalogStick l_stick;
+ AnalogStick r_stick;
};
static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size");
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 33416b5dd..1e2677320 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -14,10 +14,10 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/shared_memory.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/hid/irs.h"
@@ -40,11 +40,12 @@ namespace Service::HID {
// Updating period for each HID device.
// HID is polled every 15ms, this value was derived from
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet
-constexpr auto pad_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.6Hz)
+constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz)
+constexpr auto motion_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.666Hz)
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
-IAppletResource::IAppletResource(Core::System& system)
- : ServiceFramework("IAppletResource"), system(system) {
+IAppletResource::IAppletResource(Core::System& system_)
+ : ServiceFramework{system_, "IAppletResource"} {
static const FunctionInfo functions[] = {
{0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
};
@@ -58,31 +59,43 @@ IAppletResource::IAppletResource(Core::System& system)
MakeController<Controller_Mouse>(HidController::Mouse);
MakeController<Controller_Keyboard>(HidController::Keyboard);
MakeController<Controller_XPad>(HidController::XPad);
- MakeController<Controller_Stubbed>(HidController::Unknown1);
- MakeController<Controller_Stubbed>(HidController::Unknown2);
- MakeController<Controller_Stubbed>(HidController::Unknown3);
- MakeController<Controller_Stubbed>(HidController::SixAxisSensor);
+ MakeController<Controller_Stubbed>(HidController::HomeButton);
+ MakeController<Controller_Stubbed>(HidController::SleepButton);
+ MakeController<Controller_Stubbed>(HidController::CaptureButton);
+ MakeController<Controller_Stubbed>(HidController::InputDetector);
+ MakeController<Controller_Stubbed>(HidController::UniquePad);
MakeController<Controller_NPad>(HidController::NPad);
MakeController<Controller_Gesture>(HidController::Gesture);
+ MakeController<Controller_Stubbed>(HidController::ConsoleSixAxisSensor);
// Homebrew doesn't try to activate some controllers, so we activate them by default
GetController<Controller_NPad>(HidController::NPad).ActivateController();
GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController();
- GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00);
- GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00);
- GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
+ GetController<Controller_Stubbed>(HidController::HomeButton).SetCommonHeaderOffset(0x4C00);
+ GetController<Controller_Stubbed>(HidController::SleepButton).SetCommonHeaderOffset(0x4E00);
+ GetController<Controller_Stubbed>(HidController::CaptureButton).SetCommonHeaderOffset(0x5000);
+ GetController<Controller_Stubbed>(HidController::InputDetector).SetCommonHeaderOffset(0x5200);
+ GetController<Controller_Stubbed>(HidController::UniquePad).SetCommonHeaderOffset(0x5A00);
+ GetController<Controller_Stubbed>(HidController::ConsoleSixAxisSensor)
+ .SetCommonHeaderOffset(0x3C200);
// Register update callbacks
pad_update_event = Core::Timing::CreateEvent(
"HID::UpdatePadCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ const auto guard = LockService();
UpdateControllers(user_data, ns_late);
});
-
- // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
+ motion_update_event = Core::Timing::CreateEvent(
+ "HID::MotionPadCallback",
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ const auto guard = LockService();
+ UpdateMotion(user_data, ns_late);
+ });
system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event);
+ system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event);
ReloadInputDevices();
}
@@ -97,6 +110,7 @@ void IAppletResource::DeactivateController(HidController controller) {
IAppletResource ::~IAppletResource() {
system.CoreTiming().UnscheduleEvent(pad_update_event, 0);
+ system.CoreTiming().UnscheduleEvent(motion_update_event, 0);
}
void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
@@ -119,25 +133,62 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,
controller->OnUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
}
+ // If ns_late is higher than the update rate ignore the delay
+ if (ns_late > motion_update_ns) {
+ ns_late = {};
+ }
+
core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event);
}
+void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ auto& core_timing = system.CoreTiming();
+
+ controllers[static_cast<size_t>(HidController::NPad)]->OnMotionUpdate(
+ core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
+
+ // If ns_late is higher than the update rate ignore the delay
+ if (ns_late > motion_update_ns) {
+ ns_late = {};
+ }
+
+ core_timing.ScheduleEvent(motion_update_ns - ns_late, motion_update_event);
+}
+
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
public:
- IActiveVibrationDeviceList() : ServiceFramework("IActiveVibrationDeviceList") {
+ explicit IActiveVibrationDeviceList(Core::System& system_,
+ std::shared_ptr<IAppletResource> applet_resource_)
+ : ServiceFramework{system_, "IActiveVibrationDeviceList"},
+ applet_resource(applet_resource_) {
+ // clang-format off
static const FunctionInfo functions[] = {
- {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"},
+ {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
private:
- void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ void InitializeVibrationDevice(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
+
+ if (applet_resource != nullptr) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .InitializeVibrationDevice(vibration_device_handle);
+ }
+
+ LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}",
+ vibration_device_handle.npad_type, vibration_device_handle.npad_id,
+ vibration_device_handle.device_index);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+
+ std::shared_ptr<IAppletResource> applet_resource;
};
std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
@@ -148,7 +199,7 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
return applet_resource;
}
-Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
+Hid::Hid(Core::System& system_) : ServiceFramework{system_, "hid"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Hid::CreateAppletResource, "CreateAppletResource"},
@@ -164,8 +215,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{56, nullptr, "ActivateJoyXpad"},
{58, nullptr, "GetJoyXpadLifoHandle"},
{59, nullptr, "GetJoyXpadIds"},
- {60, nullptr, "ActivateSixAxisSensor"},
- {61, nullptr, "DeactivateSixAxisSensor"},
+ {60, &Hid::ActivateSixAxisSensor, "ActivateSixAxisSensor"},
+ {61, &Hid::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"},
{62, nullptr, "GetSixAxisSensorLifoHandle"},
{63, nullptr, "ActivateJoySixAxisSensor"},
{64, nullptr, "DeactivateJoySixAxisSensor"},
@@ -173,10 +224,10 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"},
{67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"},
{68, nullptr, "IsSixAxisSensorFusionEnabled"},
- {69, nullptr, "EnableSixAxisSensorFusion"},
- {70, nullptr, "SetSixAxisSensorFusionParameters"},
- {71, nullptr, "GetSixAxisSensorFusionParameters"},
- {72, nullptr, "ResetSixAxisSensorFusionParameters"},
+ {69, &Hid::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"},
+ {70, &Hid::SetSixAxisSensorFusionParameters, "SetSixAxisSensorFusionParameters"},
+ {71, &Hid::GetSixAxisSensorFusionParameters, "GetSixAxisSensorFusionParameters"},
+ {72, &Hid::ResetSixAxisSensorFusionParameters, "ResetSixAxisSensorFusionParameters"},
{73, nullptr, "SetAccelerometerParameters"},
{74, nullptr, "GetAccelerometerParameters"},
{75, nullptr, "ResetAccelerometerParameters"},
@@ -209,8 +260,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
{129, &Hid::GetNpadHandheldActivationMode, "GetNpadHandheldActivationMode"},
{130, &Hid::SwapNpadAssignment, "SwapNpadAssignment"},
- {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
- {132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
+ {131, &Hid::IsUnintendedHomeButtonInputProtectionEnabled, "IsUnintendedHomeButtonInputProtectionEnabled"},
+ {132, &Hid::EnableUnintendedHomeButtonInputProtection, "EnableUnintendedHomeButtonInputProtection"},
{133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"},
{134, nullptr, "SetNpadAnalogStickUseCenterClamp"},
{135, nullptr, "SetNpadCaptureButtonAssignment"},
@@ -226,7 +277,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{208, nullptr, "GetActualVibrationGcErmCommand"},
{209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
{210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
- {211, nullptr, "IsVibrationDeviceMounted"},
+ {211, &Hid::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"},
{300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
{301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
{302, &Hid::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"},
@@ -245,7 +296,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{404, nullptr, "HasLeftRightBattery"},
{405, nullptr, "GetNpadInterfaceType"},
{406, nullptr, "GetNpadLeftRightInterfaceType"},
- {407, nullptr, "GetNpadOfHighestBatteryLevelForJoyLeft"},
+ {407, nullptr, "GetNpadOfHighestBatteryLevel"},
{408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
{500, nullptr, "GetPalmaConnectionHandle"},
{501, nullptr, "InitializePalma"},
@@ -277,8 +328,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
{527, nullptr, "EnablePalmaBoostMode"},
{528, nullptr, "GetPalmaBluetoothAddress"},
{529, nullptr, "SetDisallowedPalmaConnection"},
- {1000, nullptr, "SetNpadCommunicationMode"},
- {1001, nullptr, "GetNpadCommunicationMode"},
+ {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
+ {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
{1002, nullptr, "SetTouchScreenConfiguration"},
{1003, nullptr, "IsFirmwareUpdateNeededForNotification"},
{2000, nullptr, "ActivateDigitizer"},
@@ -305,117 +356,152 @@ void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) {
rb.PushIpcInterface<IAppletResource>(applet_resource);
}
-void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto basic_xpad_id{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", basic_xpad_id,
- applet_resource_user_id);
+ applet_resource->ActivateController(HidController::DebugPad);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->ActivateController(HidController::XPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id);
+ applet_resource->ActivateController(HidController::Touchscreen);
- IPC::ResponseBuilder rb{ctx, 3};
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.Push(0);
}
-void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Mouse);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->ActivateController(HidController::DebugPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::Keyboard);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->ActivateController(HidController::Touchscreen);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) {
+void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto flags{rp.Pop<u32>()};
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags);
- applet_resource->ActivateController(HidController::Mouse);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 basic_xpad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->ActivateController(HidController::XPad);
+
+ LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}",
+ parameters.basic_xpad_id, parameters.applet_resource_user_id);
- applet_resource->ActivateController(HidController::Keyboard);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) {
+void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto flags{rp.Pop<u32>()};
- LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags);
+ const auto applet_resource_user_id{rp.Pop<u64>()};
- IPC::ResponseBuilder rb{ctx, 2};
+ LOG_DEBUG(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
+ rb.Push(0);
}
-void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
+void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto unknown{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
- applet_resource->ActivateController(HidController::Gesture);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
- // Should have no effect with how our npad sets up the data
+void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto unknown{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
- applet_resource->ActivateController(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -423,11 +509,120 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool enable_sixaxis_sensor_fusion;
+ INSERT_PADDING_BYTES_NOINIT(3);
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, "
+ "device_index={}, applet_resource_user_id={}",
+ parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type,
+ parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ f32 parameter1;
+ f32 parameter2;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSixAxisFusionParameters(parameters.parameter1, parameters.parameter2);
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, parameter1={}, "
+ "parameter2={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.parameter1,
+ parameters.parameter2, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ f32 parameter1 = 0;
+ f32 parameter2 = 0;
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ std::tie(parameter1, parameter2) =
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetSixAxisFusionParameters();
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(parameter1);
+ rb.Push(parameter2);
+}
+
+void Hid::ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .ResetSixAxisFusionParameters();
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -435,14 +630,17 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto drift_mode{rp.Pop<u32>()};
+ const auto sixaxis_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
+ const auto drift_mode{rp.PopEnum<Controller_NPad::GyroscopeZeroDriftMode>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode{drift_mode});
+ .SetGyroscopeZeroDriftMode(drift_mode);
- LOG_DEBUG(Service_HID, "called, handle={}, drift_mode={}, applet_resource_user_id={}", handle,
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, "
+ "applet_resource_user_id={}",
+ sixaxis_handle.npad_type, sixaxis_handle.npad_id, sixaxis_handle.device_index,
drift_mode, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
@@ -451,29 +649,42 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
- LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(
- static_cast<u32>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetGyroscopeZeroDriftMode()));
+ rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetGyroscopeZeroDriftMode());
}
void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode::Standard);
- LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -481,27 +692,53 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) {
void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- // TODO (Hexagon12): Properly implement reading gyroscope values from controllers.
- rb.Push(true);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .IsSixAxisSensorAtRest());
+}
+
+void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ u32 unknown;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->ActivateController(HidController::Gesture);
+
+ LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
}
void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto supported_styleset{rp.Pop<u32>()};
- LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset);
-
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.SetSupportedStyleSet({supported_styleset});
+ LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -512,21 +749,22 @@ void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
-
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(controller.GetSupportedStyleSet().raw);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetSupportedStyleSet()
+ .raw);
}
void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSupportedNpadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -535,48 +773,62 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->ActivateController(HidController::NPad);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- applet_resource->ActivateController(HidController::NPad);
}
void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->DeactivateController(HidController::NPad);
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- applet_resource->DeactivateController(HidController::NPad);
}
void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto unknown{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ u64 unknown;
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", npad_id,
- applet_resource_user_id, unknown);
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}",
+ parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetStyleSetChangedEvent(npad_id));
+ .GetStyleSetChangedEvent(parameters.npad_id));
}
void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .DisconnectNpad(parameters.npad_id);
+
+ LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id,
+ parameters.applet_resource_user_id);
- applet_resource->GetController<Controller_NPad>(HidController::NPad).DisconnectNPad(npad_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -589,22 +841,41 @@ void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .GetLedPattern(npad_id)
- .raw);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLedPattern(npad_id)
+ .raw);
+}
+
+void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
+ // Should have no effect with how our npad sets up the data
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ u32 unknown;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->ActivateController(HidController::NPad);
+
+ LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown,
+ parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
}
void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto hold_type{rp.Pop<u64>()};
+ const auto hold_type{rp.PopEnum<Controller_NPad::NpadHoldType>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).SetHoldType(hold_type);
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}",
applet_resource_user_id, hold_type);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
-
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -615,22 +886,26 @@ void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- const auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(static_cast<u64>(controller.GetHoldType()));
+ rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad).GetHoldType());
}
void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single);
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single);
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -639,16 +914,22 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx
void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
// TODO: Check the differences between this and SetNpadJoyAssignmentModeSingleByDefault
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto npad_joy_device_type{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ u64 npad_joy_device_type;
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single);
LOG_WARNING(Service_HID,
"(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
- npad_id, applet_resource_user_id, npad_joy_device_type);
-
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single);
+ parameters.npad_id, parameters.applet_resource_user_id,
+ parameters.npad_joy_device_type);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -656,14 +937,19 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Dual);
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -671,13 +957,15 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto unknown_1{rp.Pop<u32>()};
- const auto unknown_2{rp.Pop<u32>()};
+ const auto npad_id_1{rp.Pop<u32>()};
+ const auto npad_id_2{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID,
- "(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
- unknown_1, unknown_2, applet_resource_user_id);
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2);
+
+ LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
+ npad_id_1, npad_id_2, applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -687,9 +975,9 @@ void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).StartLRAssignmentMode();
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.StartLRAssignmentMode();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -699,9 +987,9 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
+ applet_resource->GetController<Controller_NPad>(HidController::NPad).StopLRAssignmentMode();
+
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
- controller.StopLRAssignmentMode();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -710,10 +998,13 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) {
void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto mode{rp.Pop<u64>()};
+ const auto activation_mode{rp.PopEnum<Controller_NPad::NpadHandheldActivationMode>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadHandheldActivationMode(activation_mode);
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}",
- applet_resource_user_id, mode);
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, activation_mode={}",
+ applet_resource_user_id, activation_mode);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -723,25 +1014,28 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
- applet_resource_user_id);
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetNpadHandheldActivationMode());
}
void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto npad_1{rp.Pop<u32>()};
- const auto npad_2{rp.Pop<u32>()};
+ const auto npad_id_1{rp.Pop<u32>()};
+ const auto npad_id_2{rp.Pop<u32>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}",
- applet_resource_user_id, npad_1, npad_2);
+ const bool res = applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SwapNpadAssignment(npad_id_1, npad_id_2);
+
+ LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
+ npad_id_1, npad_id_2, applet_resource_user_id);
- auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
IPC::ResponseBuilder rb{ctx, 2};
- if (controller.SwapNpadAssignment(npad_1, npad_2)) {
+ if (res) {
rb.Push(RESULT_SUCCESS);
} else {
LOG_ERROR(Service_HID, "Npads are not connected!");
@@ -749,61 +1043,99 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
}
}
-void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ u32 npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
- applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(true);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-}
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
-void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(false);
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id));
}
-void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) {
+void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto controller_id{rp.Pop<u32>()};
- const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ bool unintended_home_button_input_protection;
+ INSERT_PADDING_BYTES_NOINIT(3);
+ u32 npad_id;
+ u64 applet_resource_user_id;
+ };
- LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetUnintendedHomeButtonInputProtectionEnabled(
+ parameters.unintended_home_button_input_protection, parameters.npad_id);
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, unintended_home_button_input_protection={}, npad_id={},"
+ "applet_resource_user_id={}",
+ parameters.unintended_home_button_input_protection, parameters.npad_id,
+ parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
-
- applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateController({controller_id}, {vibration_values});
}
-void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
+void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()};
+
+ VibrationDeviceInfo vibration_device_info;
+
+ vibration_device_info.type = VibrationDeviceType::LinearResonantActuator;
+
+ switch (vibration_device_handle.device_index) {
+ case Controller_NPad::DeviceIndex::Left:
+ vibration_device_info.position = VibrationDevicePosition::Left;
+ break;
+ case Controller_NPad::DeviceIndex::Right:
+ vibration_device_info.position = VibrationDevicePosition::Right;
+ break;
+ case Controller_NPad::DeviceIndex::None:
+ default:
+ UNREACHABLE_MSG("DeviceIndex should never be None!");
+ vibration_device_info.position = VibrationDevicePosition::None;
+ break;
+ }
- LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+ LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}",
+ vibration_device_info.type, vibration_device_info.position);
- const auto controllers = ctx.ReadBuffer(0);
- const auto vibrations = ctx.ReadBuffer(1);
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(vibration_device_info);
+}
- std::vector<u32> controller_list(controllers.size() / sizeof(u32));
- std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() /
- sizeof(Controller_NPad::Vibration));
+void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle vibration_device_handle;
+ Controller_NPad::VibrationValue vibration_value;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- std::memcpy(controller_list.data(), controllers.data(), controllers.size());
- std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
- std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
- [](u32 controller_id) { return controller_id - 3; });
+ const auto parameters{rp.PopRaw<Parameters>()};
applet_resource->GetController<Controller_NPad>(HidController::NPad)
- .VibrateController(controller_list, vibration_list);
+ .VibrateController(parameters.vibration_device_handle, parameters.vibration_value);
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -811,25 +1143,24 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto controller_id{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle vibration_device_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
- IPC::ResponseBuilder rb{ctx, 6};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw<Controller_NPad::Vibration>(
- applet_resource->GetController<Controller_NPad>(HidController::NPad).GetLastVibration());
-}
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
-void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_HID, "called");
-
- IPC::ResponseBuilder rb{ctx, 4};
+ IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(1);
- rb.Push<u32>(0);
+ rb.PushRaw(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLastVibration(parameters.vibration_device_handle));
}
void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
@@ -837,13 +1168,14 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IActiveVibrationDeviceList>();
+ rb.PushIpcInterface<IActiveVibrationDeviceList>(system, applet_resource);
}
void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto can_vibrate{rp.Pop<bool>()};
- Settings::values.vibration_enabled = can_vibrate;
+
+ Settings::values.vibration_enabled.SetValue(can_vibrate);
LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
@@ -856,7 +1188,76 @@ void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(Settings::values.vibration_enabled);
+ rb.Push(Settings::values.vibration_enabled.GetValue());
+}
+
+void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ const auto handles = ctx.ReadBuffer(0);
+ const auto vibrations = ctx.ReadBuffer(1);
+
+ std::vector<Controller_NPad::DeviceHandle> vibration_device_handles(
+ handles.size() / sizeof(Controller_NPad::DeviceHandle));
+ std::vector<Controller_NPad::VibrationValue> vibration_values(
+ vibrations.size() / sizeof(Controller_NPad::VibrationValue));
+
+ std::memcpy(vibration_device_handles.data(), handles.data(), handles.size());
+ std::memcpy(vibration_values.data(), vibrations.data(), vibrations.size());
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .VibrateControllers(vibration_device_handles, vibration_values);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetPermitVibrationSession(true);
+
+ LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetPermitVibrationSession(false);
+
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Controller_NPad::DeviceHandle vibration_device_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.vibration_device_handle.npad_type,
+ parameters.vibration_device_handle.npad_id,
+ parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .IsVibrationDeviceMounted(parameters.vibration_device_handle));
}
void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
@@ -872,11 +1273,19 @@ void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -884,11 +1293,19 @@ void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
void Hid::StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto handle{rp.Pop<u32>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ Controller_NPad::DeviceHandle sixaxis_handle;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
- LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle,
- applet_resource_user_id);
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(
+ Service_HID,
+ "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
+ parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
+ parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -979,9 +1396,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
+void Hid::SetNpadCommunicationMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+ const auto communication_mode{rp.PopEnum<Controller_NPad::NpadCommunicationMode>()};
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetNpadCommunicationMode(communication_mode);
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, communication_mode={}",
+ applet_resource_user_id, communication_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void Hid::GetNpadCommunicationMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+ applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetNpadCommunicationMode());
+}
+
class HidDbg final : public ServiceFramework<HidDbg> {
public:
- explicit HidDbg() : ServiceFramework{"hid:dbg"} {
+ explicit HidDbg(Core::System& system_) : ServiceFramework{system_, "hid:dbg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "DeactivateDebugPad"},
@@ -1108,7 +1553,7 @@ public:
class HidSys final : public ServiceFramework<HidSys> {
public:
- explicit HidSys() : ServiceFramework{"hid:sys"} {
+ explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{31, nullptr, "SendKeyboardLockKeyEvent"},
@@ -1242,7 +1687,7 @@ public:
class HidTmp final : public ServiceFramework<HidTmp> {
public:
- explicit HidTmp() : ServiceFramework{"hid:tmp"} {
+ explicit HidTmp(Core::System& system_) : ServiceFramework{system_, "hid:tmp"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetConsoleSixAxisSensorCalibrationValues"},
@@ -1255,7 +1700,7 @@ public:
class HidBus final : public ServiceFramework<HidBus> {
public:
- explicit HidBus() : ServiceFramework{"hidbus"} {
+ explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "GetBusHandle"},
@@ -1285,15 +1730,15 @@ void ReloadInputDevices() {
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<Hid>(system)->InstallAsService(service_manager);
- std::make_shared<HidBus>()->InstallAsService(service_manager);
- std::make_shared<HidDbg>()->InstallAsService(service_manager);
- std::make_shared<HidSys>()->InstallAsService(service_manager);
- std::make_shared<HidTmp>()->InstallAsService(service_manager);
+ std::make_shared<HidBus>(system)->InstallAsService(service_manager);
+ std::make_shared<HidDbg>(system)->InstallAsService(service_manager);
+ std::make_shared<HidSys>(system)->InstallAsService(service_manager);
+ std::make_shared<HidTmp>(system)->InstallAsService(service_manager);
std::make_shared<IRS>(system)->InstallAsService(service_manager);
- std::make_shared<IRS_SYS>()->InstallAsService(service_manager);
+ std::make_shared<IRS_SYS>(system)->InstallAsService(service_manager);
- std::make_shared<XCD_SYS>()->InstallAsService(service_manager);
+ std::make_shared<XCD_SYS>(system)->InstallAsService(service_manager);
}
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index efb07547f..7cc0433e2 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -29,19 +29,21 @@ enum class HidController : std::size_t {
Mouse,
Keyboard,
XPad,
- Unknown1,
- Unknown2,
- Unknown3,
- SixAxisSensor,
+ HomeButton,
+ SleepButton,
+ CaptureButton,
+ InputDetector,
+ UniquePad,
NPad,
Gesture,
+ ConsoleSixAxisSensor,
MaxControllers,
};
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
- explicit IAppletResource(Core::System& system);
+ explicit IAppletResource(Core::System& system_);
~IAppletResource() override;
void ActivateController(HidController controller);
@@ -65,11 +67,12 @@ private:
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
+ void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
std::shared_ptr<Kernel::SharedMemory> shared_mem;
std::shared_ptr<Core::Timing::EventType> pad_update_event;
- Core::System& system;
+ std::shared_ptr<Core::Timing::EventType> motion_update_event;
std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
controllers{};
@@ -77,28 +80,33 @@ private:
class Hid final : public ServiceFramework<Hid> {
public:
- explicit Hid(Core::System& system);
+ explicit Hid(Core::System& system_);
~Hid() override;
std::shared_ptr<IAppletResource> GetAppletResource();
private:
void CreateAppletResource(Kernel::HLERequestContext& ctx);
- void ActivateXpad(Kernel::HLERequestContext& ctx);
- void GetXpadIDs(Kernel::HLERequestContext& ctx);
void ActivateDebugPad(Kernel::HLERequestContext& ctx);
void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
void ActivateMouse(Kernel::HLERequestContext& ctx);
void ActivateKeyboard(Kernel::HLERequestContext& ctx);
void SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx);
- void ActivateGesture(Kernel::HLERequestContext& ctx);
- void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
+ void ActivateXpad(Kernel::HLERequestContext& ctx);
+ void GetXpadIDs(Kernel::HLERequestContext& ctx);
+ void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartSixAxisSensor(Kernel::HLERequestContext& ctx);
void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
+ void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx);
+ void SetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx);
+ void GetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx);
+ void ResetSixAxisSensorFusionParameters(Kernel::HLERequestContext& ctx);
void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
+ void ActivateGesture(Kernel::HLERequestContext& ctx);
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
@@ -107,6 +115,7 @@ private:
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
void DisconnectNpad(Kernel::HLERequestContext& ctx);
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
+ void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx);
@@ -118,15 +127,18 @@ private:
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
void GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
void SwapNpadAssignment(Kernel::HLERequestContext& ctx);
- void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
- void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
+ void IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx);
+ void EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx);
+ void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
void SendVibrationValue(Kernel::HLERequestContext& ctx);
- void SendVibrationValues(Kernel::HLERequestContext& ctx);
void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
- void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
void PermitVibration(Kernel::HLERequestContext& ctx);
void IsVibrationPermitted(Kernel::HLERequestContext& ctx);
+ void SendVibrationValues(Kernel::HLERequestContext& ctx);
+ void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
+ void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
+ void IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx);
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
@@ -138,9 +150,26 @@ private:
void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx);
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
+ void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
+ void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
+
+ enum class VibrationDeviceType : u32 {
+ LinearResonantActuator = 1,
+ };
+
+ enum class VibrationDevicePosition : u32 {
+ None = 0,
+ Left = 1,
+ Right = 2,
+ };
+
+ struct VibrationDeviceInfo {
+ VibrationDeviceType type{};
+ VibrationDevicePosition position{};
+ };
+ static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
std::shared_ptr<IAppletResource> applet_resource;
- Core::System& system;
};
/// Reload input devices. Used when input configuration changed
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index e82fd031b..c8413099f 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -12,7 +12,7 @@
namespace Service::HID {
-IRS::IRS(Core::System& system) : ServiceFramework{"irs"}, system(system) {
+IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} {
// clang-format off
static const FunctionInfo functions[] = {
{302, &IRS::ActivateIrsensor, "ActivateIrsensor"},
@@ -175,7 +175,7 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
IRS::~IRS() = default;
-IRS_SYS::IRS_SYS() : ServiceFramework{"irs:sys"} {
+IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{500, nullptr, "SetAppletResourceUserId"},
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index 8918ad6ca..be0c486ba 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -7,6 +7,10 @@
#include "core/hle/kernel/object.h"
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class SharedMemory;
}
@@ -15,7 +19,7 @@ namespace Service::HID {
class IRS final : public ServiceFramework<IRS> {
public:
- explicit IRS(Core::System& system);
+ explicit IRS(Core::System& system_);
~IRS() override;
private:
@@ -37,14 +41,14 @@ private:
void RunIrLedProcessor(Kernel::HLERequestContext& ctx);
void StopImageProcessorAsync(Kernel::HLERequestContext& ctx);
void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx);
+
std::shared_ptr<Kernel::SharedMemory> shared_mem;
const u32 device_handle{0xABCD};
- Core::System& system;
};
class IRS_SYS final : public ServiceFramework<IRS_SYS> {
public:
- explicit IRS_SYS();
+ explicit IRS_SYS(Core::System& system);
~IRS_SYS() override;
};
diff --git a/src/core/hle/service/hid/xcd.cpp b/src/core/hle/service/hid/xcd.cpp
index c8e9125f6..43a8840d0 100644
--- a/src/core/hle/service/hid/xcd.cpp
+++ b/src/core/hle/service/hid/xcd.cpp
@@ -6,7 +6,7 @@
namespace Service::HID {
-XCD_SYS::XCD_SYS() : ServiceFramework{"xcd:sys"} {
+XCD_SYS::XCD_SYS(Core::System& system_) : ServiceFramework{system_, "xcd:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetDataFormat"},
diff --git a/src/core/hle/service/hid/xcd.h b/src/core/hle/service/hid/xcd.h
index fd506d303..54932c228 100644
--- a/src/core/hle/service/hid/xcd.h
+++ b/src/core/hle/service/hid/xcd.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::HID {
class XCD_SYS final : public ServiceFramework<XCD_SYS> {
public:
- explicit XCD_SYS();
+ explicit XCD_SYS(Core::System& system_);
~XCD_SYS() override;
};
diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp
index 17350b403..f4490f3d9 100644
--- a/src/core/hle/service/lbl/lbl.cpp
+++ b/src/core/hle/service/lbl/lbl.cpp
@@ -15,35 +15,35 @@ namespace Service::LBL {
class LBL final : public ServiceFramework<LBL> {
public:
- explicit LBL() : ServiceFramework{"lbl"} {
+ explicit LBL(Core::System& system_) : ServiceFramework{system_, "lbl"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SaveCurrentSetting"},
{1, nullptr, "LoadCurrentSetting"},
- {2, nullptr, "SetCurrentBrightnessSetting"},
- {3, nullptr, "GetCurrentBrightnessSetting"},
+ {2, &LBL::SetCurrentBrightnessSetting, "SetCurrentBrightnessSetting"},
+ {3, &LBL::GetCurrentBrightnessSetting, "GetCurrentBrightnessSetting"},
{4, nullptr, "ApplyCurrentBrightnessSettingToBacklight"},
{5, nullptr, "GetBrightnessSettingAppliedToBacklight"},
- {6, nullptr, "SwitchBacklightOn"},
- {7, nullptr, "SwitchBacklightOff"},
- {8, nullptr, "GetBacklightSwitchStatus"},
- {9, nullptr, "EnableDimming"},
- {10, nullptr, "DisableDimming"},
- {11, nullptr, "IsDimmingEnabled"},
- {12, nullptr, "EnableAutoBrightnessControl"},
- {13, nullptr, "DisableAutoBrightnessControl"},
- {14, nullptr, "IsAutoBrightnessControlEnabled"},
- {15, nullptr, "SetAmbientLightSensorValue"},
- {16, nullptr, "GetAmbientLightSensorValue"},
- {17, nullptr, "SetBrightnessReflectionDelayLevel"},
- {18, nullptr, "GetBrightnessReflectionDelayLevel"},
- {19, nullptr, "SetCurrentBrightnessMapping"},
- {20, nullptr, "GetCurrentBrightnessMapping"},
- {21, nullptr, "SetCurrentAmbientLightSensorMapping"},
- {22, nullptr, "GetCurrentAmbientLightSensorMapping"},
- {23, nullptr, "IsAmbientLightSensorAvailable"},
- {24, nullptr, "SetCurrentBrightnessSettingForVrMode"},
- {25, nullptr, "GetCurrentBrightnessSettingForVrMode"},
+ {6, &LBL::SwitchBacklightOn, "SwitchBacklightOn"},
+ {7, &LBL::SwitchBacklightOff, "SwitchBacklightOff"},
+ {8, &LBL::GetBacklightSwitchStatus, "GetBacklightSwitchStatus"},
+ {9, &LBL::EnableDimming, "EnableDimming"},
+ {10, &LBL::DisableDimming, "DisableDimming"},
+ {11, &LBL::IsDimmingEnabled, "IsDimmingEnabled"},
+ {12, &LBL::EnableAutoBrightnessControl, "EnableAutoBrightnessControl"},
+ {13, &LBL::DisableAutoBrightnessControl, "DisableAutoBrightnessControl"},
+ {14, &LBL::IsAutoBrightnessControlEnabled, "IsAutoBrightnessControlEnabled"},
+ {15, &LBL::SetAmbientLightSensorValue, "SetAmbientLightSensorValue"},
+ {16, &LBL::GetAmbientLightSensorValue, "GetAmbientLightSensorValue"},
+ {17, &LBL::SetBrightnessReflectionDelayLevel, "SetBrightnessReflectionDelayLevel"},
+ {18, &LBL::GetBrightnessReflectionDelayLevel, "GetBrightnessReflectionDelayLevel"},
+ {19, &LBL::SetCurrentBrightnessMapping, "SetCurrentBrightnessMapping"},
+ {20, &LBL::GetCurrentBrightnessMapping, "GetCurrentBrightnessMapping"},
+ {21, &LBL::SetCurrentAmbientLightSensorMapping, "SetCurrentAmbientLightSensorMapping"},
+ {22, &LBL::GetCurrentAmbientLightSensorMapping, "GetCurrentAmbientLightSensorMapping"},
+ {23, &LBL::IsAmbientLightSensorAvailable, "IsAmbientLightSensorAvailable"},
+ {24, &LBL::SetCurrentBrightnessSettingForVrMode, "SetCurrentBrightnessSettingForVrMode"},
+ {25, &LBL::GetCurrentBrightnessSettingForVrMode, "GetCurrentBrightnessSettingForVrMode"},
{26, &LBL::EnableVrMode, "EnableVrMode"},
{27, &LBL::DisableVrMode, "DisableVrMode"},
{28, &LBL::IsVrModeEnabled, "IsVrModeEnabled"},
@@ -55,6 +55,237 @@ public:
}
private:
+ enum class BacklightSwitchStatus : u32 {
+ Off = 0,
+ On = 1,
+ };
+
+ void SetCurrentBrightnessSetting(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto brightness = rp.Pop<float>();
+
+ if (!std::isfinite(brightness)) {
+ LOG_ERROR(Service_LBL, "Brightness is infinite!");
+ brightness = 0.0f;
+ }
+
+ LOG_DEBUG(Service_LBL, "called brightness={}", brightness);
+
+ current_brightness = brightness;
+ update_instantly = true;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetCurrentBrightnessSetting(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto brightness = current_brightness;
+ if (!std::isfinite(brightness)) {
+ LOG_ERROR(Service_LBL, "Brightness is infinite!");
+ brightness = 0.0f;
+ }
+
+ LOG_DEBUG(Service_LBL, "called brightness={}", brightness);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(brightness);
+ }
+
+ void SwitchBacklightOn(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto fade_time = rp.Pop<u64_le>();
+ LOG_WARNING(Service_LBL, "(STUBBED) called, fade_time={}", fade_time);
+
+ backlight_enabled = true;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SwitchBacklightOff(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto fade_time = rp.Pop<u64_le>();
+ LOG_WARNING(Service_LBL, "(STUBBED) called, fade_time={}", fade_time);
+
+ backlight_enabled = false;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetBacklightSwitchStatus(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum<BacklightSwitchStatus>(backlight_enabled ? BacklightSwitchStatus::On
+ : BacklightSwitchStatus::Off);
+ }
+
+ void EnableDimming(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LBL, "called");
+
+ dimming = true;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void DisableDimming(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LBL, "called");
+
+ dimming = false;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void IsDimmingEnabled(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(dimming);
+ }
+
+ void EnableAutoBrightnessControl(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LBL, "called");
+ auto_brightness = true;
+ update_instantly = true;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void DisableAutoBrightnessControl(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LBL, "called");
+ auto_brightness = false;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void IsAutoBrightnessControlEnabled(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(auto_brightness);
+ }
+
+ void SetAmbientLightSensorValue(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto light_value = rp.Pop<float>();
+
+ LOG_DEBUG(Service_LBL, "called light_value={}", light_value);
+
+ ambient_light_value = light_value;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetAmbientLightSensorValue(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(ambient_light_value);
+ }
+
+ void SetBrightnessReflectionDelayLevel(Kernel::HLERequestContext& ctx) {
+ // This is Intentional, this function does absolutely nothing
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetBrightnessReflectionDelayLevel(Kernel::HLERequestContext& ctx) {
+ // This is intentional, the function is hard coded to return 0.0f on hardware
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(0.0f);
+ }
+
+ void SetCurrentBrightnessMapping(Kernel::HLERequestContext& ctx) {
+ // This is Intentional, this function does absolutely nothing
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetCurrentBrightnessMapping(Kernel::HLERequestContext& ctx) {
+ // This is Intentional, this function does absolutely nothing
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ // This function is suppose to return something but it seems like it doesn't
+ }
+
+ void SetCurrentAmbientLightSensorMapping(Kernel::HLERequestContext& ctx) {
+ // This is Intentional, this function does absolutely nothing
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetCurrentAmbientLightSensorMapping(Kernel::HLERequestContext& ctx) {
+ // This is Intentional, this function does absolutely nothing
+ LOG_DEBUG(Service_LBL, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ // This function is suppose to return something but it seems like it doesn't
+ }
+
+ void IsAmbientLightSensorAvailable(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_LBL, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ // TODO(ogniK): Only return true if there's no device error
+ rb.Push(true);
+ }
+
+ void SetCurrentBrightnessSettingForVrMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto brightness = rp.Pop<float>();
+
+ if (!std::isfinite(brightness)) {
+ LOG_ERROR(Service_LBL, "Brightness is infinite!");
+ brightness = 0.0f;
+ }
+
+ LOG_DEBUG(Service_LBL, "called brightness={}", brightness);
+
+ current_vr_brightness = brightness;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetCurrentBrightnessSettingForVrMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto brightness = current_vr_brightness;
+ if (!std::isfinite(brightness)) {
+ LOG_ERROR(Service_LBL, "Brightness is infinite!");
+ brightness = 0.0f;
+ }
+
+ LOG_DEBUG(Service_LBL, "called brightness={}", brightness);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(brightness);
+ }
+
void EnableVrMode(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_LBL, "called");
@@ -82,10 +313,18 @@ private:
}
bool vr_mode_enabled = false;
+ float current_brightness = 1.0f;
+ float backlight_brightness = 1.0f;
+ float ambient_light_value = 0.0f;
+ float current_vr_brightness = 1.0f;
+ bool dimming = true;
+ bool backlight_enabled = true;
+ bool update_instantly = false;
+ bool auto_brightness = false; // TODO(ogniK): Move to system settings
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<LBL>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<LBL>(system)->InstallAsService(sm);
}
} // namespace Service::LBL
diff --git a/src/core/hle/service/lbl/lbl.h b/src/core/hle/service/lbl/lbl.h
index bf6f400f8..9c2021026 100644
--- a/src/core/hle/service/lbl/lbl.h
+++ b/src/core/hle/service/lbl/lbl.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::LBL {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::LBL
diff --git a/src/core/hle/service/ldn/errors.h b/src/core/hle/service/ldn/errors.h
new file mode 100644
index 000000000..a718c5c66
--- /dev/null
+++ b/src/core/hle/service/ldn/errors.h
@@ -0,0 +1,13 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::LDN {
+
+constexpr ResultCode ERROR_DISABLED{ErrorModule::LDN, 22};
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 49972cd69..c630d93cd 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -6,6 +6,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/result.h"
+#include "core/hle/service/ldn/errors.h"
#include "core/hle/service/ldn/ldn.h"
#include "core/hle/service/sm/sm.h"
@@ -13,7 +14,7 @@ namespace Service::LDN {
class IMonitorService final : public ServiceFramework<IMonitorService> {
public:
- explicit IMonitorService() : ServiceFramework{"IMonitorService"} {
+ explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetStateForMonitor"},
@@ -33,7 +34,7 @@ public:
class LDNM final : public ServiceFramework<LDNM> {
public:
- explicit LDNM() : ServiceFramework{"ldn:m"} {
+ explicit LDNM(Core::System& system_) : ServiceFramework{system_, "ldn:m"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &LDNM::CreateMonitorService, "CreateMonitorService"}
@@ -48,15 +49,15 @@ public:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IMonitorService>();
+ rb.PushIpcInterface<IMonitorService>(system);
}
};
class ISystemLocalCommunicationService final
: public ServiceFramework<ISystemLocalCommunicationService> {
public:
- explicit ISystemLocalCommunicationService()
- : ServiceFramework{"ISystemLocalCommunicationService"} {
+ explicit ISystemLocalCommunicationService(Core::System& system_)
+ : ServiceFramework{system_, "ISystemLocalCommunicationService"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetState"},
@@ -99,10 +100,11 @@ public:
class IUserLocalCommunicationService final
: public ServiceFramework<IUserLocalCommunicationService> {
public:
- explicit IUserLocalCommunicationService() : ServiceFramework{"IUserLocalCommunicationService"} {
+ explicit IUserLocalCommunicationService(Core::System& system_)
+ : ServiceFramework{system_, "IUserLocalCommunicationService"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "GetState"},
+ {0, &IUserLocalCommunicationService::GetState, "GetState"},
{1, nullptr, "GetNetworkInfo"},
{2, nullptr, "GetIpv4Address"},
{3, nullptr, "GetDisconnectReason"},
@@ -137,18 +139,43 @@ public:
RegisterHandlers(functions);
}
- void Initialize2(Kernel::HLERequestContext& ctx) {
+ void GetState(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_LDN, "(STUBBED) called");
- // Result success seem make this services start network and continue.
- // If we just pass result error then it will stop and maybe try again and again.
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ // Indicate a network error, as we do not actually emulate LDN
+ rb.Push(static_cast<u32>(State::Error));
+
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Initialize2(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LDN, "called");
+
+ is_initialized = true;
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_UNKNOWN);
+ rb.Push(RESULT_SUCCESS);
}
+
+private:
+ enum class State {
+ None,
+ Initialized,
+ AccessPointOpened,
+ AccessPointCreated,
+ StationOpened,
+ StationConnected,
+ Error,
+ };
+
+ bool is_initialized{};
};
class LDNS final : public ServiceFramework<LDNS> {
public:
- explicit LDNS() : ServiceFramework{"ldn:s"} {
+ explicit LDNS(Core::System& system_) : ServiceFramework{system_, "ldn:s"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &LDNS::CreateSystemLocalCommunicationService, "CreateSystemLocalCommunicationService"},
@@ -163,13 +190,13 @@ public:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemLocalCommunicationService>();
+ rb.PushIpcInterface<ISystemLocalCommunicationService>(system);
}
};
class LDNU final : public ServiceFramework<LDNU> {
public:
- explicit LDNU() : ServiceFramework{"ldn:u"} {
+ explicit LDNU(Core::System& system_) : ServiceFramework{system_, "ldn:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &LDNU::CreateUserLocalCommunicationService, "CreateUserLocalCommunicationService"},
@@ -184,14 +211,14 @@ public:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IUserLocalCommunicationService>();
+ rb.PushIpcInterface<IUserLocalCommunicationService>(system);
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<LDNM>()->InstallAsService(sm);
- std::make_shared<LDNS>()->InstallAsService(sm);
- std::make_shared<LDNU>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<LDNM>(system)->InstallAsService(sm);
+ std::make_shared<LDNS>(system)->InstallAsService(sm);
+ std::make_shared<LDNU>(system)->InstallAsService(sm);
}
} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.h b/src/core/hle/service/ldn/ldn.h
index 6b2a3c2b2..3ccd9738b 100644
--- a/src/core/hle/service/ldn/ldn.h
+++ b/src/core/hle/service/ldn/ldn.h
@@ -4,6 +4,10 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
@@ -11,6 +15,6 @@ class ServiceManager;
namespace Service::LDN {
/// Registers all LDN services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::LDN
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index d8cd10e31..c724d2554 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -9,11 +9,12 @@
#include "common/alignment.h"
#include "common/hex_util.h"
#include "common/scope_exit.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/memory/system_control.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/svc_results.h"
#include "core/hle/service/ldr/ldr.h"
#include "core/hle/service/service.h"
#include "core/loader/nro.h"
@@ -23,7 +24,7 @@ namespace Service::LDR {
constexpr ResultCode ERROR_INSUFFICIENT_ADDRESS_SPACE{ErrorModule::RO, 2};
-constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
+[[maybe_unused]] constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51};
constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52};
constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53};
constexpr ResultCode ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54};
@@ -33,7 +34,7 @@ constexpr ResultCode ERROR_ALREADY_LOADED{ErrorModule::Loader, 57};
constexpr ResultCode ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81};
constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::Loader, 82};
constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84};
-constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
+[[maybe_unused]] constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85};
constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87};
constexpr std::size_t MAXIMUM_LOADED_RO{0x40};
@@ -114,7 +115,7 @@ static_assert(sizeof(NROInfo) == 0x60, "NROInfo has invalid size.");
class DebugMonitor final : public ServiceFramework<DebugMonitor> {
public:
- explicit DebugMonitor() : ServiceFramework{"ldr:dmnt"} {
+ explicit DebugMonitor(Core::System& system_) : ServiceFramework{system_, "ldr:dmnt"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "AddProcessToDebugLaunchQueue"},
@@ -129,7 +130,7 @@ public:
class ProcessManager final : public ServiceFramework<ProcessManager> {
public:
- explicit ProcessManager() : ServiceFramework{"ldr:pm"} {
+ explicit ProcessManager(Core::System& system_) : ServiceFramework{system_, "ldr:pm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateProcess"},
@@ -146,7 +147,7 @@ public:
class Shell final : public ServiceFramework<Shell> {
public:
- explicit Shell() : ServiceFramework{"ldr:shel"} {
+ explicit Shell(Core::System& system_) : ServiceFramework{system_, "ldr:shel"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "AddProcessToLaunchQueue"},
@@ -160,13 +161,13 @@ public:
class RelocatableObject final : public ServiceFramework<RelocatableObject> {
public:
- explicit RelocatableObject(Core::System& system) : ServiceFramework{"ldr:ro"}, system(system) {
+ explicit RelocatableObject(Core::System& system_) : ServiceFramework{system_, "ldr:ro"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &RelocatableObject::LoadNro, "LoadNro"},
{1, &RelocatableObject::UnloadNro, "UnloadNro"},
{2, &RelocatableObject::LoadNrr, "LoadNrr"},
- {3, nullptr, "UnloadNrr"},
+ {3, &RelocatableObject::UnloadNrr, "UnloadNrr"},
{4, &RelocatableObject::Initialize, "Initialize"},
{10, nullptr, "LoadNrrEx"},
};
@@ -272,6 +273,20 @@ public:
rb.Push(RESULT_SUCCESS);
}
+ void UnloadNrr(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto pid = rp.Pop<u64>();
+ const auto nrr_address = rp.Pop<VAddr>();
+
+ LOG_DEBUG(Service_LDR, "called with pid={}, nrr_address={:016X}", pid, nrr_address);
+
+ nrr.erase(nrr_address);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+
+ rb.Push(RESULT_SUCCESS);
+ }
+
bool ValidateRegionForMap(Kernel::Memory::PageTable& page_table, VAddr start,
std::size_t size) const {
constexpr std::size_t padding_size{4 * Kernel::Memory::PageSize};
@@ -315,7 +330,7 @@ public:
const VAddr addr{GetRandomMapRegion(page_table, size)};
const ResultCode result{page_table.MapProcessCodeMemory(addr, baseAddress, size)};
- if (result == Kernel::ERR_INVALID_ADDRESS_STATE) {
+ if (result == Kernel::ResultInvalidCurrentMemory) {
continue;
}
@@ -346,7 +361,7 @@ public:
const ResultCode result{
page_table.MapProcessCodeMemory(addr + nro_size, bss_addr, bss_size)};
- if (result == Kernel::ERR_INVALID_ADDRESS_STATE) {
+ if (result == Kernel::ResultInvalidCurrentMemory) {
continue;
}
@@ -512,9 +527,6 @@ public:
header.segment_headers[RO_INDEX].memory_size,
header.segment_headers[DATA_INDEX].memory_size, nro_address});
- // Invalidate JIT caches for the newly mapped process code
- system.InvalidateCpuInstructionCaches();
-
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push(*map_result);
@@ -575,8 +587,6 @@ public:
const auto result{UnmapNro(iter->second)};
- system.InvalidateCpuInstructionCaches();
-
nro.erase(iter);
IPC::ResponseBuilder rb{ctx, 2};
@@ -624,13 +634,12 @@ private:
Common::Is4KBAligned(header.segment_headers[RO_INDEX].memory_size) &&
Common::Is4KBAligned(header.segment_headers[DATA_INDEX].memory_size);
}
- Core::System& system;
};
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
- std::make_shared<DebugMonitor>()->InstallAsService(sm);
- std::make_shared<ProcessManager>()->InstallAsService(sm);
- std::make_shared<Shell>()->InstallAsService(sm);
+ std::make_shared<DebugMonitor>(system)->InstallAsService(sm);
+ std::make_shared<ProcessManager>(system)->InstallAsService(sm);
+ std::make_shared<Shell>(system)->InstallAsService(sm);
std::make_shared<RelocatableObject>(system)->InstallAsService(sm);
}
diff --git a/src/core/hle/service/ldr/ldr.h b/src/core/hle/service/ldr/ldr.h
index 7ac8c0b65..104fc15c5 100644
--- a/src/core/hle/service/ldr/ldr.h
+++ b/src/core/hle/service/ldr/ldr.h
@@ -4,6 +4,10 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp
index dec96b771..7d7542fc2 100644
--- a/src/core/hle/service/lm/lm.cpp
+++ b/src/core/hle/service/lm/lm.cpp
@@ -5,20 +5,71 @@
#include <sstream>
#include <string>
+#include <optional>
+#include <unordered_map>
+#include <boost/container_hash/hash.hpp>
#include "common/logging/log.h"
#include "common/scope_exit.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/lm/lm.h"
-#include "core/hle/service/lm/manager.h"
#include "core/hle/service/service.h"
#include "core/memory.h"
namespace Service::LM {
+enum class LogSeverity : u8 {
+ Trace = 0,
+ Info = 1,
+ Warning = 2,
+ Error = 3,
+ Fatal = 4,
+};
+
+// To keep flags out of hashing as well as the payload size
+struct LogPacketHeaderEntry {
+ u64_le pid{};
+ u64_le tid{};
+ LogSeverity severity{};
+ u8 verbosity{};
+
+ auto operator<=>(const LogPacketHeaderEntry&) const = default;
+};
+} // namespace Service::LM
+
+namespace std {
+template <>
+struct hash<Service::LM::LogPacketHeaderEntry> {
+ std::size_t operator()(const Service::LM::LogPacketHeaderEntry& k) const noexcept {
+ std::size_t seed{};
+ boost::hash_combine(seed, k.pid);
+ boost::hash_combine(seed, k.tid);
+ boost::hash_combine(seed, k.severity);
+ boost::hash_combine(seed, k.verbosity);
+ return seed;
+ };
+};
+} // namespace std
+
+namespace Service::LM {
+
+enum class LogDestination : u32 {
+ TargetManager = 1 << 0,
+ Uart = 1 << 1,
+ UartSleep = 1 << 2,
+ All = 0xffff,
+};
+DECLARE_ENUM_FLAG_OPERATORS(LogDestination);
+
+enum class LogPacketFlags : u8 {
+ Head = 1 << 0,
+ Tail = 1 << 1,
+ LittleEndian = 1 << 2,
+};
+DECLARE_ENUM_FLAG_OPERATORS(LogPacketFlags);
class ILogger final : public ServiceFramework<ILogger> {
public:
- explicit ILogger(Manager& manager_, Core::Memory::Memory& memory_)
- : ServiceFramework("ILogger"), manager{manager_}, memory{memory_} {
+ explicit ILogger(Core::System& system_) : ServiceFramework{system_, "ILogger"} {
static const FunctionInfo functions[] = {
{0, &ILogger::Log, "Log"},
{1, &ILogger::SetDestination, "SetDestination"},
@@ -28,60 +79,269 @@ public:
private:
void Log(Kernel::HLERequestContext& ctx) {
+ std::size_t offset{};
+ const auto data = ctx.ReadBuffer();
+
// This function only succeeds - Get that out of the way
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- // Read MessageHeader, despite not doing anything with it right now
- MessageHeader header{};
- VAddr addr{ctx.BufferDescriptorX()[0].Address()};
- const VAddr end_addr{addr + ctx.BufferDescriptorX()[0].size};
- memory.ReadBlock(addr, &header, sizeof(MessageHeader));
- addr += sizeof(MessageHeader);
-
- FieldMap fields;
- while (addr < end_addr) {
- const auto field = static_cast<Field>(memory.Read8(addr++));
- const auto length = memory.Read8(addr++);
+ if (data.size() < sizeof(LogPacketHeader)) {
+ LOG_ERROR(Service_LM, "Data size is too small for header! size={}", data.size());
+ return;
+ }
- if (static_cast<Field>(memory.Read8(addr)) == Field::Skip) {
- ++addr;
- }
+ LogPacketHeader header{};
+ std::memcpy(&header, data.data(), sizeof(LogPacketHeader));
+ offset += sizeof(LogPacketHeader);
- SCOPE_EXIT({ addr += length; });
+ LogPacketHeaderEntry entry{
+ .pid = header.pid,
+ .tid = header.tid,
+ .severity = header.severity,
+ .verbosity = header.verbosity,
+ };
- if (field == Field::Skip) {
- continue;
+ if (True(header.flags & LogPacketFlags::Head)) {
+ std::vector<u8> tmp(data.size() - sizeof(LogPacketHeader));
+ std::memcpy(tmp.data(), data.data() + offset, tmp.size());
+ entries[entry] = std::move(tmp);
+ } else {
+ // Append to existing entry
+ if (!entries.contains(entry)) {
+ LOG_ERROR(Service_LM, "Log entry does not exist!");
+ return;
}
+ std::vector<u8> tmp(data.size() - sizeof(LogPacketHeader));
- std::vector<u8> data(length);
- memory.ReadBlock(addr, data.data(), length);
- fields.emplace(field, std::move(data));
+ auto& existing_entry = entries[entry];
+ const auto base = existing_entry.size();
+ existing_entry.resize(base + (data.size() - sizeof(LogPacketHeader)));
+ std::memcpy(existing_entry.data() + base, data.data() + offset,
+ (data.size() - sizeof(LogPacketHeader)));
}
- manager.Log({header, std::move(fields)});
+ if (True(header.flags & LogPacketFlags::Tail)) {
+ auto it = entries.find(entry);
+ if (it == entries.end()) {
+ LOG_ERROR(Service_LM, "Log entry does not exist!");
+ return;
+ }
+ ParseLog(it->first, it->second);
+ entries.erase(it);
+ }
}
void SetDestination(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto destination = rp.PopEnum<DestinationFlag>();
-
- LOG_DEBUG(Service_LM, "called, destination={:08X}", static_cast<u32>(destination));
+ const auto log_destination = rp.PopEnum<LogDestination>();
- manager.SetDestination(destination);
+ LOG_DEBUG(Service_LM, "called, destination={}", DestinationToString(log_destination));
+ destination = log_destination;
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
- Manager& manager;
- Core::Memory::Memory& memory;
+ u64 ReadLeb128(const std::vector<u8>& data, std::size_t& offset) {
+ u64 result{};
+ u32 shift{};
+
+ for (std::size_t i = 0; i < sizeof(u64); i++) {
+ const auto v = data[offset];
+ result |= (static_cast<u64>(v & 0x7f) << shift);
+ shift += 7;
+ offset++;
+ if (offset >= data.size() || ((v & 0x80) == 0)) {
+ break;
+ }
+ }
+ return result;
+ }
+
+ std::optional<std::string> ReadString(const std::vector<u8>& data, std::size_t& offset,
+ std::size_t length) {
+ if (length == 0) {
+ return std::nullopt;
+ }
+ const auto length_to_read = std::min(length, data.size() - offset);
+
+ std::string output(length_to_read, '\0');
+ std::memcpy(output.data(), data.data() + offset, length_to_read);
+ offset += length_to_read;
+ return output;
+ }
+
+ u32_le ReadAsU32(const std::vector<u8>& data, std::size_t& offset, std::size_t length) {
+ ASSERT(length == sizeof(u32));
+ u32_le output{};
+ std::memcpy(&output, data.data() + offset, sizeof(u32));
+ offset += length;
+ return output;
+ }
+
+ u64_le ReadAsU64(const std::vector<u8>& data, std::size_t& offset, std::size_t length) {
+ ASSERT(length == sizeof(u64));
+ u64_le output{};
+ std::memcpy(&output, data.data() + offset, sizeof(u64));
+ offset += length;
+ return output;
+ }
+
+ void ParseLog(const LogPacketHeaderEntry entry, const std::vector<u8>& log_data) {
+ // Possible entries
+ std::optional<std::string> text_log;
+ std::optional<u32> line_number;
+ std::optional<std::string> file_name;
+ std::optional<std::string> function_name;
+ std::optional<std::string> module_name;
+ std::optional<std::string> thread_name;
+ std::optional<u64> log_pack_drop_count;
+ std::optional<s64> user_system_clock;
+ std::optional<std::string> process_name;
+
+ std::size_t offset{};
+ while (offset < log_data.size()) {
+ const auto key = static_cast<LogDataChunkKey>(ReadLeb128(log_data, offset));
+ const auto chunk_size = ReadLeb128(log_data, offset);
+
+ switch (key) {
+ case LogDataChunkKey::LogSessionBegin:
+ case LogDataChunkKey::LogSessionEnd:
+ break;
+ case LogDataChunkKey::TextLog:
+ text_log = ReadString(log_data, offset, chunk_size);
+ break;
+ case LogDataChunkKey::LineNumber:
+ line_number = ReadAsU32(log_data, offset, chunk_size);
+ break;
+ case LogDataChunkKey::FileName:
+ file_name = ReadString(log_data, offset, chunk_size);
+ break;
+ case LogDataChunkKey::FunctionName:
+ function_name = ReadString(log_data, offset, chunk_size);
+ break;
+ case LogDataChunkKey::ModuleName:
+ module_name = ReadString(log_data, offset, chunk_size);
+ break;
+ case LogDataChunkKey::ThreadName:
+ thread_name = ReadString(log_data, offset, chunk_size);
+ break;
+ case LogDataChunkKey::LogPacketDropCount:
+ log_pack_drop_count = ReadAsU64(log_data, offset, chunk_size);
+ break;
+ case LogDataChunkKey::UserSystemClock:
+ user_system_clock = ReadAsU64(log_data, offset, chunk_size);
+ break;
+ case LogDataChunkKey::ProcessName:
+ process_name = ReadString(log_data, offset, chunk_size);
+ break;
+ }
+ }
+
+ std::string output_log{};
+ if (process_name) {
+ output_log += fmt::format("Process: {}\n", *process_name);
+ }
+ if (module_name) {
+ output_log += fmt::format("Module: {}\n", *module_name);
+ }
+ if (file_name) {
+ output_log += fmt::format("File: {}\n", *file_name);
+ }
+ if (function_name) {
+ output_log += fmt::format("Function: {}\n", *function_name);
+ }
+ if (line_number && *line_number != 0) {
+ output_log += fmt::format("Line: {}\n", *line_number);
+ }
+ output_log += fmt::format("ProcessID: {:X}\n", entry.pid);
+ output_log += fmt::format("ThreadID: {:X}\n", entry.tid);
+
+ if (text_log) {
+ output_log += fmt::format("Log Text: {}\n", *text_log);
+ }
+
+ switch (entry.severity) {
+ case LogSeverity::Trace:
+ LOG_DEBUG(Service_LM, "LogManager TRACE ({}):\n{}", DestinationToString(destination),
+ output_log);
+ break;
+ case LogSeverity::Info:
+ LOG_INFO(Service_LM, "LogManager INFO ({}):\n{}", DestinationToString(destination),
+ output_log);
+ break;
+ case LogSeverity::Warning:
+ LOG_WARNING(Service_LM, "LogManager WARNING ({}):\n{}",
+ DestinationToString(destination), output_log);
+ break;
+ case LogSeverity::Error:
+ LOG_ERROR(Service_LM, "LogManager ERROR ({}):\n{}", DestinationToString(destination),
+ output_log);
+ break;
+ case LogSeverity::Fatal:
+ LOG_CRITICAL(Service_LM, "LogManager FATAL ({}):\n{}", DestinationToString(destination),
+ output_log);
+ break;
+ default:
+ LOG_CRITICAL(Service_LM, "LogManager UNKNOWN ({}):\n{}",
+ DestinationToString(destination), output_log);
+ break;
+ }
+ }
+
+ static std::string DestinationToString(LogDestination destination) {
+ if (True(destination & LogDestination::All)) {
+ return "TargetManager | Uart | UartSleep";
+ }
+ std::string output{};
+ if (True(destination & LogDestination::TargetManager)) {
+ output += "| TargetManager";
+ }
+ if (True(destination & LogDestination::Uart)) {
+ output += "| Uart";
+ }
+ if (True(destination & LogDestination::UartSleep)) {
+ output += "| UartSleep";
+ }
+ if (output.length() > 0) {
+ return output.substr(2);
+ }
+ return "No Destination";
+ }
+
+ enum class LogDataChunkKey : u32 {
+ LogSessionBegin = 0,
+ LogSessionEnd = 1,
+ TextLog = 2,
+ LineNumber = 3,
+ FileName = 4,
+ FunctionName = 5,
+ ModuleName = 6,
+ ThreadName = 7,
+ LogPacketDropCount = 8,
+ UserSystemClock = 9,
+ ProcessName = 10,
+ };
+
+ struct LogPacketHeader {
+ u64_le pid{};
+ u64_le tid{};
+ LogPacketFlags flags{};
+ INSERT_PADDING_BYTES(1);
+ LogSeverity severity{};
+ u8 verbosity{};
+ u32_le payload_size{};
+ };
+ static_assert(sizeof(LogPacketHeader) == 0x18, "LogPacketHeader is an invalid size");
+
+ std::unordered_map<LogPacketHeaderEntry, std::vector<u8>> entries{};
+ LogDestination destination{LogDestination::All};
};
class LM final : public ServiceFramework<LM> {
public:
- explicit LM(Manager& manager_, Core::Memory::Memory& memory_)
- : ServiceFramework{"lm"}, manager{manager_}, memory{memory_} {
+ explicit LM(Core::System& system_) : ServiceFramework{system_, "lm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &LM::OpenLogger, "OpenLogger"},
@@ -97,16 +357,12 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ILogger>(manager, memory);
+ rb.PushIpcInterface<ILogger>(system);
}
-
- Manager& manager;
- Core::Memory::Memory& memory;
};
void InstallInterfaces(Core::System& system) {
- std::make_shared<LM>(system.GetLogManager(), system.Memory())
- ->InstallAsService(system.ServiceManager());
+ std::make_shared<LM>(system)->InstallAsService(system.ServiceManager());
}
} // namespace Service::LM
diff --git a/src/core/hle/service/lm/manager.cpp b/src/core/hle/service/lm/manager.cpp
deleted file mode 100644
index 3ee2374e7..000000000
--- a/src/core/hle/service/lm/manager.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/string_util.h"
-#include "core/hle/service/lm/manager.h"
-#include "core/reporter.h"
-
-namespace Service::LM {
-
-std::ostream& operator<<(std::ostream& os, DestinationFlag dest) {
- std::vector<std::string> array;
- const auto check_single_flag = [dest, &array](DestinationFlag check, std::string name) {
- if ((static_cast<u32>(check) & static_cast<u32>(dest)) != 0) {
- array.emplace_back(std::move(name));
- }
- };
-
- check_single_flag(DestinationFlag::Default, "Default");
- check_single_flag(DestinationFlag::UART, "UART");
- check_single_flag(DestinationFlag::UARTSleeping, "UART (Sleeping)");
-
- os << "[";
- for (const auto& entry : array) {
- os << entry << ", ";
- }
- return os << "]";
-}
-
-std::ostream& operator<<(std::ostream& os, MessageHeader::Severity severity) {
- switch (severity) {
- case MessageHeader::Severity::Trace:
- return os << "Trace";
- case MessageHeader::Severity::Info:
- return os << "Info";
- case MessageHeader::Severity::Warning:
- return os << "Warning";
- case MessageHeader::Severity::Error:
- return os << "Error";
- case MessageHeader::Severity::Critical:
- return os << "Critical";
- default:
- return os << fmt::format("{:08X}", static_cast<u32>(severity));
- }
-}
-
-std::ostream& operator<<(std::ostream& os, Field field) {
- switch (field) {
- case Field::Skip:
- return os << "Skip";
- case Field::Message:
- return os << "Message";
- case Field::Line:
- return os << "Line";
- case Field::Filename:
- return os << "Filename";
- case Field::Function:
- return os << "Function";
- case Field::Module:
- return os << "Module";
- case Field::Thread:
- return os << "Thread";
- default:
- return os << fmt::format("{:08X}", static_cast<u32>(field));
- }
-}
-
-std::string FormatField(Field type, const std::vector<u8>& data) {
- switch (type) {
- case Field::Skip:
- return "";
- case Field::Line:
- if (data.size() >= sizeof(u32)) {
- u32 line;
- std::memcpy(&line, data.data(), sizeof(u32));
- return fmt::format("{}", line);
- }
- return "[ERROR DECODING LINE NUMBER]";
- case Field::Message:
- case Field::Filename:
- case Field::Function:
- case Field::Module:
- case Field::Thread:
- return Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(data.data()), data.size());
- default:
- UNIMPLEMENTED_MSG("Unimplemented field type={}", type);
- return "";
- }
-}
-
-Manager::Manager(Core::Reporter& reporter) : reporter(reporter) {}
-
-Manager::~Manager() = default;
-
-void Manager::SetEnabled(bool enabled) {
- this->enabled = enabled;
-}
-
-void Manager::SetDestination(DestinationFlag destination) {
- this->destination = destination;
-}
-
-void Manager::Log(LogMessage message) {
- if (message.header.IsHeadLog()) {
- InitializeLog();
- }
-
- current_log.emplace_back(std::move(message));
-
- if (current_log.back().header.IsTailLog()) {
- FinalizeLog();
- }
-}
-
-void Manager::Flush() {
- FinalizeLog();
-}
-
-void Manager::InitializeLog() {
- current_log.clear();
-
- LOG_INFO(Service_LM, "Initialized new log session");
-}
-
-void Manager::FinalizeLog() {
- reporter.SaveLogReport(static_cast<u32>(destination), std::move(current_log));
-
- LOG_INFO(Service_LM, "Finalized current log session");
-}
-
-} // namespace Service::LM
diff --git a/src/core/hle/service/lm/manager.h b/src/core/hle/service/lm/manager.h
deleted file mode 100644
index 544e636ba..000000000
--- a/src/core/hle/service/lm/manager.h
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2019 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <map>
-#include <ostream>
-#include <vector>
-#include "common/bit_field.h"
-#include "common/common_types.h"
-#include "common/swap.h"
-
-namespace Core {
-class Reporter;
-}
-
-namespace Service::LM {
-
-enum class DestinationFlag : u32 {
- Default = 1,
- UART = 2,
- UARTSleeping = 4,
-
- All = 0xFFFF,
-};
-
-struct MessageHeader {
- enum Flags : u32_le {
- IsHead = 1,
- IsTail = 2,
- };
- enum Severity : u32_le {
- Trace,
- Info,
- Warning,
- Error,
- Critical,
- };
-
- u64_le pid;
- u64_le thread_context;
- union {
- BitField<0, 16, Flags> flags;
- BitField<16, 8, Severity> severity;
- BitField<24, 8, u32> verbosity;
- };
- u32_le payload_size;
-
- bool IsHeadLog() const {
- return flags & IsHead;
- }
- bool IsTailLog() const {
- return flags & IsTail;
- }
-};
-static_assert(sizeof(MessageHeader) == 0x18, "MessageHeader is incorrect size");
-
-enum class Field : u8 {
- Skip = 1,
- Message = 2,
- Line = 3,
- Filename = 4,
- Function = 5,
- Module = 6,
- Thread = 7,
-};
-
-std::ostream& operator<<(std::ostream& os, DestinationFlag dest);
-std::ostream& operator<<(std::ostream& os, MessageHeader::Severity severity);
-std::ostream& operator<<(std::ostream& os, Field field);
-
-using FieldMap = std::map<Field, std::vector<u8>>;
-
-struct LogMessage {
- MessageHeader header;
- FieldMap fields;
-};
-
-std::string FormatField(Field type, const std::vector<u8>& data);
-
-class Manager {
-public:
- explicit Manager(Core::Reporter& reporter);
- ~Manager();
-
- void SetEnabled(bool enabled);
- void SetDestination(DestinationFlag destination);
-
- void Log(LogMessage message);
-
- void Flush();
-
-private:
- void InitializeLog();
- void FinalizeLog();
-
- bool enabled = true;
- DestinationFlag destination = DestinationFlag::All;
-
- std::vector<LogMessage> current_log;
-
- Core::Reporter& reporter;
-};
-
-} // namespace Service::LM
diff --git a/src/core/hle/service/mig/mig.cpp b/src/core/hle/service/mig/mig.cpp
index 113a4665c..1599d941b 100644
--- a/src/core/hle/service/mig/mig.cpp
+++ b/src/core/hle/service/mig/mig.cpp
@@ -12,7 +12,7 @@ namespace Service::Migration {
class MIG_USR final : public ServiceFramework<MIG_USR> {
public:
- explicit MIG_USR() : ServiceFramework{"mig:usr"} {
+ explicit MIG_USR(Core::System& system_) : ServiceFramework{system_, "mig:usr"} {
// clang-format off
static const FunctionInfo functions[] = {
{10, nullptr, "TryGetLastMigrationInfo"},
@@ -33,8 +33,8 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<MIG_USR>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<MIG_USR>(system)->InstallAsService(sm);
}
} // namespace Service::Migration
diff --git a/src/core/hle/service/mig/mig.h b/src/core/hle/service/mig/mig.h
index 288c1c1b3..2b24cdf2c 100644
--- a/src/core/hle/service/mig/mig.h
+++ b/src/core/hle/service/mig/mig.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::Migration {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::Migration
diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp
index 4730070cb..70350a2a3 100644
--- a/src/core/hle/service/mii/manager.cpp
+++ b/src/core/hle/service/mii/manager.cpp
@@ -21,7 +21,7 @@ namespace {
constexpr ResultCode ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
-constexpr std::size_t DefaultMiiCount{sizeof(RawData::DefaultMii) / sizeof(DefaultMii)};
+constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'};
constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7};
@@ -100,6 +100,7 @@ MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
.mole_scale = static_cast<u8>(bf.mole_scale.Value()),
.mole_x = static_cast<u8>(bf.mole_x.Value()),
.mole_y = static_cast<u8>(bf.mole_y.Value()),
+ .padding = 0,
};
}
@@ -131,7 +132,7 @@ template <typename T>
T GetRandomValue(T min, T max) {
std::random_device device;
std::mt19937 gen(device());
- std::uniform_int_distribution<u64> distribution(0, static_cast<u64>(max));
+ std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max));
return static_cast<T>(distribution(gen));
}
@@ -140,13 +141,6 @@ T GetRandomValue(T max) {
return GetRandomValue<T>({}, max);
}
-template <typename T>
-T GetArrayValue(const u8* data, std::size_t index) {
- T result{};
- std::memcpy(&result, &data[index * sizeof(T)], sizeof(T));
- return result;
-}
-
MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) {
MiiStoreBitFields bf{};
@@ -192,32 +186,20 @@ MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Commo
const std::size_t index{3 * static_cast<std::size_t>(age) +
9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
- const auto faceline_type_info{
- GetArrayValue<RandomMiiData4>(&RawData::RandomMiiFaceline[0], index)};
- const auto faceline_color_info{GetArrayValue<RandomMiiData3>(
- RawData::RandomMiiFacelineColor.data(),
+ const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)};
+ const auto faceline_color_info{RawData::RandomMiiFacelineColor.at(
3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
- const auto faceline_wrinkle_info{
- GetArrayValue<RandomMiiData4>(RawData::RandomMiiFacelineWrinkle.data(), index)};
- const auto faceline_makeup_info{
- GetArrayValue<RandomMiiData4>(RawData::RandomMiiFacelineMakeup.data(), index)};
- const auto hair_type_info{
- GetArrayValue<RandomMiiData4>(RawData::RandomMiiHairType.data(), index)};
- const auto hair_color_info{GetArrayValue<RandomMiiData3>(RawData::RandomMiiHairColor.data(),
- 3 * static_cast<std::size_t>(race) +
- static_cast<std::size_t>(age))};
- const auto eye_type_info{
- GetArrayValue<RandomMiiData4>(RawData::RandomMiiEyeType.data(), index)};
- const auto eye_color_info{GetArrayValue<RandomMiiData2>(RawData::RandomMiiEyeColor.data(),
- static_cast<std::size_t>(race))};
- const auto eyebrow_type_info{
- GetArrayValue<RandomMiiData4>(RawData::RandomMiiEyebrowType.data(), index)};
- const auto nose_type_info{
- GetArrayValue<RandomMiiData4>(RawData::RandomMiiNoseType.data(), index)};
- const auto mouth_type_info{
- GetArrayValue<RandomMiiData4>(RawData::RandomMiiMouthType.data(), index)};
- const auto glasses_type_info{GetArrayValue<RandomMiiData2>(RawData::RandomMiiGlassType.data(),
- static_cast<std::size_t>(age))};
+ const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
+ const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
+ const auto hair_type_info{RawData::RandomMiiHairType.at(index)};
+ const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
+ static_cast<std::size_t>(age))};
+ const auto eye_type_info{RawData::RandomMiiEyeType.at(index)};
+ const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
+ const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
+ const auto nose_type_info{RawData::RandomMiiNoseType.at(index)};
+ const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)};
+ const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
bf.faceline_type.Assign(
faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
@@ -428,7 +410,7 @@ bool MiiManager::IsFullDatabase() const {
}
u32 MiiManager::GetCount(SourceFlag source_flag) const {
- u32 count{};
+ std::size_t count{};
if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
// TODO(bunnei): We don't implement the Mii database, but when we do, update this
count += 0;
@@ -436,7 +418,7 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
count += DefaultMiiCount;
}
- return count;
+ return static_cast<u32>(count);
}
ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info,
@@ -454,8 +436,7 @@ MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
}
MiiInfo MiiManager::BuildDefault(std::size_t index) {
- return ConvertStoreDataToInfo(BuildDefaultStoreData(
- GetArrayValue<DefaultMii>(RawData::DefaultMii.data(), index), user_id));
+ return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
}
ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
diff --git a/src/core/hle/service/mii/manager.h b/src/core/hle/service/mii/manager.h
index 927451dea..2106a528a 100644
--- a/src/core/hle/service/mii/manager.h
+++ b/src/core/hle/service/mii/manager.h
@@ -27,58 +27,58 @@ enum class SourceFlag : u32 {
DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
struct MiiInfo {
- Common::UUID uuid{Common::INVALID_UUID};
- std::array<char16_t, 11> name{};
- u8 font_region{};
- u8 favorite_color{};
- u8 gender{};
- u8 height{};
- u8 build{};
- u8 type{};
- u8 region_move{};
- u8 faceline_type{};
- u8 faceline_color{};
- u8 faceline_wrinkle{};
- u8 faceline_make{};
- u8 hair_type{};
- u8 hair_color{};
- u8 hair_flip{};
- u8 eye_type{};
- u8 eye_color{};
- u8 eye_scale{};
- u8 eye_aspect{};
- u8 eye_rotate{};
- u8 eye_x{};
- u8 eye_y{};
- u8 eyebrow_type{};
- u8 eyebrow_color{};
- u8 eyebrow_scale{};
- u8 eyebrow_aspect{};
- u8 eyebrow_rotate{};
- u8 eyebrow_x{};
- u8 eyebrow_y{};
- u8 nose_type{};
- u8 nose_scale{};
- u8 nose_y{};
- u8 mouth_type{};
- u8 mouth_color{};
- u8 mouth_scale{};
- u8 mouth_aspect{};
- u8 mouth_y{};
- u8 beard_color{};
- u8 beard_type{};
- u8 mustache_type{};
- u8 mustache_scale{};
- u8 mustache_y{};
- u8 glasses_type{};
- u8 glasses_color{};
- u8 glasses_scale{};
- u8 glasses_y{};
- u8 mole_type{};
- u8 mole_scale{};
- u8 mole_x{};
- u8 mole_y{};
- INSERT_PADDING_BYTES(1);
+ Common::UUID uuid;
+ std::array<char16_t, 11> name;
+ u8 font_region;
+ u8 favorite_color;
+ u8 gender;
+ u8 height;
+ u8 build;
+ u8 type;
+ u8 region_move;
+ u8 faceline_type;
+ u8 faceline_color;
+ u8 faceline_wrinkle;
+ u8 faceline_make;
+ u8 hair_type;
+ u8 hair_color;
+ u8 hair_flip;
+ u8 eye_type;
+ u8 eye_color;
+ u8 eye_scale;
+ u8 eye_aspect;
+ u8 eye_rotate;
+ u8 eye_x;
+ u8 eye_y;
+ u8 eyebrow_type;
+ u8 eyebrow_color;
+ u8 eyebrow_scale;
+ u8 eyebrow_aspect;
+ u8 eyebrow_rotate;
+ u8 eyebrow_x;
+ u8 eyebrow_y;
+ u8 nose_type;
+ u8 nose_scale;
+ u8 nose_y;
+ u8 mouth_type;
+ u8 mouth_color;
+ u8 mouth_scale;
+ u8 mouth_aspect;
+ u8 mouth_y;
+ u8 beard_color;
+ u8 beard_type;
+ u8 mustache_type;
+ u8 mustache_scale;
+ u8 mustache_y;
+ u8 glasses_type;
+ u8 glasses_color;
+ u8 glasses_scale;
+ u8 glasses_y;
+ u8 mole_type;
+ u8 mole_scale;
+ u8 mole_x;
+ u8 mole_y;
+ u8 padding;
std::u16string Name() const;
};
@@ -233,7 +233,7 @@ struct RandomMiiData4 {
Age age{};
Race race{};
u32 values_count{};
- std::array<u8, 0xbc> values{};
+ std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
@@ -241,14 +241,14 @@ struct RandomMiiData3 {
u32 arg_1;
u32 arg_2;
u32 values_count;
- std::array<u8, 0xbc> values{};
+ std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
struct RandomMiiData2 {
u32 arg_1;
u32 values_count;
- std::array<u8, 0xbc> values{};
+ std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
@@ -324,7 +324,7 @@ public:
ResultCode GetIndex(const MiiInfo& info, u32& index);
private:
- const Common::UUID user_id;
+ const Common::UUID user_id{Common::INVALID_UUID};
u64 update_counter{};
};
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index b81bf6277..26be9e45b 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -18,7 +18,8 @@ constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public:
- explicit IDatabaseService() : ServiceFramework{"IDatabaseService"} {
+ explicit IDatabaseService(Core::System& system_)
+ : ServiceFramework{system_, "IDatabaseService"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDatabaseService::IsUpdated, "IsUpdated"},
@@ -47,6 +48,7 @@ public:
{23, nullptr, "Convert"},
{24, nullptr, "ConvertCoreDataToCharInfo"},
{25, nullptr, "ConvertCharInfoToCoreData"},
+ {26, nullptr, "Append"},
};
// clang-format on
@@ -251,7 +253,8 @@ private:
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
public:
- explicit MiiDBModule(const char* name) : ServiceFramework{name} {
+ explicit MiiDBModule(Core::System& system_, const char* name)
+ : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
@@ -265,7 +268,7 @@ private:
void GetDatabaseService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IDatabaseService>();
+ rb.PushIpcInterface<IDatabaseService>(system);
LOG_DEBUG(Service_Mii, "called");
}
@@ -273,7 +276,7 @@ private:
class MiiImg final : public ServiceFramework<MiiImg> {
public:
- explicit MiiImg() : ServiceFramework{"miiimg"} {
+ explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -297,11 +300,11 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<MiiDBModule>("mii:e")->InstallAsService(sm);
- std::make_shared<MiiDBModule>("mii:u")->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<MiiDBModule>(system, "mii:e")->InstallAsService(sm);
+ std::make_shared<MiiDBModule>(system, "mii:u")->InstallAsService(sm);
- std::make_shared<MiiImg>()->InstallAsService(sm);
+ std::make_shared<MiiImg>(system)->InstallAsService(sm);
}
} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h
index 7ce9be50e..9d3238e72 100644
--- a/src/core/hle/service/mii/mii.h
+++ b/src/core/hle/service/mii/mii.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::Mii {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::Mii
diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/raw_data.cpp
index 25d7bae0c..9d3c8017a 100644
--- a/src/core/hle/service/mii/raw_data.cpp
+++ b/src/core/hle/service/mii/raw_data.cpp
@@ -22,2240 +22,1642 @@
namespace Service::Mii::RawData {
-const std::array<u8, 1728> DefaultMii{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
- 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
- 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
- 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
- 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00,
- 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x6f, 0x00,
- 0x20, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::DefaultMii, 8> DefaultMii{
+ Service::Mii::DefaultMii{
+ .face_type = 0,
+ .face_color = 0,
+ .face_wrinkle = 0,
+ .face_makeup = 0,
+ .hair_type = 33,
+ .hair_color = 1,
+ .hair_flip = 0,
+ .eye_type = 2,
+ .eye_color = 0,
+ .eye_scale = 4,
+ .eye_aspect = 3,
+ .eye_rotate = 4,
+ .eye_x = 2,
+ .eye_y = 12,
+ .eyebrow_type = 6,
+ .eyebrow_color = 1,
+ .eyebrow_scale = 4,
+ .eyebrow_aspect = 3,
+ .eyebrow_rotate = 6,
+ .eyebrow_x = 2,
+ .eyebrow_y = 10,
+ .nose_type = 1,
+ .nose_scale = 4,
+ .nose_y = 9,
+ .mouth_type = 23,
+ .mouth_color = 0,
+ .mouth_scale = 4,
+ .mouth_aspect = 3,
+ .mouth_y = 13,
+ .mustache_type = 0,
+ .beard_type = 0,
+ .beard_color = 0,
+ .mustache_scale = 4,
+ .mustache_y = 10,
+ .glasses_type = 0,
+ .glasses_color = 0,
+ .glasses_scale = 4,
+ .glasses_y = 10,
+ .mole_type = 0,
+ .mole_scale = 4,
+ .mole_x = 2,
+ .mole_y = 20,
+ .height = 64,
+ .weight = 64,
+ .gender = Gender::Male,
+ .favorite_color = 0,
+ .region = 0,
+ .font_region = FontRegion::Standard,
+ .type = 0,
+ },
+ Service::Mii::DefaultMii{
+ .face_type = 0,
+ .face_color = 0,
+ .face_wrinkle = 0,
+ .face_makeup = 0,
+ .hair_type = 12,
+ .hair_color = 1,
+ .hair_flip = 0,
+ .eye_type = 4,
+ .eye_color = 0,
+ .eye_scale = 4,
+ .eye_aspect = 3,
+ .eye_rotate = 3,
+ .eye_x = 2,
+ .eye_y = 12,
+ .eyebrow_type = 0,
+ .eyebrow_color = 1,
+ .eyebrow_scale = 4,
+ .eyebrow_aspect = 3,
+ .eyebrow_rotate = 6,
+ .eyebrow_x = 2,
+ .eyebrow_y = 10,
+ .nose_type = 1,
+ .nose_scale = 4,
+ .nose_y = 9,
+ .mouth_type = 23,
+ .mouth_color = 0,
+ .mouth_scale = 4,
+ .mouth_aspect = 3,
+ .mouth_y = 13,
+ .mustache_type = 0,
+ .beard_type = 0,
+ .beard_color = 0,
+ .mustache_scale = 4,
+ .mustache_y = 10,
+ .glasses_type = 0,
+ .glasses_color = 0,
+ .glasses_scale = 4,
+ .glasses_y = 10,
+ .mole_type = 0,
+ .mole_scale = 4,
+ .mole_x = 2,
+ .mole_y = 20,
+ .height = 64,
+ .weight = 64,
+ .gender = Gender::Female,
+ .favorite_color = 0,
+ .region = 0,
+ .font_region = FontRegion::Standard,
+ .type = 0,
+ },
+ Service::Mii::DefaultMii{
+ .face_type = 0,
+ .face_color = 4,
+ .face_wrinkle = 0,
+ .face_makeup = 0,
+ .hair_type = 68,
+ .hair_color = 0,
+ .hair_flip = 0,
+ .eye_type = 2,
+ .eye_color = 0,
+ .eye_scale = 4,
+ .eye_aspect = 3,
+ .eye_rotate = 4,
+ .eye_x = 2,
+ .eye_y = 12,
+ .eyebrow_type = 6,
+ .eyebrow_color = 0,
+ .eyebrow_scale = 4,
+ .eyebrow_aspect = 3,
+ .eyebrow_rotate = 6,
+ .eyebrow_x = 2,
+ .eyebrow_y = 10,
+ .nose_type = 1,
+ .nose_scale = 4,
+ .nose_y = 9,
+ .mouth_type = 23,
+ .mouth_color = 0,
+ .mouth_scale = 4,
+ .mouth_aspect = 3,
+ .mouth_y = 13,
+ .mustache_type = 0,
+ .beard_type = 0,
+ .beard_color = 0,
+ .mustache_scale = 4,
+ .mustache_y = 10,
+ .glasses_type = 0,
+ .glasses_color = 0,
+ .glasses_scale = 4,
+ .glasses_y = 10,
+ .mole_type = 0,
+ .mole_scale = 4,
+ .mole_x = 2,
+ .mole_y = 20,
+ .height = 64,
+ .weight = 64,
+ .gender = Gender::Male,
+ .favorite_color = 4,
+ .region = 0,
+ .font_region = FontRegion::Standard,
+ .type = 0,
+ },
+ Service::Mii::DefaultMii{
+ .face_type = 0,
+ .face_color = 0,
+ .face_wrinkle = 0,
+ .face_makeup = 0,
+ .hair_type = 55,
+ .hair_color = 6,
+ .hair_flip = 0,
+ .eye_type = 2,
+ .eye_color = 4,
+ .eye_scale = 4,
+ .eye_aspect = 3,
+ .eye_rotate = 4,
+ .eye_x = 2,
+ .eye_y = 12,
+ .eyebrow_type = 6,
+ .eyebrow_color = 6,
+ .eyebrow_scale = 4,
+ .eyebrow_aspect = 3,
+ .eyebrow_rotate = 6,
+ .eyebrow_x = 2,
+ .eyebrow_y = 10,
+ .nose_type = 1,
+ .nose_scale = 4,
+ .nose_y = 9,
+ .mouth_type = 23,
+ .mouth_color = 0,
+ .mouth_scale = 4,
+ .mouth_aspect = 3,
+ .mouth_y = 13,
+ .mustache_type = 0,
+ .beard_type = 0,
+ .beard_color = 0,
+ .mustache_scale = 4,
+ .mustache_y = 10,
+ .glasses_type = 0,
+ .glasses_color = 0,
+ .glasses_scale = 4,
+ .glasses_y = 10,
+ .mole_type = 0,
+ .mole_scale = 4,
+ .mole_x = 2,
+ .mole_y = 20,
+ .height = 64,
+ .weight = 64,
+ .gender = Gender::Male,
+ .favorite_color = 5,
+ .region = 0,
+ .font_region = FontRegion::Standard,
+ .type = 0,
+ },
+ Service::Mii::DefaultMii{
+ .face_type = 0,
+ .face_color = 1,
+ .face_wrinkle = 0,
+ .face_makeup = 0,
+ .hair_type = 33,
+ .hair_color = 1,
+ .hair_flip = 0,
+ .eye_type = 2,
+ .eye_color = 0,
+ .eye_scale = 4,
+ .eye_aspect = 3,
+ .eye_rotate = 4,
+ .eye_x = 2,
+ .eye_y = 12,
+ .eyebrow_type = 6,
+ .eyebrow_color = 1,
+ .eyebrow_scale = 4,
+ .eyebrow_aspect = 3,
+ .eyebrow_rotate = 6,
+ .eyebrow_x = 2,
+ .eyebrow_y = 10,
+ .nose_type = 1,
+ .nose_scale = 4,
+ .nose_y = 9,
+ .mouth_type = 23,
+ .mouth_color = 0,
+ .mouth_scale = 4,
+ .mouth_aspect = 3,
+ .mouth_y = 13,
+ .mustache_type = 0,
+ .beard_type = 0,
+ .beard_color = 0,
+ .mustache_scale = 4,
+ .mustache_y = 10,
+ .glasses_type = 0,
+ .glasses_color = 0,
+ .glasses_scale = 4,
+ .glasses_y = 10,
+ .mole_type = 0,
+ .mole_scale = 4,
+ .mole_x = 2,
+ .mole_y = 20,
+ .height = 64,
+ .weight = 64,
+ .gender = Gender::Male,
+ .favorite_color = 0,
+ .region = 0,
+ .font_region = FontRegion::Standard,
+ .type = 0,
+ },
+ Service::Mii::DefaultMii{
+ .face_type = 0,
+ .face_color = 2,
+ .face_wrinkle = 0,
+ .face_makeup = 0,
+ .hair_type = 24,
+ .hair_color = 0,
+ .hair_flip = 0,
+ .eye_type = 4,
+ .eye_color = 0,
+ .eye_scale = 4,
+ .eye_aspect = 3,
+ .eye_rotate = 3,
+ .eye_x = 2,
+ .eye_y = 12,
+ .eyebrow_type = 0,
+ .eyebrow_color = 0,
+ .eyebrow_scale = 4,
+ .eyebrow_aspect = 3,
+ .eyebrow_rotate = 6,
+ .eyebrow_x = 2,
+ .eyebrow_y = 10,
+ .nose_type = 1,
+ .nose_scale = 4,
+ .nose_y = 9,
+ .mouth_type = 23,
+ .mouth_color = 0,
+ .mouth_scale = 4,
+ .mouth_aspect = 3,
+ .mouth_y = 13,
+ .mustache_type = 0,
+ .beard_type = 0,
+ .beard_color = 0,
+ .mustache_scale = 4,
+ .mustache_y = 10,
+ .glasses_type = 0,
+ .glasses_color = 0,
+ .glasses_scale = 4,
+ .glasses_y = 10,
+ .mole_type = 0,
+ .mole_scale = 4,
+ .mole_x = 2,
+ .mole_y = 20,
+ .height = 64,
+ .weight = 64,
+ .gender = Gender::Female,
+ .favorite_color = 2,
+ .region = 0,
+ .font_region = FontRegion::Standard,
+ .type = 0,
+ },
+ Service::Mii::DefaultMii{
+ .face_type = 0,
+ .face_color = 0,
+ .face_wrinkle = 0,
+ .face_makeup = 0,
+ .hair_type = 14,
+ .hair_color = 7,
+ .hair_flip = 0,
+ .eye_type = 4,
+ .eye_color = 5,
+ .eye_scale = 4,
+ .eye_aspect = 3,
+ .eye_rotate = 3,
+ .eye_x = 2,
+ .eye_y = 12,
+ .eyebrow_type = 0,
+ .eyebrow_color = 7,
+ .eyebrow_scale = 4,
+ .eyebrow_aspect = 3,
+ .eyebrow_rotate = 6,
+ .eyebrow_x = 2,
+ .eyebrow_y = 10,
+ .nose_type = 1,
+ .nose_scale = 4,
+ .nose_y = 9,
+ .mouth_type = 23,
+ .mouth_color = 0,
+ .mouth_scale = 4,
+ .mouth_aspect = 3,
+ .mouth_y = 13,
+ .mustache_type = 0,
+ .beard_type = 0,
+ .beard_color = 0,
+ .mustache_scale = 4,
+ .mustache_y = 10,
+ .glasses_type = 0,
+ .glasses_color = 0,
+ .glasses_scale = 4,
+ .glasses_y = 10,
+ .mole_type = 0,
+ .mole_scale = 4,
+ .mole_x = 2,
+ .mole_y = 20,
+ .height = 64,
+ .weight = 64,
+ .gender = Gender::Female,
+ .favorite_color = 6,
+ .region = 0,
+ .font_region = FontRegion::Standard,
+ .type = 0,
+ },
+ Service::Mii::DefaultMii{
+ .face_type = 0,
+ .face_color = 0,
+ .face_wrinkle = 0,
+ .face_makeup = 0,
+ .hair_type = 12,
+ .hair_color = 1,
+ .hair_flip = 0,
+ .eye_type = 4,
+ .eye_color = 0,
+ .eye_scale = 4,
+ .eye_aspect = 3,
+ .eye_rotate = 3,
+ .eye_x = 2,
+ .eye_y = 12,
+ .eyebrow_type = 0,
+ .eyebrow_color = 1,
+ .eyebrow_scale = 4,
+ .eyebrow_aspect = 3,
+ .eyebrow_rotate = 6,
+ .eyebrow_x = 2,
+ .eyebrow_y = 10,
+ .nose_type = 1,
+ .nose_scale = 4,
+ .nose_y = 9,
+ .mouth_type = 23,
+ .mouth_color = 0,
+ .mouth_scale = 4,
+ .mouth_aspect = 3,
+ .mouth_y = 13,
+ .mustache_type = 0,
+ .beard_type = 0,
+ .beard_color = 0,
+ .mustache_scale = 4,
+ .mustache_y = 10,
+ .glasses_type = 0,
+ .glasses_color = 0,
+ .glasses_scale = 4,
+ .glasses_y = 10,
+ .mole_type = 0,
+ .mole_scale = 4,
+ .mole_x = 2,
+ .mole_y = 20,
+ .height = 64,
+ .weight = 64,
+ .gender = Gender::Female,
+ .favorite_color = 7,
+ .region = 0,
+ .font_region = FontRegion::Standard,
+ .type = 0,
+ },
-const std::array<u8, 3672> RandomMiiFaceline{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+};
-const std::array<u8, 1200> RandomMiiFacelineColor{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 10,
+ .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 10,
+ .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 10,
+ .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 12,
+ .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 13,
+ .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 12,
+ .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 12,
+ .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 13,
+ .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 12,
+ .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 10,
+ .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 10,
+ .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 10,
+ .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 12,
+ .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 12,
+ .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 12,
+ .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 12,
+ .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 12,
+ .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 12,
+ .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10},
+ },
+};
-const std::array<u8, 3672> RandomMiiFacelineWrinkle{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 0,
+ .arg_2 = 0,
+ .values_count = 10,
+ .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 0,
+ .arg_2 = 1,
+ .values_count = 10,
+ .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 0,
+ .arg_2 = 2,
+ .values_count = 10,
+ .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 1,
+ .arg_2 = 0,
+ .values_count = 10,
+ .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 1,
+ .arg_2 = 1,
+ .values_count = 10,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 1,
+ .arg_2 = 2,
+ .values_count = 10,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
+ },
+};
-const std::array<u8, 3672> RandomMiiFacelineMakeup{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11},
+ },
+};
-const std::array<u8, 3672> RandomMiiHairType{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
- 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
- 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
- 0x36, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x42, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
- 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
- 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
- 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
- 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
- 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
- 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
- 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
- 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
- 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
- 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
- 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00,
- 0x4c, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
- 0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
- 0x38, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00,
- 0x44, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00,
- 0x51, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
- 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
- 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
- 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
- 0x46, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
- 0x56, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
- 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x2d, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
- 0x41, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
- 0x2f, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
- 0x37, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00,
- 0x43, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00,
- 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
- 0x4c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
- 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x2e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
- 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
- 0x4a, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00,
- 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x32, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
- 0x4d, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
- 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x2a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
- 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
- 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
- 0x3a, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
- 0x4a, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00,
- 0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
- 0x3c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
- 0x45, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
- 0x4f, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
- 0x54, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
- 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0x51, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00,
- 0x45, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
- 0x53, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ },
+};
-const std::array<u8, 1800> RandomMiiHairColor{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 30,
+ .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45,
+ 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 31,
+ .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47,
+ 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 31,
+ .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47,
+ 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 38,
+ .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50,
+ 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 39,
+ .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 39,
+ .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 18,
+ .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 19,
+ .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 19,
+ .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 39,
+ .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 42,
+ .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50,
+ 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 42,
+ .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50,
+ 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 44,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50,
+ 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 44,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58,
+ 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 44,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58,
+ 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 24,
+ .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14,
+ 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 27,
+ .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17,
+ 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 27,
+ .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17,
+ 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85},
+ },
+};
-const std::array<u8, 3672> RandomMiiEyeType{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
- 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
- 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
- 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
- 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
- 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x2f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x1d, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0x29, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
- 0x35, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0x37, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x25, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
- 0x35, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
- 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
- 0x39, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
- 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
- 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
- 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
- 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
- 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
- 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
- 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
- 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
- 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
- 0x2e, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<RandomMiiData3, 9> RandomMiiHairColor{
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 0,
+ .arg_2 = 0,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 0,
+ .arg_2 = 1,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 0,
+ .arg_2 = 2,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 1,
+ .arg_2 = 0,
+ .values_count = 20,
+ .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 1,
+ .arg_2 = 1,
+ .values_count = 20,
+ .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 1,
+ .arg_2 = 2,
+ .values_count = 20,
+ .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 2,
+ .arg_2 = 0,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 2,
+ .arg_2 = 1,
+ .values_count = 20,
+ .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3},
+ },
+ Service::Mii::RandomMiiData3{
+ .arg_1 = 2,
+ .arg_2 = 2,
+ .values_count = 20,
+ .values = {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
+ },
+};
-const std::array<u8, 588> RandomMiiEyeColor{
- 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 26,
+ .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27,
+ 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 26,
+ .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27,
+ 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 27,
+ .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27,
+ 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 35,
+ .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29,
+ 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 35,
+ .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29,
+ 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 35,
+ .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29,
+ 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 30,
+ .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21,
+ 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 30,
+ .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21,
+ 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 30,
+ .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22,
+ 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 39,
+ .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27,
+ 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 39,
+ .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27,
+ 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 40,
+ .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26,
+ 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 46,
+ .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
+ 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37,
+ 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 46,
+ .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
+ 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37,
+ 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 46,
+ .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18,
+ 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37,
+ 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 34,
+ .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23,
+ 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 34,
+ .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23,
+ 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 35,
+ .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24,
+ 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47},
+ },
+};
-const std::array<u8, 3672> RandomMiiEyebrowType{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{
+ Service::Mii::RandomMiiData2{
+ .arg_1 = 0,
+ .values_count = 10,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ },
+ Service::Mii::RandomMiiData2{
+ .arg_1 = 1,
+ .values_count = 10,
+ .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5},
+ },
+ Service::Mii::RandomMiiData2{
+ .arg_1 = 2,
+ .values_count = 10,
+ .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ },
+};
-const std::array<u8, 3672> RandomMiiNoseType{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 18,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 18,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 18,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 23,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 23,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 23,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 21,
+ .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 21,
+ .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 21,
+ .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 9,
+ .values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 9,
+ .values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 9,
+ .values = {0, 1, 3, 7, 8, 9, 10, 11, 13},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 11,
+ .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 11,
+ .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 11,
+ .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 9,
+ .values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 9,
+ .values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 9,
+ .values = {0, 3, 7, 8, 9, 10, 11, 13, 15},
+ },
+};
-const std::array<u8, 3672> RandomMiiMouthType{
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 11,
+ .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 11,
+ .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 11,
+ .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 18,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 18,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 15,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 18,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 18,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 15,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 8,
+ .values = {0, 1, 3, 4, 8, 10, 13, 14},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 8,
+ .values = {0, 1, 3, 4, 8, 10, 13, 14},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 8,
+ .values = {0, 1, 3, 4, 8, 10, 13, 14},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 12,
+ .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 11,
+ .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 10,
+ .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 12,
+ .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 11,
+ .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 10,
+ .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14},
+ },
+};
-const std::array<u8, 588> RandomMiiGlassType{
- 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00,
- 0x60, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
- 0x56, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00,
- 0x60, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x4e, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 25,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18,
+ 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 27,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17,
+ 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 28,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17,
+ 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 24,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 26,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 26,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 24,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 26,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Male,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 26,
+ .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Black,
+ .values_count = 25,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15,
+ 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Black,
+ .values_count = 26,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
+ 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Black,
+ .values_count = 26,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
+ 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::White,
+ .values_count = 25,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15,
+ 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::White,
+ .values_count = 26,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
+ 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::White,
+ .values_count = 25,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
+ 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Young,
+ .race = Race::Asian,
+ .values_count = 24,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14,
+ 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Normal,
+ .race = Race::Asian,
+ .values_count = 25,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
+ 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
+ },
+ Service::Mii::RandomMiiData4{
+ .gender = Gender::Female,
+ .age = Age::Old,
+ .race = Race::Asian,
+ .values_count = 25,
+ .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14,
+ 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33},
+ },
+};
+
+const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{
+ Service::Mii::RandomMiiData2{
+ .arg_1 = 0,
+ .values_count = 9,
+ .values = {90, 94, 96, 100, 0, 0, 0, 0, 0},
+ },
+ Service::Mii::RandomMiiData2{
+ .arg_1 = 1,
+ .values_count = 9,
+ .values = {83, 86, 90, 93, 94, 96, 98, 100, 0},
+ },
+ Service::Mii::RandomMiiData2{
+ .arg_1 = 2,
+ .values_count = 9,
+ .values = {78, 83, 0, 93, 0, 0, 98, 100, 0},
+ },
+};
} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h
index a02a5c0fd..0e35d69d2 100644
--- a/src/core/hle/service/mii/raw_data.h
+++ b/src/core/hle/service/mii/raw_data.h
@@ -7,21 +7,22 @@
#include <array>
#include "common/common_types.h"
+#include "core/hle/service/mii/manager.h"
namespace Service::Mii::RawData {
-extern const std::array<u8, 1728> DefaultMii;
-extern const std::array<u8, 3672> RandomMiiFaceline;
-extern const std::array<u8, 1200> RandomMiiFacelineColor;
-extern const std::array<u8, 3672> RandomMiiFacelineWrinkle;
-extern const std::array<u8, 3672> RandomMiiFacelineMakeup;
-extern const std::array<u8, 3672> RandomMiiHairType;
-extern const std::array<u8, 1800> RandomMiiHairColor;
-extern const std::array<u8, 3672> RandomMiiEyeType;
-extern const std::array<u8, 588> RandomMiiEyeColor;
-extern const std::array<u8, 3672> RandomMiiEyebrowType;
-extern const std::array<u8, 3672> RandomMiiNoseType;
-extern const std::array<u8, 3672> RandomMiiMouthType;
-extern const std::array<u8, 588> RandomMiiGlassType;
+extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii;
+extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline;
+extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor;
+extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle;
+extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup;
+extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType;
+extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor;
+extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType;
+extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor;
+extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType;
+extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType;
+extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType;
+extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType;
} // namespace Service::Mii::RawData
diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp
index 25c24e537..b0cb07d24 100644
--- a/src/core/hle/service/mm/mm_u.cpp
+++ b/src/core/hle/service/mm/mm_u.cpp
@@ -6,12 +6,13 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/service/mm/mm_u.h"
+#include "core/hle/service/sm/sm.h"
namespace Service::MM {
class MM_U final : public ServiceFramework<MM_U> {
public:
- explicit MM_U() : ServiceFramework{"mm:u"} {
+ explicit MM_U(Core::System& system_) : ServiceFramework{system_, "mm:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &MM_U::InitializeOld, "InitializeOld"},
@@ -104,8 +105,8 @@ private:
u32 id{1};
};
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<MM_U>()->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<MM_U>(system)->InstallAsService(service_manager);
}
} // namespace Service::MM
diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h
index 5439fa653..49b6a3355 100644
--- a/src/core/hle/service/mm/mm_u.h
+++ b/src/core/hle/service/mm/mm_u.h
@@ -4,11 +4,17 @@
#pragma once
-#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
+namespace Service::SM {
+class ServiceManager;
+}
namespace Service::MM {
/// Registers all MM services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::MM
diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp
index e38dea1f4..2dcda16f6 100644
--- a/src/core/hle/service/ncm/ncm.cpp
+++ b/src/core/hle/service/ncm/ncm.cpp
@@ -14,8 +14,8 @@ namespace Service::NCM {
class ILocationResolver final : public ServiceFramework<ILocationResolver> {
public:
- explicit ILocationResolver(FileSys::StorageId id)
- : ServiceFramework{"ILocationResolver"}, storage(id) {
+ explicit ILocationResolver(Core::System& system_, FileSys::StorageId id)
+ : ServiceFramework{system_, "ILocationResolver"}, storage{id} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "ResolveProgramPath"},
@@ -45,12 +45,13 @@ public:
}
private:
- FileSys::StorageId storage;
+ [[maybe_unused]] FileSys::StorageId storage;
};
class IRegisteredLocationResolver final : public ServiceFramework<IRegisteredLocationResolver> {
public:
- explicit IRegisteredLocationResolver() : ServiceFramework{"IRegisteredLocationResolver"} {
+ explicit IRegisteredLocationResolver(Core::System& system_)
+ : ServiceFramework{system_, "IRegisteredLocationResolver"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "ResolveProgramPath"},
@@ -72,7 +73,8 @@ public:
class IAddOnContentLocationResolver final : public ServiceFramework<IAddOnContentLocationResolver> {
public:
- explicit IAddOnContentLocationResolver() : ServiceFramework{"IAddOnContentLocationResolver"} {
+ explicit IAddOnContentLocationResolver(Core::System& system_)
+ : ServiceFramework{system_, "IAddOnContentLocationResolver"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "ResolveAddOnContentPath"},
@@ -89,7 +91,7 @@ public:
class LR final : public ServiceFramework<LR> {
public:
- explicit LR() : ServiceFramework{"lr"} {
+ explicit LR(Core::System& system_) : ServiceFramework{system_, "lr"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "OpenLocationResolver"},
@@ -105,7 +107,7 @@ public:
class NCM final : public ServiceFramework<NCM> {
public:
- explicit NCM() : ServiceFramework{"ncm"} {
+ explicit NCM(Core::System& system_) : ServiceFramework{system_, "ncm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateContentStorage"},
@@ -130,9 +132,9 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<LR>()->InstallAsService(sm);
- std::make_shared<NCM>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<LR>(system)->InstallAsService(sm);
+ std::make_shared<NCM>(system)->InstallAsService(sm);
}
} // namespace Service::NCM
diff --git a/src/core/hle/service/ncm/ncm.h b/src/core/hle/service/ncm/ncm.h
index 7bc8518a6..ee01eddc0 100644
--- a/src/core/hle/service/ncm/ncm.h
+++ b/src/core/hle/service/ncm/ncm.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::NCM {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::NCM
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 780ea30fe..6ab35de47 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -16,7 +16,7 @@ namespace Service::NFC {
class IAm final : public ServiceFramework<IAm> {
public:
- explicit IAm() : ServiceFramework{"NFC::IAm"} {
+ explicit IAm(Core::System& system_) : ServiceFramework{system_, "NFC::IAm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -31,7 +31,7 @@ public:
class NFC_AM final : public ServiceFramework<NFC_AM> {
public:
- explicit NFC_AM() : ServiceFramework{"nfc:am"} {
+ explicit NFC_AM(Core::System& system_) : ServiceFramework{system_, "nfc:am"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_AM::CreateAmInterface, "CreateAmInterface"},
@@ -47,13 +47,13 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IAm>();
+ rb.PushIpcInterface<IAm>(system);
}
};
class MFIUser final : public ServiceFramework<MFIUser> {
public:
- explicit MFIUser() : ServiceFramework{"NFC::MFIUser"} {
+ explicit MFIUser(Core::System& system_) : ServiceFramework{system_, "NFC::MFIUser"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -79,7 +79,7 @@ public:
class NFC_MF_U final : public ServiceFramework<NFC_MF_U> {
public:
- explicit NFC_MF_U() : ServiceFramework{"nfc:mf:u"} {
+ explicit NFC_MF_U(Core::System& system_) : ServiceFramework{system_, "nfc:mf:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_MF_U::CreateUserInterface, "CreateUserInterface"},
@@ -95,13 +95,13 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<MFIUser>();
+ rb.PushIpcInterface<MFIUser>(system);
}
};
class IUser final : public ServiceFramework<IUser> {
public:
- explicit IUser() : ServiceFramework{"NFC::IUser"} {
+ explicit IUser(Core::System& system_) : ServiceFramework{system_, "NFC::IUser"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IUser::InitializeOld, "InitializeOld"},
@@ -171,7 +171,7 @@ private:
class NFC_U final : public ServiceFramework<NFC_U> {
public:
- explicit NFC_U() : ServiceFramework{"nfc:user"} {
+ explicit NFC_U(Core::System& system_) : ServiceFramework{system_, "nfc:user"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_U::CreateUserInterface, "CreateUserInterface"},
@@ -187,13 +187,13 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IUser>();
+ rb.PushIpcInterface<IUser>(system);
}
};
class ISystem final : public ServiceFramework<ISystem> {
public:
- explicit ISystem() : ServiceFramework{"ISystem"} {
+ explicit ISystem(Core::System& system_) : ServiceFramework{system_, "ISystem"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -230,7 +230,7 @@ public:
class NFC_SYS final : public ServiceFramework<NFC_SYS> {
public:
- explicit NFC_SYS() : ServiceFramework{"nfc:sys"} {
+ explicit NFC_SYS(Core::System& system_) : ServiceFramework{system_, "nfc:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_SYS::CreateSystemInterface, "CreateSystemInterface"},
@@ -246,15 +246,15 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystem>();
+ rb.PushIpcInterface<ISystem>(system);
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<NFC_AM>()->InstallAsService(sm);
- std::make_shared<NFC_MF_U>()->InstallAsService(sm);
- std::make_shared<NFC_U>()->InstallAsService(sm);
- std::make_shared<NFC_SYS>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<NFC_AM>(system)->InstallAsService(sm);
+ std::make_shared<NFC_MF_U>(system)->InstallAsService(sm);
+ std::make_shared<NFC_U>(system)->InstallAsService(sm);
+ std::make_shared<NFC_SYS>(system)->InstallAsService(sm);
}
} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h
index 4d2d815f9..5a94b076d 100644
--- a/src/core/hle/service/nfc/nfc.h
+++ b/src/core/hle/service/nfc/nfc.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::NFC {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::NFC
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 5e2d769a4..2d1d4d67f 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -2,15 +2,17 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
#include <atomic>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/lock.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
@@ -20,18 +22,20 @@ namespace ErrCodes {
constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152);
} // namespace ErrCodes
-Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
- : ServiceFramework(name), module(std::move(module)), system(system) {
+Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
+ const char* name)
+ : ServiceFramework{system_, name}, module{std::move(module_)} {
auto& kernel = system.Kernel();
- nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, "IUser:NFCTagDetected");
+ nfc_tag_load = Kernel::KEvent::Create(kernel, "IUser:NFCTagDetected");
+ nfc_tag_load->Initialize();
}
Module::Interface::~Interface() = default;
class IUser final : public ServiceFramework<IUser> {
public:
- IUser(Module::Interface& nfp_interface, Core::System& system)
- : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) {
+ explicit IUser(Module::Interface& nfp_interface_, Core::System& system_)
+ : ServiceFramework{system_, "NFP::IUser"}, nfp_interface{nfp_interface_} {
static const FunctionInfo functions[] = {
{0, &IUser::Initialize, "Initialize"},
{1, &IUser::Finalize, "Finalize"},
@@ -62,9 +66,10 @@ public:
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- deactivate_event = Kernel::WritableEvent::CreateEventPair(kernel, "IUser:DeactivateEvent");
- availability_change_event =
- Kernel::WritableEvent::CreateEventPair(kernel, "IUser:AvailabilityChangeEvent");
+ deactivate_event = Kernel::KEvent::Create(kernel, "IUser:DeactivateEvent");
+ deactivate_event->Initialize();
+ availability_change_event = Kernel::KEvent::Create(kernel, "IUser:AvailabilityChangeEvent");
+ availability_change_event->Initialize();
}
private:
@@ -72,10 +77,10 @@ private:
std::array<u8, 10> uuid;
u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it
// mean something else
- INSERT_PADDING_BYTES(0x15);
+ std::array<u8, 0x15> padding_1;
u32_le protocol;
u32_le tag_type;
- INSERT_PADDING_BYTES(0x2c);
+ std::array<u8, 0x2c> padding_2;
};
static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size");
@@ -162,7 +167,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(deactivate_event.readable);
+ rb.PushCopyObjects(deactivate_event->GetReadableEvent());
}
void StopDetection(Kernel::HLERequestContext& ctx) {
@@ -171,7 +176,7 @@ private:
switch (device_state) {
case DeviceState::TagFound:
case DeviceState::TagNearby:
- deactivate_event.writable->Signal();
+ deactivate_event->GetWritableEvent()->Signal();
device_state = DeviceState::Initialized;
break;
case DeviceState::SearchingForTag:
@@ -188,12 +193,6 @@ private:
void GetDeviceState(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
- auto nfc_event = nfp_interface.GetNFCEvent();
- if (!nfc_event->ShouldWait(&ctx.GetThread()) && !has_attached_handle) {
- device_state = DeviceState::TagFound;
- nfc_event->Clear();
- }
-
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(device_state));
@@ -213,13 +212,15 @@ private:
LOG_DEBUG(Service_NFP, "called");
IPC::ResponseBuilder rb{ctx, 2};
- auto amiibo = nfp_interface.GetAmiiboBuffer();
- TagInfo tag_info{};
- tag_info.uuid = amiibo.uuid;
- tag_info.uuid_length = static_cast<u8>(tag_info.uuid.size());
-
- tag_info.protocol = 1; // TODO(ogniK): Figure out actual values
- tag_info.tag_type = 2;
+ const auto& amiibo = nfp_interface.GetAmiiboBuffer();
+ const TagInfo tag_info{
+ .uuid = amiibo.uuid,
+ .uuid_length = static_cast<u8>(amiibo.uuid.size()),
+ .padding_1 = {},
+ .protocol = 1, // TODO(ogniK): Figure out actual values
+ .tag_type = 2,
+ .padding_2 = {},
+ };
ctx.WriteBuffer(tag_info);
rb.Push(RESULT_SUCCESS);
}
@@ -236,7 +237,7 @@ private:
LOG_DEBUG(Service_NFP, "called");
IPC::ResponseBuilder rb{ctx, 2};
- auto amiibo = nfp_interface.GetAmiiboBuffer();
+ const auto& amiibo = nfp_interface.GetAmiiboBuffer();
ctx.WriteBuffer(amiibo.model_info);
rb.Push(RESULT_SUCCESS);
}
@@ -264,7 +265,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(availability_change_event.readable);
+ rb.PushCopyObjects(availability_change_event->GetReadableEvent());
}
void GetRegisterInfo(Kernel::HLERequestContext& ctx) {
@@ -318,8 +319,8 @@ private:
const u32 npad_id{0}; // Player 1 controller
State state{State::NonInitialized};
DeviceState device_state{DeviceState::Initialized};
- Kernel::EventPair deactivate_event;
- Kernel::EventPair availability_change_event;
+ std::shared_ptr<Kernel::KEvent> deactivate_event;
+ std::shared_ptr<Kernel::KEvent> availability_change_event;
const Module::Interface& nfp_interface;
};
@@ -338,12 +339,12 @@ bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
}
std::memcpy(&amiibo, buffer.data(), sizeof(amiibo));
- nfc_tag_load.writable->Signal();
+ nfc_tag_load->GetWritableEvent()->Signal();
return true;
}
-const std::shared_ptr<Kernel::ReadableEvent>& Module::Interface::GetNFCEvent() const {
- return nfc_tag_load.readable;
+const std::shared_ptr<Kernel::KReadableEvent>& Module::Interface::GetNFCEvent() const {
+ return nfc_tag_load->GetReadableEvent();
}
const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const {
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 200013795..c46551760 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -6,17 +6,21 @@
#include <array>
#include <vector>
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
+
#include "core/hle/service/service.h"
+namespace Kernel {
+class KEvent;
+}
+
namespace Service::NFP {
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name);
+ explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
+ const char* name);
~Interface() override;
struct ModelInfo {
@@ -34,16 +38,15 @@ public:
void CreateUserInterface(Kernel::HLERequestContext& ctx);
bool LoadAmiibo(const std::vector<u8>& buffer);
- const std::shared_ptr<Kernel::ReadableEvent>& GetNFCEvent() const;
+ const std::shared_ptr<Kernel::KReadableEvent>& GetNFCEvent() const;
const AmiiboFile& GetAmiiboBuffer() const;
private:
- Kernel::EventPair nfc_tag_load{};
+ std::shared_ptr<Kernel::KEvent> nfc_tag_load;
AmiiboFile amiibo{};
protected:
std::shared_ptr<Module> module;
- Core::System& system;
};
};
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 298184f17..10b0ef944 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -6,8 +6,8 @@
namespace Service::NFP {
-NFP_User::NFP_User(std::shared_ptr<Module> module, Core::System& system)
- : Module::Interface(std::move(module), system, "nfp:user") {
+NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_)
+ : Interface(std::move(module_), system_, "nfp:user") {
static const FunctionInfo functions[] = {
{0, &NFP_User::CreateUserInterface, "CreateUserInterface"},
};
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
index 1686ebf20..7f3c124f6 100644
--- a/src/core/hle/service/nfp/nfp_user.h
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -10,7 +10,7 @@ namespace Service::NFP {
class NFP_User final : public Module::Interface {
public:
- explicit NFP_User(std::shared_ptr<Module> module, Core::System& system);
+ explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_);
~NFP_User() override;
};
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 01ddcdbd6..afb3342d6 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -4,11 +4,12 @@
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/service.h"
+#include "core/network/network.h"
#include "core/settings.h"
namespace Service::NIFM {
@@ -20,9 +21,96 @@ enum class RequestState : u32 {
Connected = 3,
};
+struct IpAddressSetting {
+ bool is_automatic{};
+ Network::IPv4Address current_address{};
+ Network::IPv4Address subnet_mask{};
+ Network::IPv4Address gateway{};
+};
+static_assert(sizeof(IpAddressSetting) == 0xD, "IpAddressSetting has incorrect size.");
+
+struct DnsSetting {
+ bool is_automatic{};
+ Network::IPv4Address primary_dns{};
+ Network::IPv4Address secondary_dns{};
+};
+static_assert(sizeof(DnsSetting) == 0x9, "DnsSetting has incorrect size.");
+
+struct ProxySetting {
+ bool enabled{};
+ INSERT_PADDING_BYTES(1);
+ u16 port{};
+ std::array<char, 0x64> proxy_server{};
+ bool automatic_auth_enabled{};
+ std::array<char, 0x20> user{};
+ std::array<char, 0x20> password{};
+ INSERT_PADDING_BYTES(1);
+};
+static_assert(sizeof(ProxySetting) == 0xAA, "ProxySetting has incorrect size.");
+
+struct IpSettingData {
+ IpAddressSetting ip_address_setting{};
+ DnsSetting dns_setting{};
+ ProxySetting proxy_setting{};
+ u16 mtu{};
+};
+static_assert(sizeof(IpSettingData) == 0xC2, "IpSettingData has incorrect size.");
+
+struct SfWirelessSettingData {
+ u8 ssid_length{};
+ std::array<char, 0x20> ssid{};
+ u8 unknown_1{};
+ u8 unknown_2{};
+ u8 unknown_3{};
+ std::array<char, 0x41> passphrase{};
+};
+static_assert(sizeof(SfWirelessSettingData) == 0x65, "SfWirelessSettingData has incorrect size.");
+
+struct NifmWirelessSettingData {
+ u8 ssid_length{};
+ std::array<char, 0x21> ssid{};
+ u8 unknown_1{};
+ INSERT_PADDING_BYTES(1);
+ u32 unknown_2{};
+ u32 unknown_3{};
+ std::array<char, 0x41> passphrase{};
+ INSERT_PADDING_BYTES(3);
+};
+static_assert(sizeof(NifmWirelessSettingData) == 0x70,
+ "NifmWirelessSettingData has incorrect size.");
+
+#pragma pack(push, 1)
+struct SfNetworkProfileData {
+ IpSettingData ip_setting_data{};
+ u128 uuid{};
+ std::array<char, 0x40> network_name{};
+ u8 unknown_1{};
+ u8 unknown_2{};
+ u8 unknown_3{};
+ u8 unknown_4{};
+ SfWirelessSettingData wireless_setting_data{};
+ INSERT_PADDING_BYTES(1);
+};
+static_assert(sizeof(SfNetworkProfileData) == 0x17C, "SfNetworkProfileData has incorrect size.");
+
+struct NifmNetworkProfileData {
+ u128 uuid{};
+ std::array<char, 0x40> network_name{};
+ u32 unknown_1{};
+ u32 unknown_2{};
+ u8 unknown_3{};
+ u8 unknown_4{};
+ INSERT_PADDING_BYTES(2);
+ NifmWirelessSettingData wireless_setting_data{};
+ IpSettingData ip_setting_data{};
+};
+static_assert(sizeof(NifmNetworkProfileData) == 0x18E,
+ "NifmNetworkProfileData has incorrect size.");
+#pragma pack(pop)
+
class IScanRequest final : public ServiceFramework<IScanRequest> {
public:
- explicit IScanRequest() : ServiceFramework("IScanRequest") {
+ explicit IScanRequest(Core::System& system_) : ServiceFramework{system_, "IScanRequest"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Submit"},
@@ -39,7 +127,7 @@ public:
class IRequest final : public ServiceFramework<IRequest> {
public:
- explicit IRequest(Core::System& system) : ServiceFramework("IRequest") {
+ explicit IRequest(Core::System& system_) : ServiceFramework{system_, "IRequest"} {
static const FunctionInfo functions[] = {
{0, &IRequest::GetRequestState, "GetRequestState"},
{1, &IRequest::GetResult, "GetResult"},
@@ -61,7 +149,7 @@ public:
{18, nullptr, "SetRequirementByRevision"},
{19, nullptr, "GetRequirement"},
{20, nullptr, "GetRevision"},
- {21, nullptr, "GetAppletInfo"},
+ {21, &IRequest::GetAppletInfo, "GetAppletInfo"},
{22, nullptr, "GetAdditionalInfo"},
{23, nullptr, "SetKeptInSleep"},
{24, nullptr, "RegisterSocketDescriptor"},
@@ -70,8 +158,11 @@ public:
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- event1 = Kernel::WritableEvent::CreateEventPair(kernel, "IRequest:Event1");
- event2 = Kernel::WritableEvent::CreateEventPair(kernel, "IRequest:Event2");
+
+ event1 = Kernel::KEvent::Create(kernel, "IRequest:Event1");
+ event1->Initialize();
+ event2 = Kernel::KEvent::Create(kernel, "IRequest:Event2");
+ event2->Initialize();
}
private:
@@ -107,7 +198,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 2};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(event1.readable, event2.readable);
+ rb.PushCopyObjects(event1->GetReadableEvent(), event2->GetReadableEvent());
}
void Cancel(Kernel::HLERequestContext& ctx) {
@@ -124,12 +215,26 @@ private:
rb.Push(RESULT_SUCCESS);
}
- Kernel::EventPair event1, event2;
+ void GetAppletInfo(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ std::vector<u8> out_buffer(ctx.GetWriteBufferSize());
+
+ ctx.WriteBuffer(out_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(0);
+ rb.Push<u32>(0);
+ rb.Push<u32>(0);
+ }
+
+ std::shared_ptr<Kernel::KEvent> event1, event2;
};
class INetworkProfile final : public ServiceFramework<INetworkProfile> {
public:
- explicit INetworkProfile() : ServiceFramework("INetworkProfile") {
+ explicit INetworkProfile(Core::System& system_) : ServiceFramework{system_, "INetworkProfile"} {
static const FunctionInfo functions[] = {
{0, nullptr, "Update"},
{1, nullptr, "PersistOld"},
@@ -141,7 +246,7 @@ public:
class IGeneralService final : public ServiceFramework<IGeneralService> {
public:
- IGeneralService(Core::System& system);
+ explicit IGeneralService(Core::System& system_);
private:
void GetClientId(Kernel::HLERequestContext& ctx) {
@@ -158,7 +263,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IScanRequest>();
+ rb.PushIpcInterface<IScanRequest>(system);
}
void CreateRequest(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NIFM, "called");
@@ -168,12 +273,62 @@ private:
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IRequest>(system);
}
+ void GetCurrentNetworkProfile(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ const SfNetworkProfileData network_profile_data{
+ .ip_setting_data{
+ .ip_address_setting{
+ .is_automatic{true},
+ .current_address{192, 168, 1, 100},
+ .subnet_mask{255, 255, 255, 0},
+ .gateway{192, 168, 1, 1},
+ },
+ .dns_setting{
+ .is_automatic{true},
+ .primary_dns{1, 1, 1, 1},
+ .secondary_dns{1, 0, 0, 1},
+ },
+ .proxy_setting{
+ .enabled{false},
+ .port{},
+ .proxy_server{},
+ .automatic_auth_enabled{},
+ .user{},
+ .password{},
+ },
+ .mtu{1500},
+ },
+ .uuid{0xdeadbeef, 0xdeadbeef},
+ .network_name{"yuzu Network"},
+ .wireless_setting_data{
+ .ssid_length{12},
+ .ssid{"yuzu Network"},
+ .passphrase{"yuzupassword"},
+ },
+ };
+
+ ctx.WriteBuffer(network_profile_data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+ void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ const auto [ipv4, error] = Network::GetHostIPv4Address();
+ UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(ipv4);
+ }
void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NIFM, "called");
@@ -186,9 +341,37 @@ private:
IPC::ResponseBuilder rb{ctx, 6, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<INetworkProfile>();
+ rb.PushIpcInterface<INetworkProfile>(system);
rb.PushRaw<u128>(uuid);
}
+ void GetCurrentIpConfigInfo(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NIFM, "(STUBBED) called");
+
+ struct IpConfigInfo {
+ IpAddressSetting ip_address_setting;
+ DnsSetting dns_setting;
+ };
+ static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
+ "IpConfigInfo has incorrect size.");
+
+ const IpConfigInfo ip_config_info{
+ .ip_address_setting{
+ .is_automatic{true},
+ .current_address{192, 168, 1, 100},
+ .subnet_mask{255, 255, 255, 0},
+ .gateway{192, 168, 1, 1},
+ },
+ .dns_setting{
+ .is_automatic{true},
+ .primary_dns{1, 1, 1, 1},
+ .secondary_dns{1, 0, 0, 1},
+ },
+ };
+
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(IpConfigInfo) / sizeof(u32)};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<IpConfigInfo>(ip_config_info);
+ }
void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NIFM, "(STUBBED) called");
@@ -218,27 +401,26 @@ private:
rb.Push<u8>(1);
}
}
- Core::System& system;
};
-IGeneralService::IGeneralService(Core::System& system)
- : ServiceFramework("IGeneralService"), system(system) {
+IGeneralService::IGeneralService(Core::System& system_)
+ : ServiceFramework{system_, "IGeneralService"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &IGeneralService::GetClientId, "GetClientId"},
{2, &IGeneralService::CreateScanRequest, "CreateScanRequest"},
{4, &IGeneralService::CreateRequest, "CreateRequest"},
- {5, nullptr, "GetCurrentNetworkProfile"},
+ {5, &IGeneralService::GetCurrentNetworkProfile, "GetCurrentNetworkProfile"},
{6, nullptr, "EnumerateNetworkInterfaces"},
{7, nullptr, "EnumerateNetworkProfiles"},
{8, nullptr, "GetNetworkProfile"},
{9, nullptr, "SetNetworkProfile"},
{10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
{11, nullptr, "GetScanDataOld"},
- {12, nullptr, "GetCurrentIpAddress"},
+ {12, &IGeneralService::GetCurrentIpAddress, "GetCurrentIpAddress"},
{13, nullptr, "GetCurrentAccessPointOld"},
{14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
- {15, nullptr, "GetCurrentIpConfigInfo"},
+ {15, &IGeneralService::GetCurrentIpConfigInfo, "GetCurrentIpConfigInfo"},
{16, nullptr, "SetWirelessCommunicationEnabled"},
{17, &IGeneralService::IsWirelessCommunicationEnabled, "IsWirelessCommunicationEnabled"},
{18, nullptr, "GetInternetConnectionStatus"},
@@ -275,8 +457,8 @@ IGeneralService::IGeneralService(Core::System& system)
class NetworkInterface final : public ServiceFramework<NetworkInterface> {
public:
- explicit NetworkInterface(const char* name, Core::System& system)
- : ServiceFramework{name}, system(system) {
+ explicit NetworkInterface(const char* name, Core::System& system_)
+ : ServiceFramework{system_, name} {
static const FunctionInfo functions[] = {
{4, &NetworkInterface::CreateGeneralServiceOld, "CreateGeneralServiceOld"},
{5, &NetworkInterface::CreateGeneralService, "CreateGeneralService"},
@@ -284,6 +466,7 @@ public:
RegisterHandlers(functions);
}
+private:
void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NIFM, "called");
@@ -299,9 +482,6 @@ public:
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IGeneralService>(system);
}
-
-private:
- Core::System& system;
};
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index 6857e18f9..c3dd4f386 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -4,14 +4,14 @@
#pragma once
-namespace Service::SM {
-class ServiceManager;
-}
-
namespace Core {
class System;
}
+namespace Service::SM {
+class ServiceManager;
+}
+
namespace Service::NIFM {
/// Registers all NIFM services with the specified service manager.
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index 11aa74828..f3be0b878 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -6,9 +6,10 @@
#include <ctime>
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nim/nim.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
@@ -17,7 +18,8 @@ namespace Service::NIM {
class IShopServiceAsync final : public ServiceFramework<IShopServiceAsync> {
public:
- IShopServiceAsync() : ServiceFramework("IShopServiceAsync") {
+ explicit IShopServiceAsync(Core::System& system_)
+ : ServiceFramework{system_, "IShopServiceAsync"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Cancel"},
@@ -35,7 +37,8 @@ public:
class IShopServiceAccessor final : public ServiceFramework<IShopServiceAccessor> {
public:
- IShopServiceAccessor() : ServiceFramework("IShopServiceAccessor") {
+ explicit IShopServiceAccessor(Core::System& system_)
+ : ServiceFramework{system_, "IShopServiceAccessor"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IShopServiceAccessor::CreateAsyncInterface, "CreateAsyncInterface"},
@@ -50,13 +53,14 @@ private:
LOG_WARNING(Service_NIM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IShopServiceAsync>();
+ rb.PushIpcInterface<IShopServiceAsync>(system);
}
};
class IShopServiceAccessServer final : public ServiceFramework<IShopServiceAccessServer> {
public:
- IShopServiceAccessServer() : ServiceFramework("IShopServiceAccessServer") {
+ explicit IShopServiceAccessServer(Core::System& system_)
+ : ServiceFramework{system_, "IShopServiceAccessServer"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IShopServiceAccessServer::CreateAccessorInterface, "CreateAccessorInterface"},
@@ -71,13 +75,13 @@ private:
LOG_WARNING(Service_NIM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IShopServiceAccessor>();
+ rb.PushIpcInterface<IShopServiceAccessor>(system);
}
};
class NIM final : public ServiceFramework<NIM> {
public:
- explicit NIM() : ServiceFramework{"nim"} {
+ explicit NIM(Core::System& system_) : ServiceFramework{system_, "nim"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateSystemUpdateTask"},
@@ -207,14 +211,14 @@ public:
class NIM_ECA final : public ServiceFramework<NIM_ECA> {
public:
- explicit NIM_ECA() : ServiceFramework{"nim:eca"} {
+ explicit NIM_ECA(Core::System& system_) : ServiceFramework{system_, "nim:eca"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NIM_ECA::CreateServerInterface, "CreateServerInterface"},
{1, nullptr, "RefreshDebugAvailability"},
{2, nullptr, "ClearDebugResponse"},
{3, nullptr, "RegisterDebugResponse"},
- {4, nullptr, "IsLargeResourceAvailable"},
+ {4, &NIM_ECA::IsLargeResourceAvailable, "IsLargeResourceAvailable"},
};
// clang-format on
@@ -226,13 +230,25 @@ private:
LOG_WARNING(Service_NIM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IShopServiceAccessServer>();
+ rb.PushIpcInterface<IShopServiceAccessServer>(system);
+ }
+
+ void IsLargeResourceAvailable(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const auto unknown{rp.Pop<u64>()};
+
+ LOG_INFO(Service_NIM, "(STUBBED) called, unknown={}", unknown);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(false);
}
};
class NIM_SHP final : public ServiceFramework<NIM_SHP> {
public:
- explicit NIM_SHP() : ServiceFramework{"nim:shp"} {
+ explicit NIM_SHP(Core::System& system_) : ServiceFramework{system_, "nim:shp"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestDeviceAuthenticationToken"},
@@ -272,8 +288,8 @@ public:
class IEnsureNetworkClockAvailabilityService final
: public ServiceFramework<IEnsureNetworkClockAvailabilityService> {
public:
- explicit IEnsureNetworkClockAvailabilityService(Core::System& system)
- : ServiceFramework("IEnsureNetworkClockAvailabilityService") {
+ explicit IEnsureNetworkClockAvailabilityService(Core::System& system_)
+ : ServiceFramework{system_, "IEnsureNetworkClockAvailabilityService"} {
static const FunctionInfo functions[] = {
{0, &IEnsureNetworkClockAvailabilityService::StartTask, "StartTask"},
{1, &IEnsureNetworkClockAvailabilityService::GetFinishNotificationEvent,
@@ -286,17 +302,18 @@ public:
RegisterHandlers(functions);
auto& kernel = system.Kernel();
- finished_event = Kernel::WritableEvent::CreateEventPair(
- kernel, "IEnsureNetworkClockAvailabilityService:FinishEvent");
+ finished_event =
+ Kernel::KEvent::Create(kernel, "IEnsureNetworkClockAvailabilityService:FinishEvent");
+ finished_event->Initialize();
}
private:
- Kernel::EventPair finished_event;
+ std::shared_ptr<Kernel::KEvent> finished_event;
void StartTask(Kernel::HLERequestContext& ctx) {
// No need to connect to the internet, just finish the task straight away.
LOG_DEBUG(Service_NIM, "called");
- finished_event.writable->Signal();
+ finished_event->GetWritableEvent()->Signal();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -306,7 +323,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(finished_event.readable);
+ rb.PushCopyObjects(finished_event->GetReadableEvent());
}
void GetResult(Kernel::HLERequestContext& ctx) {
@@ -318,7 +335,7 @@ private:
void Cancel(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NIM, "called");
- finished_event.writable->Clear();
+ finished_event->GetWritableEvent()->Clear();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
@@ -345,7 +362,7 @@ private:
class NTC final : public ServiceFramework<NTC> {
public:
- explicit NTC(Core::System& system) : ServiceFramework{"ntc"}, system(system) {
+ explicit NTC(Core::System& system_) : ServiceFramework{system_, "ntc"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NTC::OpenEnsureNetworkClockAvailabilityService, "OpenEnsureNetworkClockAvailabilityService"},
@@ -380,13 +397,12 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
- Core::System& system;
};
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
- std::make_shared<NIM>()->InstallAsService(sm);
- std::make_shared<NIM_ECA>()->InstallAsService(sm);
- std::make_shared<NIM_SHP>()->InstallAsService(sm);
+ std::make_shared<NIM>(system)->InstallAsService(sm);
+ std::make_shared<NIM_ECA>(system)->InstallAsService(sm);
+ std::make_shared<NIM_SHP>(system)->InstallAsService(sm);
std::make_shared<NTC>(system)->InstallAsService(sm);
}
diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h
index dbe25dc01..571153fe6 100644
--- a/src/core/hle/service/nim/nim.h
+++ b/src/core/hle/service/nim/nim.h
@@ -4,14 +4,14 @@
#pragma once
-namespace Service::SM {
-class ServiceManager;
-}
-
namespace Core {
class System;
}
+namespace Service::SM {
+class ServiceManager;
+}
+
namespace Service::NIM {
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp
index 8fa16fb08..f7a58f659 100644
--- a/src/core/hle/service/npns/npns.cpp
+++ b/src/core/hle/service/npns/npns.cpp
@@ -12,7 +12,7 @@ namespace Service::NPNS {
class NPNS_S final : public ServiceFramework<NPNS_S> {
public:
- explicit NPNS_S() : ServiceFramework{"npns:s"} {
+ explicit NPNS_S(Core::System& system_) : ServiceFramework{system_, "npns:s"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "ListenAll"},
@@ -62,7 +62,7 @@ public:
class NPNS_U final : public ServiceFramework<NPNS_U> {
public:
- explicit NPNS_U() : ServiceFramework{"npns:u"} {
+ explicit NPNS_U(Core::System& system_) : ServiceFramework{system_, "npns:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "ListenAll"},
@@ -91,9 +91,9 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<NPNS_S>()->InstallAsService(sm);
- std::make_shared<NPNS_U>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<NPNS_S>(system)->InstallAsService(sm);
+ std::make_shared<NPNS_U>(system)->InstallAsService(sm);
}
} // namespace Service::NPNS
diff --git a/src/core/hle/service/npns/npns.h b/src/core/hle/service/npns/npns.h
index 861cd3e48..3b7596b6b 100644
--- a/src/core/hle/service/npns/npns.h
+++ b/src/core/hle/service/npns/npns.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::NPNS {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::NPNS
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 886450be2..6ccf8995c 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -3,8 +3,10 @@
// Refer to the license.txt file included.
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/ns/errors.h"
@@ -16,7 +18,8 @@
namespace Service::NS {
-IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountProxyInterface"} {
+IAccountProxyInterface::IAccountProxyInterface(Core::System& system_)
+ : ServiceFramework{system_, "IAccountProxyInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateUserAccount"},
@@ -28,8 +31,8 @@ IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountPro
IAccountProxyInterface::~IAccountProxyInterface() = default;
-IApplicationManagerInterface::IApplicationManagerInterface()
- : ServiceFramework{"IApplicationManagerInterface"} {
+IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_)
+ : ServiceFramework{system_, "IApplicationManagerInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "ListApplicationRecord"},
@@ -297,7 +300,8 @@ void IApplicationManagerInterface::GetApplicationControlData(Kernel::HLERequestC
const auto size = ctx.GetWriteBufferSize();
- const FileSys::PatchManager pm{title_id};
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
std::vector<u8> out;
@@ -425,8 +429,8 @@ ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguag
return MakeResult(static_cast<u64>(*language_code));
}
-IApplicationVersionInterface::IApplicationVersionInterface()
- : ServiceFramework{"IApplicationVersionInterface"} {
+IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_)
+ : ServiceFramework{system_, "IApplicationVersionInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetLaunchRequiredVersion"},
@@ -446,8 +450,8 @@ IApplicationVersionInterface::IApplicationVersionInterface()
IApplicationVersionInterface::~IApplicationVersionInterface() = default;
-IContentManagementInterface::IContentManagementInterface()
- : ServiceFramework{"IContentManagementInterface"} {
+IContentManagementInterface::IContentManagementInterface(Core::System& system_)
+ : ServiceFramework{system_, "IContentManagementInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{11, nullptr, "CalculateApplicationOccupiedSize"},
@@ -466,7 +470,8 @@ IContentManagementInterface::IContentManagementInterface()
IContentManagementInterface::~IContentManagementInterface() = default;
-IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface"} {
+IDocumentInterface::IDocumentInterface(Core::System& system_)
+ : ServiceFramework{system_, "IDocumentInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{21, nullptr, "GetApplicationContentPath"},
@@ -480,7 +485,8 @@ IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface"
IDocumentInterface::~IDocumentInterface() = default;
-IDownloadTaskInterface::IDownloadTaskInterface() : ServiceFramework{"IDownloadTaskInterface"} {
+IDownloadTaskInterface::IDownloadTaskInterface(Core::System& system_)
+ : ServiceFramework{system_, "IDownloadTaskInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{701, nullptr, "ClearTaskStatusList"},
@@ -500,7 +506,8 @@ IDownloadTaskInterface::IDownloadTaskInterface() : ServiceFramework{"IDownloadTa
IDownloadTaskInterface::~IDownloadTaskInterface() = default;
-IECommerceInterface::IECommerceInterface() : ServiceFramework{"IECommerceInterface"} {
+IECommerceInterface::IECommerceInterface(Core::System& system_)
+ : ServiceFramework{system_, "IECommerceInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestLinkDevice"},
@@ -518,8 +525,8 @@ IECommerceInterface::IECommerceInterface() : ServiceFramework{"IECommerceInterfa
IECommerceInterface::~IECommerceInterface() = default;
-IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface()
- : ServiceFramework{"IFactoryResetInterface"} {
+IFactoryResetInterface::IFactoryResetInterface(Core::System& system_)
+ : ServiceFramework{system_, "IFactoryResetInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{100, nullptr, "ResetToFactorySettings"},
@@ -537,14 +544,14 @@ IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface()
IFactoryResetInterface::~IFactoryResetInterface() = default;
-NS::NS(const char* name) : ServiceFramework{name} {
+NS::NS(const char* name, Core::System& system_) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{7992, &NS::PushInterface<IECommerceInterface>, "GetECommerceInterface"},
{7993, &NS::PushInterface<IApplicationVersionInterface>, "GetApplicationVersionInterface"},
{7994, &NS::PushInterface<IFactoryResetInterface>, "GetFactoryResetInterface"},
{7995, &NS::PushInterface<IAccountProxyInterface>, "GetAccountProxyInterface"},
- {7996, &NS::PushInterface<IApplicationManagerInterface>, "GetApplicationManagerInterface"},
+ {7996, &NS::PushIApplicationManagerInterface, "GetApplicationManagerInterface"},
{7997, &NS::PushInterface<IDownloadTaskInterface>, "GetDownloadTaskInterface"},
{7998, &NS::PushInterface<IContentManagementInterface>, "GetContentManagementInterface"},
{7999, &NS::PushInterface<IDocumentInterface>, "GetDocumentInterface"},
@@ -557,12 +564,12 @@ NS::NS(const char* name) : ServiceFramework{name} {
NS::~NS() = default;
std::shared_ptr<IApplicationManagerInterface> NS::GetApplicationManagerInterface() const {
- return GetInterface<IApplicationManagerInterface>();
+ return GetInterface<IApplicationManagerInterface>(system);
}
class NS_DEV final : public ServiceFramework<NS_DEV> {
public:
- explicit NS_DEV() : ServiceFramework{"ns:dev"} {
+ explicit NS_DEV(Core::System& system_) : ServiceFramework{system_, "ns:dev"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LaunchProgram"},
@@ -589,7 +596,8 @@ public:
class ISystemUpdateControl final : public ServiceFramework<ISystemUpdateControl> {
public:
- explicit ISystemUpdateControl() : ServiceFramework{"ISystemUpdateControl"} {
+ explicit ISystemUpdateControl(Core::System& system_)
+ : ServiceFramework{system_, "ISystemUpdateControl"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "HasDownloaded"},
@@ -624,7 +632,7 @@ public:
class NS_SU final : public ServiceFramework<NS_SU> {
public:
- explicit NS_SU() : ServiceFramework{"ns:su"} {
+ explicit NS_SU(Core::System& system_) : ServiceFramework{system_, "ns:su"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetBackgroundNetworkUpdateState"},
@@ -656,16 +664,16 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemUpdateControl>();
+ rb.PushIpcInterface<ISystemUpdateControl>(system);
}
};
class NS_VM final : public ServiceFramework<NS_VM> {
public:
- explicit NS_VM() : ServiceFramework{"ns:vm"} {
+ explicit NS_VM(Core::System& system_) : ServiceFramework{system_, "ns:vm"} {
// clang-format off
static const FunctionInfo functions[] = {
- {1200, nullptr, "NeedsUpdateVulnerability"},
+ {1200, &NS_VM::NeedsUpdateVulnerability, "NeedsUpdateVulnerability"},
{1201, nullptr, "UpdateSafeSystemVersionForDebug"},
{1202, nullptr, "GetSafeSystemVersion"},
};
@@ -673,19 +681,28 @@ public:
RegisterHandlers(functions);
}
+
+private:
+ void NeedsUpdateVulnerability(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(false);
+ }
};
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
- std::make_shared<NS>("ns:am2")->InstallAsService(service_manager);
- std::make_shared<NS>("ns:ec")->InstallAsService(service_manager);
- std::make_shared<NS>("ns:rid")->InstallAsService(service_manager);
- std::make_shared<NS>("ns:rt")->InstallAsService(service_manager);
- std::make_shared<NS>("ns:web")->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:am2", system)->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:ec", system)->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:rid", system)->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:rt", system)->InstallAsService(service_manager);
+ std::make_shared<NS>("ns:web", system)->InstallAsService(service_manager);
- std::make_shared<NS_DEV>()->InstallAsService(service_manager);
- std::make_shared<NS_SU>()->InstallAsService(service_manager);
- std::make_shared<NS_VM>()->InstallAsService(service_manager);
+ std::make_shared<NS_DEV>(system)->InstallAsService(service_manager);
+ std::make_shared<NS_SU>(system)->InstallAsService(service_manager);
+ std::make_shared<NS_VM>(system)->InstallAsService(service_manager);
std::make_shared<PL_U>(system)->InstallAsService(service_manager);
}
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index c2554b878..991271f3e 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service {
namespace FileSystem {
@@ -16,13 +20,13 @@ namespace NS {
class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> {
public:
- explicit IAccountProxyInterface();
+ explicit IAccountProxyInterface(Core::System& system_);
~IAccountProxyInterface() override;
};
class IApplicationManagerInterface final : public ServiceFramework<IApplicationManagerInterface> {
public:
- explicit IApplicationManagerInterface();
+ explicit IApplicationManagerInterface(Core::System& system_);
~IApplicationManagerInterface() override;
ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages);
@@ -36,63 +40,71 @@ private:
class IApplicationVersionInterface final : public ServiceFramework<IApplicationVersionInterface> {
public:
- explicit IApplicationVersionInterface();
+ explicit IApplicationVersionInterface(Core::System& system_);
~IApplicationVersionInterface() override;
};
class IContentManagementInterface final : public ServiceFramework<IContentManagementInterface> {
public:
- explicit IContentManagementInterface();
+ explicit IContentManagementInterface(Core::System& system_);
~IContentManagementInterface() override;
};
class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
public:
- explicit IDocumentInterface();
+ explicit IDocumentInterface(Core::System& system_);
~IDocumentInterface() override;
};
class IDownloadTaskInterface final : public ServiceFramework<IDownloadTaskInterface> {
public:
- explicit IDownloadTaskInterface();
+ explicit IDownloadTaskInterface(Core::System& system_);
~IDownloadTaskInterface() override;
};
class IECommerceInterface final : public ServiceFramework<IECommerceInterface> {
public:
- explicit IECommerceInterface();
+ explicit IECommerceInterface(Core::System& system_);
~IECommerceInterface() override;
};
class IFactoryResetInterface final : public ServiceFramework<IFactoryResetInterface> {
public:
- explicit IFactoryResetInterface();
+ explicit IFactoryResetInterface(Core::System& system_);
~IFactoryResetInterface() override;
};
class NS final : public ServiceFramework<NS> {
public:
- explicit NS(const char* name);
+ explicit NS(const char* name, Core::System& system_);
~NS() override;
std::shared_ptr<IApplicationManagerInterface> GetApplicationManagerInterface() const;
private:
- template <typename T>
+ template <typename T, typename... Args>
void PushInterface(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<T>();
+ rb.PushIpcInterface<T>(system);
+ }
+
+ void PushIApplicationManagerInterface(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NS, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IApplicationManagerInterface>(system);
}
- template <typename T>
- std::shared_ptr<T> GetInterface() const {
+ template <typename T, typename... Args>
+ std::shared_ptr<T> GetInterface(Args&&... args) const {
static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
"Not a base of ServiceFrameworkBase");
- return std::make_shared<T>();
+ return std::make_shared<T>(std::forward<Args>(args)...);
}
};
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 40838a225..b6ac0a81a 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -27,42 +27,14 @@
namespace Service::NS {
-enum class FontArchives : u64 {
- Extension = 0x0100000000000810,
- Standard = 0x0100000000000811,
- Korean = 0x0100000000000812,
- ChineseTraditional = 0x0100000000000813,
- ChineseSimple = 0x0100000000000814,
-};
-
struct FontRegion {
u32 offset;
u32 size;
};
-constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
- std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
- std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
- std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
- std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
- std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
- std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
- std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
-};
-
-constexpr std::array<const char*, 7> SHARED_FONTS_TTF{
- "FontStandard.ttf",
- "FontChineseSimplified.ttf",
- "FontExtendedChineseSimplified.ttf",
- "FontChineseTraditional.ttf",
- "FontKorean.ttf",
- "FontNintendoExtended.ttf",
- "FontNintendoExtended2.ttf",
-};
-
// The below data is specific to shared font data dumped from Switch on f/w 2.2
// Virtual address and offsets/sizes likely will vary by dump
-constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
+[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be
constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be
constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
@@ -90,6 +62,23 @@ static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMem
offset += transformed_font.size() * sizeof(u32);
}
+void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output) {
+ ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
+
+ if (input.size() < 2) {
+ LOG_ERROR(Service_NS, "Input font is empty");
+ return;
+ }
+
+ const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
+ std::vector<u32> transformed_font(input.size());
+ // TODO(ogniK): Figure out a better way to do this
+ std::transform(input.begin(), input.end(), transformed_font.begin(),
+ [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
+ std::memcpy(output.data(), transformed_font.data() + 2,
+ (transformed_font.size() - 2) * sizeof(u32));
+}
+
void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output,
std::size_t& offset) {
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
@@ -151,8 +140,8 @@ struct PL_U::Impl {
std::vector<FontRegion> shared_font_regions;
};
-PL_U::PL_U(Core::System& system)
- : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()}, system(system) {
+PL_U::PL_U(Core::System& system_)
+ : ServiceFramework{system_, "pl:u"}, impl{std::make_unique<Impl>()} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &PL_U::RequestLoad, "RequestLoad"},
@@ -192,21 +181,18 @@ PL_U::PL_U(Core::System& system)
}
if (!romfs) {
- LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping",
- static_cast<u64>(font.first));
+ LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", font.first);
continue;
}
const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
if (!extracted_romfs) {
- LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping",
- static_cast<u64>(font.first));
+ LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first);
continue;
}
const auto font_fp = extracted_romfs->GetFile(font.second);
if (!font_fp) {
- LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping",
- static_cast<u64>(font.first), font.second);
+ LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second);
continue;
}
std::vector<u32> font_data_u32(font_fp->GetSize() / sizeof(u32));
diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h
index 27161bd7a..f920c7f69 100644
--- a/src/core/hle/service/ns/pl_u.h
+++ b/src/core/hle/service/ns/pl_u.h
@@ -16,11 +16,30 @@ class FileSystemController;
namespace NS {
+enum class FontArchives : u64 {
+ Extension = 0x0100000000000810,
+ Standard = 0x0100000000000811,
+ Korean = 0x0100000000000812,
+ ChineseTraditional = 0x0100000000000813,
+ ChineseSimple = 0x0100000000000814,
+};
+
+constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
+ std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
+ std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
+ std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
+ std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
+ std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
+ std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
+ std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
+};
+
+void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output);
void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset);
class PL_U final : public ServiceFramework<PL_U> {
public:
- explicit PL_U(Core::System& system);
+ explicit PL_U(Core::System& system_);
~PL_U() override;
private:
@@ -33,7 +52,6 @@ private:
struct Impl;
std::unique_ptr<Impl> impl;
- Core::System& system;
};
} // namespace NS
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 0240d6643..5681599ba 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -24,25 +24,37 @@ public:
explicit nvdevice(Core::System& system) : system{system} {}
virtual ~nvdevice() = default;
- union Ioctl {
- u32_le raw;
- BitField<0, 8, u32> cmd;
- BitField<8, 8, u32> group;
- BitField<16, 14, u32> length;
- BitField<30, 1, u32> is_in;
- BitField<31, 1, u32> is_out;
- };
+ /**
+ * Handles an ioctl1 request.
+ * @param command The ioctl command id.
+ * @param input A buffer containing the input data for the ioctl.
+ * @param output A buffer where the output data will be written to.
+ * @returns The result code of the ioctl.
+ */
+ virtual NvResult Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) = 0;
+
+ /**
+ * Handles an ioctl2 request.
+ * @param command The ioctl command id.
+ * @param input A buffer containing the input data for the ioctl.
+ * @param inline_input A buffer containing the input data for the ioctl which has been inlined.
+ * @param output A buffer where the output data will be written to.
+ * @returns The result code of the ioctl.
+ */
+ virtual NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) = 0;
/**
- * Handles an ioctl request.
+ * Handles an ioctl3 request.
* @param command The ioctl command id.
* @param input A buffer containing the input data for the ioctl.
* @param output A buffer where the output data will be written to.
+ * @param inline_output A buffer where the inlined output data will be written to.
* @returns The result code of the ioctl.
*/
- virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) = 0;
+ virtual NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) = 0;
protected:
Core::System& system;
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 3f7b8e670..ce615c758 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -18,11 +18,22 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_de
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvdisp_disp0 ::~nvdisp_disp0() = default;
-u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+NvResult nvdisp_disp0::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvdisp_disp0::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvdisp_disp0::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height,
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index 6fcdeee84..55a33b7e4 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -20,9 +20,11 @@ public:
explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvdisp_disp0() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle.
void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index d4ba88147..6b062e10e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -17,57 +17,77 @@
namespace Service::Nvidia::Devices {
-namespace NvErrCodes {
-constexpr u32 Success{};
-constexpr u32 OutOfMemory{static_cast<u32>(-12)};
-constexpr u32 InvalidInput{static_cast<u32>(-22)};
-} // namespace NvErrCodes
-
nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::~nvhost_as_gpu() = default;
-u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocInitalizeExCommand:
- return InitalizeEx(input, output);
- case IoctlCommand::IocAllocateSpaceCommand:
- return AllocateSpace(input, output);
- case IoctlCommand::IocMapBufferExCommand:
- return MapBufferEx(input, output);
- case IoctlCommand::IocBindChannelCommand:
- return BindChannel(input, output);
- case IoctlCommand::IocGetVaRegionsCommand:
- return GetVARegions(input, output);
- case IoctlCommand::IocUnmapBufferCommand:
- return UnmapBuffer(input, output);
+NvResult nvhost_as_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ switch (command.group) {
+ case 'A':
+ switch (command.cmd) {
+ case 0x1:
+ return BindChannel(input, output);
+ case 0x2:
+ return AllocateSpace(input, output);
+ case 0x3:
+ return FreeSpace(input, output);
+ case 0x5:
+ return UnmapBuffer(input, output);
+ case 0x6:
+ return MapBufferEx(input, output);
+ case 0x8:
+ return GetVARegions(input, output);
+ case 0x9:
+ return InitalizeEx(input, output);
+ case 0x14:
+ return Remap(input, output);
+ default:
+ break;
+ }
+ break;
default:
break;
}
- if (static_cast<IoctlCommand>(command.cmd.Value()) == IoctlCommand::IocRemapCommand) {
- return Remap(input, output);
- }
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
- UNIMPLEMENTED_MSG("Unimplemented ioctl command");
- return 0;
+NvResult nvhost_as_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ switch (command.group) {
+ case 'A':
+ switch (command.cmd) {
+ case 0x8:
+ return GetVARegions(input, output, inline_output);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_as_gpu::InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlInitalizeEx params{};
std::memcpy(&params, input.data(), input.size());
LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocSpace params{};
std::memcpy(&params, input.data(), input.size());
@@ -81,22 +101,36 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>&
params.offset = system.GPU().MemoryManager().Allocate(size, params.align);
}
- auto result{NvErrCodes::Success};
+ auto result = NvResult::Success;
if (!params.offset) {
LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size);
- result = NvErrCodes::OutOfMemory;
+ result = NvResult::InsufficientMemory;
}
std::memcpy(output.data(), &params, output.size());
return result;
}
-u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlFreeSpace params{};
+ std::memcpy(&params, input.data(), input.size());
+
+ LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
+ params.pages, params.page_size);
+
+ system.GPU().MemoryManager().Unmap(params.offset,
+ static_cast<std::size_t>(params.pages) * params.page_size);
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::Success;
+}
+
+NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) {
const auto num_entries = input.size() / sizeof(IoctlRemapEntry);
LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
- auto result{NvErrCodes::Success};
+ auto result = NvResult::Success;
std::vector<IoctlRemapEntry> entries(num_entries);
std::memcpy(entries.data(), input.data(), input.size());
@@ -107,7 +141,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
const auto object{nvmap_dev->GetObject(entry.nvmap_handle)};
if (!object) {
LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle);
- result = NvErrCodes::InvalidInput;
+ result = NvResult::InvalidState;
break;
}
@@ -118,7 +152,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
if (!addr) {
LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!");
- result = NvErrCodes::InvalidInput;
+ result = NvResult::InvalidState;
break;
}
}
@@ -127,7 +161,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
return result;
}
-u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlMapBufferEx params{};
std::memcpy(&params, input.data(), input.size());
@@ -141,7 +175,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
if (!object) {
LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle);
std::memcpy(output.data(), &params, output.size());
- return NvErrCodes::InvalidInput;
+ return NvResult::InvalidState;
}
// The real nvservices doesn't make a distinction between handles and ids, and
@@ -168,16 +202,16 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
params.mapping_size, params.offset);
std::memcpy(output.data(), &params, output.size());
- return NvErrCodes::InvalidInput;
+ return NvResult::InvalidState;
}
std::memcpy(output.data(), &params, output.size());
- return NvErrCodes::Success;
+ return NvResult::Success;
} else {
LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset);
std::memcpy(output.data(), &params, output.size());
- return NvErrCodes::InvalidInput;
+ return NvResult::InvalidState;
}
}
@@ -197,10 +231,10 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size);
}
- auto result{NvErrCodes::Success};
+ auto result = NvResult::Success;
if (!params.offset) {
LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size);
- result = NvErrCodes::InvalidInput;
+ result = NvResult::InvalidState;
} else {
AddBufferMap(params.offset, size, physical_address, is_alloc);
}
@@ -209,7 +243,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
return result;
}
-u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlUnmapBuffer params{};
std::memcpy(&params, input.data(), input.size());
@@ -222,20 +256,42 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
}
std::memcpy(output.data(), &params, output.size());
- return NvErrCodes::Success;
+ return NvResult::Success;
}
-u32 nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlBindChannel params{};
std::memcpy(&params, input.data(), input.size());
-
- LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}", params.fd);
channel = params.fd;
- return 0;
+ return NvResult::Success;
+}
+
+NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlGetVaRegions params{};
+ std::memcpy(&params, input.data(), input.size());
+
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+ params.buf_size);
+
+ params.buf_size = 0x30;
+ params.regions[0].offset = 0x04000000;
+ params.regions[0].page_size = 0x1000;
+ params.regions[0].pages = 0x3fbfff;
+
+ params.regions[1].offset = 0x04000000;
+ params.regions[1].page_size = 0x10000;
+ params.regions[1].pages = 0x1bffff;
+
+ // TODO(ogniK): This probably can stay stubbed but should add support way way later
+
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::Success;
}
-u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
IoctlGetVaRegions params{};
std::memcpy(&params, input.data(), input.size());
@@ -254,7 +310,8 @@ u32 nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& o
// TODO(ogniK): This probably can stay stubbed but should add support way way later
std::memcpy(output.data(), &params, output.size());
- return 0;
+ std::memcpy(inline_output.data(), &params.regions, inline_output.size());
+ return NvResult::Success;
}
std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const {
@@ -265,7 +322,7 @@ std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gp
}
}
- return {};
+ return std::nullopt;
}
void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
@@ -286,7 +343,7 @@ std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
return size;
}
- return {};
+ return std::nullopt;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index 9a0cdff0c..08035fa0e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -30,9 +30,11 @@ public:
explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_as_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
class BufferMap final {
@@ -74,31 +76,21 @@ private:
bool is_allocated{};
};
- enum class IoctlCommand : u32_le {
- IocInitalizeExCommand = 0x40284109,
- IocAllocateSpaceCommand = 0xC0184102,
- IocRemapCommand = 0x00000014,
- IocMapBufferExCommand = 0xC0284106,
- IocBindChannelCommand = 0x40044101,
- IocGetVaRegionsCommand = 0xC0404108,
- IocUnmapBufferCommand = 0xC0084105,
- };
-
struct IoctlInitalizeEx {
- u32_le big_page_size; // depends on GPU's available_big_page_sizes; 0=default
- s32_le as_fd; // ignored; passes 0
- u32_le flags; // passes 0
- u32_le reserved; // ignored; passes 0
- u64_le unk0;
- u64_le unk1;
- u64_le unk2;
+ u32_le big_page_size{}; // depends on GPU's available_big_page_sizes; 0=default
+ s32_le as_fd{}; // ignored; passes 0
+ u32_le flags{}; // passes 0
+ u32_le reserved{}; // ignored; passes 0
+ u64_le unk0{};
+ u64_le unk1{};
+ u64_le unk2{};
};
static_assert(sizeof(IoctlInitalizeEx) == 40, "IoctlInitalizeEx is incorrect size");
struct IoctlAllocSpace {
- u32_le pages;
- u32_le page_size;
- AddressSpaceFlags flags;
+ u32_le pages{};
+ u32_le page_size{};
+ AddressSpaceFlags flags{};
INSERT_PADDING_WORDS(1);
union {
u64_le offset;
@@ -107,63 +99,74 @@ private:
};
static_assert(sizeof(IoctlAllocSpace) == 24, "IoctlInitalizeEx is incorrect size");
+ struct IoctlFreeSpace {
+ u64_le offset{};
+ u32_le pages{};
+ u32_le page_size{};
+ };
+ static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
+
struct IoctlRemapEntry {
- u16_le flags;
- u16_le kind;
- u32_le nvmap_handle;
- u32_le map_offset;
- u32_le offset;
- u32_le pages;
+ u16_le flags{};
+ u16_le kind{};
+ u32_le nvmap_handle{};
+ u32_le map_offset{};
+ u32_le offset{};
+ u32_le pages{};
};
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
struct IoctlMapBufferEx {
- AddressSpaceFlags flags; // bit0: fixed_offset, bit2: cacheable
- u32_le kind; // -1 is default
- u32_le nvmap_handle;
- u32_le page_size; // 0 means don't care
- s64_le buffer_offset;
- u64_le mapping_size;
- s64_le offset;
+ AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable
+ u32_le kind{}; // -1 is default
+ u32_le nvmap_handle{};
+ u32_le page_size{}; // 0 means don't care
+ s64_le buffer_offset{};
+ u64_le mapping_size{};
+ s64_le offset{};
};
static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size");
struct IoctlUnmapBuffer {
- s64_le offset;
+ s64_le offset{};
};
static_assert(sizeof(IoctlUnmapBuffer) == 8, "IoctlUnmapBuffer is incorrect size");
struct IoctlBindChannel {
- u32_le fd;
+ s32_le fd{};
};
static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
struct IoctlVaRegion {
- u64_le offset;
- u32_le page_size;
+ u64_le offset{};
+ u32_le page_size{};
INSERT_PADDING_WORDS(1);
- u64_le pages;
+ u64_le pages{};
};
static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size");
struct IoctlGetVaRegions {
- u64_le buf_addr; // (contained output user ptr on linux, ignored)
- u32_le buf_size; // forced to 2*sizeof(struct va_region)
- u32_le reserved;
- IoctlVaRegion regions[2];
+ u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
+ u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
+ u32_le reserved{};
+ IoctlVaRegion regions[2]{};
};
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
"IoctlGetVaRegions is incorrect size");
- u32 channel{};
+ s32 channel{};
+
+ NvResult InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
- u32 InitalizeEx(const std::vector<u8>& input, std::vector<u8>& output);
- u32 AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
- u32 Remap(const std::vector<u8>& input, std::vector<u8>& output);
- u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
- u32 UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
- u32 BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
- u32 GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output);
std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index b27ee0502..f6129ef10 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -8,52 +8,66 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
#include "video_core/gpu.h"
namespace Service::Nvidia::Devices {
-nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface)
- : nvdevice(system), events_interface{events_interface} {}
+nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface,
+ SyncpointManager& syncpoint_manager)
+ : nvdevice(system), events_interface{events_interface}, syncpoint_manager{syncpoint_manager} {}
nvhost_ctrl::~nvhost_ctrl() = default;
-u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocGetConfigCommand:
- return NvOsGetConfigU32(input, output);
- case IoctlCommand::IocCtrlEventWaitCommand:
- return IocCtrlEventWait(input, output, false, ctrl);
- case IoctlCommand::IocCtrlEventWaitAsyncCommand:
- return IocCtrlEventWait(input, output, true, ctrl);
- case IoctlCommand::IocCtrlEventRegisterCommand:
- return IocCtrlEventRegister(input, output);
- case IoctlCommand::IocCtrlEventUnregisterCommand:
- return IocCtrlEventUnregister(input, output);
- case IoctlCommand::IocCtrlEventSignalCommand:
- return IocCtrlEventSignal(input, output);
+NvResult nvhost_ctrl::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x0:
+ switch (command.cmd) {
+ case 0x1b:
+ return NvOsGetConfigU32(input, output);
+ case 0x1c:
+ return IocCtrlClearEventWait(input, output);
+ case 0x1d:
+ return IocCtrlEventWait(input, output, false);
+ case 0x1e:
+ return IocCtrlEventWait(input, output, true);
+ case 0x1f:
+ return IocCtrlEventRegister(input, output);
+ case 0x20:
+ return IocCtrlEventUnregister(input, output);
+ }
+ break;
default:
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ break;
}
+
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_ctrl::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_ctrl::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_outpu) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetConfigParams params{};
std::memcpy(&params, input.data(), sizeof(params));
LOG_TRACE(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(),
params.param_str.data());
- return 0x30006; // Returns error on production mode
+ return NvResult::ConfigVarNotFound; // Returns error on production mode
}
-u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
- bool is_async, IoctlCtrl& ctrl) {
+NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
+ bool is_async) {
IocCtrlEventWaitParams params{};
std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
@@ -70,19 +84,33 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
return NvResult::BadParameter;
}
+ if (syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
+ params.value = syncpoint_manager.GetSyncpointMin(params.syncpt_id);
+ std::memcpy(output.data(), &params, sizeof(params));
+ return NvResult::Success;
+ }
+
+ if (const auto new_value = syncpoint_manager.RefreshSyncpoint(params.syncpt_id);
+ syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
+ params.value = new_value;
+ std::memcpy(output.data(), &params, sizeof(params));
+ return NvResult::Success;
+ }
+
auto event = events_interface.events[event_id];
auto& gpu = system.GPU();
+
// This is mostly to take into account unimplemented features. As synced
// gpu is always synced.
if (!gpu.IsAsync()) {
- event.writable->Signal();
+ event.event->GetWritableEvent()->Signal();
return NvResult::Success;
}
auto lock = gpu.LockSync();
- const u32 current_syncpoint_value = gpu.GetSyncpointValue(params.syncpt_id);
+ const u32 current_syncpoint_value = event.fence.value;
const s32 diff = current_syncpoint_value - params.threshold;
if (diff >= 0) {
- event.writable->Signal();
+ event.event->GetWritableEvent()->Signal();
params.value = current_syncpoint_value;
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
@@ -109,14 +137,8 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
}
params.value |= event_id;
- event.writable->Clear();
+ event.event->GetWritableEvent()->Clear();
gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
- if (!is_async && ctrl.fresh_call) {
- ctrl.must_delay = true;
- ctrl.timeout = params.timeout;
- ctrl.event_id = event_id;
- return NvResult::Timeout;
- }
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Timeout;
}
@@ -124,7 +146,7 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
return NvResult::BadParameter;
}
-u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
IocCtrlEventRegisterParams params{};
std::memcpy(&params, input.data(), sizeof(params));
const u32 event_id = params.user_event_id & 0x00FF;
@@ -133,13 +155,20 @@ u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<
return NvResult::BadParameter;
}
if (events_interface.registered[event_id]) {
- return NvResult::BadParameter;
+ const auto event_state = events_interface.status[event_id];
+ if (event_state != EventState::Free) {
+ LOG_WARNING(Service_NVDRV, "Event already registered! Unregistering previous event");
+ events_interface.UnregisterEvent(event_id);
+ } else {
+ return NvResult::BadParameter;
+ }
}
events_interface.RegisterEvent(event_id);
return NvResult::Success;
}
-u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input,
+ std::vector<u8>& output) {
IocCtrlEventUnregisterParams params{};
std::memcpy(&params, input.data(), sizeof(params));
const u32 event_id = params.user_event_id & 0x00FF;
@@ -154,24 +183,22 @@ u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vecto
return NvResult::Success;
}
-u32 nvhost_ctrl::IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
IocCtrlEventSignalParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- // TODO(Blinkhawk): This is normally called when an NvEvents timeout on WaitSynchronization
- // It is believed from RE to cancel the GPU Event. However, better research is required
- u32 event_id = params.user_event_id & 0x00FF;
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, user_event_id: {:X}", event_id);
+
+ u32 event_id = params.event_id & 0x00FF;
+ LOG_WARNING(Service_NVDRV, "cleared event wait on, event_id: {:X}", event_id);
+
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
if (events_interface.status[event_id] == EventState::Waiting) {
- auto& gpu = system.GPU();
- if (gpu.CancelSyncptInterrupt(events_interface.assigned_syncpt[event_id],
- events_interface.assigned_value[event_id])) {
- events_interface.LiberateEvent(event_id);
- events_interface.events[event_id].writable->Signal();
- }
+ events_interface.LiberateEvent(event_id);
}
+
+ syncpoint_manager.RefreshSyncpoint(events_interface.events[event_id].fence.id);
+
return NvResult::Success;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index 9898623de..c5aa1362a 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -14,137 +14,120 @@ namespace Service::Nvidia::Devices {
class nvhost_ctrl final : public nvdevice {
public:
- explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface);
+ explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface,
+ SyncpointManager& syncpoint_manager);
~nvhost_ctrl() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocSyncptReadCommand = 0xC0080014,
- IocSyncptIncrCommand = 0x40040015,
- IocSyncptWaitCommand = 0xC00C0016,
- IocModuleMutexCommand = 0x40080017,
- IocModuleRegRDWRCommand = 0xC0180018,
- IocSyncptWaitexCommand = 0xC0100019,
- IocSyncptReadMaxCommand = 0xC008001A,
- IocGetConfigCommand = 0xC183001B,
- IocCtrlEventSignalCommand = 0xC004001C,
- IocCtrlEventWaitCommand = 0xC010001D,
- IocCtrlEventWaitAsyncCommand = 0xC010001E,
- IocCtrlEventRegisterCommand = 0xC004001F,
- IocCtrlEventUnregisterCommand = 0xC0040020,
- IocCtrlEventKillCommand = 0x40080021,
- };
struct IocSyncptReadParams {
- u32_le id;
- u32_le value;
+ u32_le id{};
+ u32_le value{};
};
static_assert(sizeof(IocSyncptReadParams) == 8, "IocSyncptReadParams is incorrect size");
struct IocSyncptIncrParams {
- u32_le id;
+ u32_le id{};
};
static_assert(sizeof(IocSyncptIncrParams) == 4, "IocSyncptIncrParams is incorrect size");
struct IocSyncptWaitParams {
- u32_le id;
- u32_le thresh;
- s32_le timeout;
+ u32_le id{};
+ u32_le thresh{};
+ s32_le timeout{};
};
static_assert(sizeof(IocSyncptWaitParams) == 12, "IocSyncptWaitParams is incorrect size");
struct IocModuleMutexParams {
- u32_le id;
- u32_le lock; // (0 = unlock and 1 = lock)
+ u32_le id{};
+ u32_le lock{}; // (0 = unlock and 1 = lock)
};
static_assert(sizeof(IocModuleMutexParams) == 8, "IocModuleMutexParams is incorrect size");
struct IocModuleRegRDWRParams {
- u32_le id;
- u32_le num_offsets;
- u32_le block_size;
- u32_le offsets;
- u32_le values;
- u32_le write;
+ u32_le id{};
+ u32_le num_offsets{};
+ u32_le block_size{};
+ u32_le offsets{};
+ u32_le values{};
+ u32_le write{};
};
static_assert(sizeof(IocModuleRegRDWRParams) == 24, "IocModuleRegRDWRParams is incorrect size");
struct IocSyncptWaitexParams {
- u32_le id;
- u32_le thresh;
- s32_le timeout;
- u32_le value;
+ u32_le id{};
+ u32_le thresh{};
+ s32_le timeout{};
+ u32_le value{};
};
static_assert(sizeof(IocSyncptWaitexParams) == 16, "IocSyncptWaitexParams is incorrect size");
struct IocSyncptReadMaxParams {
- u32_le id;
- u32_le value;
+ u32_le id{};
+ u32_le value{};
};
static_assert(sizeof(IocSyncptReadMaxParams) == 8, "IocSyncptReadMaxParams is incorrect size");
struct IocGetConfigParams {
- std::array<char, 0x41> domain_str;
- std::array<char, 0x41> param_str;
- std::array<char, 0x101> config_str;
+ std::array<char, 0x41> domain_str{};
+ std::array<char, 0x41> param_str{};
+ std::array<char, 0x101> config_str{};
};
static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
struct IocCtrlEventSignalParams {
- u32_le user_event_id;
+ u32_le event_id{};
};
static_assert(sizeof(IocCtrlEventSignalParams) == 4,
"IocCtrlEventSignalParams is incorrect size");
struct IocCtrlEventWaitParams {
- u32_le syncpt_id;
- u32_le threshold;
- s32_le timeout;
- u32_le value;
+ u32_le syncpt_id{};
+ u32_le threshold{};
+ s32_le timeout{};
+ u32_le value{};
};
static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size");
struct IocCtrlEventWaitAsyncParams {
- u32_le syncpt_id;
- u32_le threshold;
- u32_le timeout;
- u32_le value;
+ u32_le syncpt_id{};
+ u32_le threshold{};
+ u32_le timeout{};
+ u32_le value{};
};
static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16,
"IocCtrlEventWaitAsyncParams is incorrect size");
struct IocCtrlEventRegisterParams {
- u32_le user_event_id;
+ u32_le user_event_id{};
};
static_assert(sizeof(IocCtrlEventRegisterParams) == 4,
"IocCtrlEventRegisterParams is incorrect size");
struct IocCtrlEventUnregisterParams {
- u32_le user_event_id;
+ u32_le user_event_id{};
};
static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
"IocCtrlEventUnregisterParams is incorrect size");
struct IocCtrlEventKill {
- u64_le user_events;
+ u64_le user_events{};
};
static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size");
- u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
-
- u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async,
- IoctlCtrl& ctrl);
-
- u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
-
- u32 IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
-
- u32 IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
+ NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);
EventInterface& events_interface;
+ SyncpointManager& syncpoint_manager;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index fba89e7a6..0320d3ae2 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -15,39 +15,66 @@ namespace Service::Nvidia::Devices {
nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {}
nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
-u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input,
- const std::vector<u8>& input2, std::vector<u8>& output,
- std::vector<u8>& output2, IoctlCtrl& ctrl, IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocGetCharacteristicsCommand:
- return GetCharacteristics(input, output, output2, version);
- case IoctlCommand::IocGetTPCMasksCommand:
- return GetTPCMasks(input, output, output2, version);
- case IoctlCommand::IocGetActiveSlotMaskCommand:
- return GetActiveSlotMask(input, output);
- case IoctlCommand::IocZcullGetCtxSizeCommand:
- return ZCullGetCtxSize(input, output);
- case IoctlCommand::IocZcullGetInfo:
- return ZCullGetInfo(input, output);
- case IoctlCommand::IocZbcSetTable:
- return ZBCSetTable(input, output);
- case IoctlCommand::IocZbcQueryTable:
- return ZBCQueryTable(input, output);
- case IoctlCommand::IocFlushL2:
- return FlushL2(input, output);
- case IoctlCommand::IocGetGpuTime:
- return GetGpuTime(input, output);
+NvResult nvhost_ctrl_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ switch (command.group) {
+ case 'G':
+ switch (command.cmd) {
+ case 0x1:
+ return ZCullGetCtxSize(input, output);
+ case 0x2:
+ return ZCullGetInfo(input, output);
+ case 0x3:
+ return ZBCSetTable(input, output);
+ case 0x4:
+ return ZBCQueryTable(input, output);
+ case 0x5:
+ return GetCharacteristics(input, output);
+ case 0x6:
+ return GetTPCMasks(input, output);
+ case 0x7:
+ return FlushL2(input, output);
+ case 0x14:
+ return GetActiveSlotMask(input, output);
+ case 0x1c:
+ return GetGpuTime(input, output);
+ default:
+ break;
+ }
+ break;
+ }
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_ctrl_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_ctrl_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output, std::vector<u8>& inline_output) {
+ switch (command.group) {
+ case 'G':
+ switch (command.cmd) {
+ case 0x5:
+ return GetCharacteristics(input, output, inline_output);
+ case 0x6:
+ return GetTPCMasks(input, output, inline_output);
+ default:
+ break;
+ }
+ break;
default:
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ break;
}
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
- std::vector<u8>& output2, IoctlVersion version) {
+NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input,
+ std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlCharacteristics params{};
std::memcpy(&params, input.data(), input.size());
@@ -88,36 +115,83 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto
params.gc.gr_compbit_store_base_hw = 0x0;
params.gpu_characteristics_buf_size = 0xA0;
params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::Success;
+}
- if (version == IoctlVersion::Version3) {
- std::memcpy(output.data(), input.data(), output.size());
- std::memcpy(output2.data(), &params.gc, output2.size());
- } else {
- std::memcpy(output.data(), &params, output.size());
- }
- return 0;
+NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ LOG_DEBUG(Service_NVDRV, "called");
+ IoctlCharacteristics params{};
+ std::memcpy(&params, input.data(), input.size());
+ params.gc.arch = 0x120;
+ params.gc.impl = 0xb;
+ params.gc.rev = 0xa1;
+ params.gc.num_gpc = 0x1;
+ params.gc.l2_cache_size = 0x40000;
+ params.gc.on_board_video_memory_size = 0x0;
+ params.gc.num_tpc_per_gpc = 0x2;
+ params.gc.bus_type = 0x20;
+ params.gc.big_page_size = 0x20000;
+ params.gc.compression_page_size = 0x20000;
+ params.gc.pde_coverage_bit_count = 0x1B;
+ params.gc.available_big_page_sizes = 0x30000;
+ params.gc.gpc_mask = 0x1;
+ params.gc.sm_arch_sm_version = 0x503;
+ params.gc.sm_arch_spa_version = 0x503;
+ params.gc.sm_arch_warp_count = 0x80;
+ params.gc.gpu_va_bit_count = 0x28;
+ params.gc.reserved = 0x0;
+ params.gc.flags = 0x55;
+ params.gc.twod_class = 0x902D;
+ params.gc.threed_class = 0xB197;
+ params.gc.compute_class = 0xB1C0;
+ params.gc.gpfifo_class = 0xB06F;
+ params.gc.inline_to_memory_class = 0xA140;
+ params.gc.dma_copy_class = 0xB0B5;
+ params.gc.max_fbps_count = 0x1;
+ params.gc.fbp_en_mask = 0x0;
+ params.gc.max_ltc_per_fbp = 0x2;
+ params.gc.max_lts_per_ltc = 0x1;
+ params.gc.max_tex_per_tpc = 0x0;
+ params.gc.max_gpc_count = 0x1;
+ params.gc.rop_l2_en_mask_0 = 0x21D70;
+ params.gc.rop_l2_en_mask_1 = 0x0;
+ params.gc.chipname = 0x6230326D67;
+ params.gc.gr_compbit_store_base_hw = 0x0;
+ params.gpu_characteristics_buf_size = 0xA0;
+ params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED)
+
+ std::memcpy(output.data(), &params, output.size());
+ std::memcpy(inline_output.data(), &params.gc, inline_output.size());
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output,
- std::vector<u8>& output2, IoctlVersion version) {
+NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGpuGetTpcMasksArgs params{};
std::memcpy(&params, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size);
if (params.mask_buffer_size != 0) {
params.tcp_mask = 3;
}
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::Success;
+}
- if (version == IoctlVersion::Version3) {
- std::memcpy(output.data(), input.data(), output.size());
- std::memcpy(output2.data(), &params.tcp_mask, output2.size());
- } else {
- std::memcpy(output.data(), &params, output.size());
+NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ IoctlGpuGetTpcMasksArgs params{};
+ std::memcpy(&params, input.data(), input.size());
+ LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size);
+ if (params.mask_buffer_size != 0) {
+ params.tcp_mask = 3;
}
-
- return 0;
+ std::memcpy(output.data(), &params, output.size());
+ std::memcpy(inline_output.data(), &params.tcp_mask, inline_output.size());
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlActiveSlotMask params{};
@@ -127,10 +201,10 @@ u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector<u8>& input, std::vector
params.slot = 0x07;
params.mask = 0x01;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlZcullGetCtxSize params{};
@@ -139,10 +213,10 @@ u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u
}
params.size = 0x1;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlNvgpuGpuZcullGetInfoArgs params{};
@@ -162,47 +236,47 @@ u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>&
params.subregion_height_align_pixels = 0x40;
params.subregion_count = 0x10;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
IoctlZbcSetTable params{};
std::memcpy(&params, input.data(), input.size());
// TODO(ogniK): What does this even actually do?
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
IoctlZbcQueryTable params{};
std::memcpy(&params, input.data(), input.size());
// TODO : To implement properly
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::FlushL2(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
IoctlFlushL2 params{};
std::memcpy(&params, input.data(), input.size());
// TODO : To implement properly
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlGetGpuTime params{};
std::memcpy(&params, input.data(), input.size());
params.gpu_time = static_cast<u64_le>(system.CoreTiming().GetGlobalTimeNs().count());
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index ef60f72ce..137b88238 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -16,32 +16,13 @@ public:
explicit nvhost_ctrl_gpu(Core::System& system);
~nvhost_ctrl_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocGetCharacteristicsCommand = 0xC0B04705,
- IocGetTPCMasksCommand = 0xC0184706,
- IocGetActiveSlotMaskCommand = 0x80084714,
- IocZcullGetCtxSizeCommand = 0x80044701,
- IocZcullGetInfo = 0x80284702,
- IocZbcSetTable = 0x402C4703,
- IocZbcQueryTable = 0xC0344704,
- IocFlushL2 = 0x40084707,
- IocInvalICache = 0x4008470D,
- IocSetMmudebugMode = 0x4008470E,
- IocSetSmDebugMode = 0x4010470F,
- IocWaitForPause = 0xC0084710,
- IocGetTcpExceptionEnStatus = 0x80084711,
- IocNumVsms = 0x80084712,
- IocVsmsMapping = 0xC0044713,
- IocGetErrorChannelUserData = 0xC008471B,
- IocGetGpuTime = 0xC010471C,
- IocGetCpuTimeCorrelationInfo = 0xC108471D,
- };
-
struct IoctlGpuCharacteristics {
u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
u32_le impl; // 0xB (NVGPU_GPU_IMPL_GM20B)
@@ -159,17 +140,21 @@ private:
};
static_assert(sizeof(IoctlGetGpuTime) == 0x10, "IoctlGetGpuTime is incorrect size");
- u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
- std::vector<u8>& output2, IoctlVersion version);
- u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output, std::vector<u8>& output2,
- IoctlVersion version);
- u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
- u32 FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
- u32 GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output);
+
+ NvResult GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output);
+
+ NvResult GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZCullGetInfo(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZBCSetTable(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index f1966ac0e..af8b3d9f1 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -7,117 +7,148 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/memory.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
namespace Service::Nvidia::Devices {
-nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
- : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
+nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager)
+ : nvdevice(system), nvmap_dev(std::move(nvmap_dev)), syncpoint_manager{syncpoint_manager} {
+ channel_fence.id = syncpoint_manager.AllocateSyncpoint();
+ channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id);
+}
+
nvhost_gpu::~nvhost_gpu() = default;
-u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocSetNVMAPfdCommand:
- return SetNVMAPfd(input, output);
- case IoctlCommand::IocSetClientDataCommand:
- return SetClientData(input, output);
- case IoctlCommand::IocGetClientDataCommand:
- return GetClientData(input, output);
- case IoctlCommand::IocZCullBind:
- return ZCullBind(input, output);
- case IoctlCommand::IocSetErrorNotifierCommand:
- return SetErrorNotifier(input, output);
- case IoctlCommand::IocChannelSetPriorityCommand:
- return SetChannelPriority(input, output);
- case IoctlCommand::IocAllocGPFIFOEx2Command:
- return AllocGPFIFOEx2(input, output);
- case IoctlCommand::IocAllocObjCtxCommand:
- return AllocateObjectContext(input, output);
- case IoctlCommand::IocChannelGetWaitbaseCommand:
- return GetWaitbase(input, output);
- case IoctlCommand::IocChannelSetTimeoutCommand:
- return ChannelSetTimeout(input, output);
- case IoctlCommand::IocChannelSetTimeslice:
- return ChannelSetTimeslice(input, output);
- default:
+NvResult nvhost_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x0:
+ switch (command.cmd) {
+ case 0x3:
+ return GetWaitbase(input, output);
+ default:
+ break;
+ }
+ break;
+ case 'H':
+ switch (command.cmd) {
+ case 0x1:
+ return SetNVMAPfd(input, output);
+ case 0x3:
+ return ChannelSetTimeout(input, output);
+ case 0x8:
+ return SubmitGPFIFOBase(input, output, false);
+ case 0x9:
+ return AllocateObjectContext(input, output);
+ case 0xb:
+ return ZCullBind(input, output);
+ case 0xc:
+ return SetErrorNotifier(input, output);
+ case 0xd:
+ return SetChannelPriority(input, output);
+ case 0x1a:
+ return AllocGPFIFOEx2(input, output);
+ case 0x1b:
+ return SubmitGPFIFOBase(input, output, true);
+ case 0x1d:
+ return ChannelSetTimeslice(input, output);
+ default:
+ break;
+ }
+ break;
+ case 'G':
+ switch (command.cmd) {
+ case 0x14:
+ return SetClientData(input, output);
+ case 0x15:
+ return GetClientData(input, output);
+ default:
+ break;
+ }
break;
}
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+};
- if (command.group == NVGPU_IOCTL_MAGIC) {
- if (command.cmd == NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO) {
- return SubmitGPFIFO(input, output);
- }
- if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) {
- return KickoffPB(input, output, input2, version);
+NvResult nvhost_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 'H':
+ switch (command.cmd) {
+ case 0x1b:
+ return SubmitGPFIFOBase(input, inline_input, output);
}
+ break;
}
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
-};
+NvResult nvhost_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
-u32 nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetNvmapFD params{};
std::memcpy(&params, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
nvmap_fd = params.nvmap_fd;
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::SetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlClientData params{};
std::memcpy(&params, input.data(), input.size());
user_data = params.data;
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::GetClientData(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_DEBUG(Service_NVDRV, "called");
IoctlClientData params{};
std::memcpy(&params, input.data(), input.size());
params.data = user_data;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::ZCullBind(const std::vector<u8>& input, std::vector<u8>& output) {
std::memcpy(&zcull_params, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va,
zcull_params.mode);
std::memcpy(output.data(), &zcull_params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetErrorNotifier params{};
std::memcpy(&params, input.data(), input.size());
LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}", params.offset,
params.size, params.mem);
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output) {
std::memcpy(&channel_priority, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority);
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocGpfifoEx2 params{};
std::memcpy(&params, input.data(), input.size());
LOG_WARNING(Service_NVDRV,
@@ -126,15 +157,15 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou
params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
params.unk3);
- auto& gpu = system.GPU();
- params.fence_out.id = assigned_syncpoints;
- params.fence_out.value = gpu.GetSyncpointValue(assigned_syncpoints);
- assigned_syncpoints++;
+ channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id);
+
+ params.fence_out = channel_fence;
+
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlAllocObjCtx params{};
std::memcpy(&params, input.data(), input.size());
LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num,
@@ -142,102 +173,149 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<
params.obj_id = 0x0;
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output) {
- if (input.size() < sizeof(IoctlSubmitGpfifo)) {
- UNIMPLEMENTED();
+static std::vector<Tegra::CommandHeader> BuildWaitCommandList(Fence fence) {
+ return {
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+ Tegra::SubmissionMode::Increasing),
+ {fence.value},
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+ Tegra::SubmissionMode::Increasing),
+ Tegra::GPU::FenceAction::Build(Tegra::GPU::FenceOperation::Acquire, fence.id),
+ };
+}
+
+static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(Fence fence, u32 add_increment) {
+ std::vector<Tegra::CommandHeader> result{
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+ Tegra::SubmissionMode::Increasing),
+ {}};
+
+ for (u32 count = 0; count < add_increment; ++count) {
+ result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+ Tegra::SubmissionMode::Increasing));
+ result.emplace_back(
+ Tegra::GPU::FenceAction::Build(Tegra::GPU::FenceOperation::Increment, fence.id));
}
- IoctlSubmitGpfifo params{};
- std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
+
+ return result;
+}
+
+static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(Fence fence,
+ u32 add_increment) {
+ std::vector<Tegra::CommandHeader> result{
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1,
+ Tegra::SubmissionMode::Increasing),
+ {}};
+ const std::vector<Tegra::CommandHeader> increment{
+ BuildIncrementCommandList(fence, add_increment)};
+
+ result.insert(result.end(), increment.begin(), increment.end());
+
+ return result;
+}
+
+NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>& output,
+ Tegra::CommandList&& entries) {
LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
params.num_entries, params.flags.raw);
- ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) +
- params.num_entries * sizeof(Tegra::CommandListHeader),
- "Incorrect input size");
+ auto& gpu = system.GPU();
- Tegra::CommandList entries(params.num_entries);
- std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)],
- params.num_entries * sizeof(Tegra::CommandListHeader));
+ params.fence_out.id = channel_fence.id;
- UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
- UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
+ if (params.flags.add_wait.Value() &&
+ !syncpoint_manager.IsSyncpointExpired(params.fence_out.id, params.fence_out.value)) {
+ gpu.PushGPUEntries(Tegra::CommandList{BuildWaitCommandList(params.fence_out)});
+ }
- auto& gpu = system.GPU();
- u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id);
- if (params.flags.increment.Value()) {
- params.fence_out.value += current_syncpoint_value;
+ if (params.flags.add_increment.Value() || params.flags.increment.Value()) {
+ const u32 increment_value = params.flags.increment.Value() ? params.fence_out.value : 0;
+ params.fence_out.value = syncpoint_manager.IncreaseSyncpoint(
+ params.fence_out.id, params.AddIncrementValue() + increment_value);
} else {
- params.fence_out.value = current_syncpoint_value;
+ params.fence_out.value = syncpoint_manager.GetSyncpointMax(params.fence_out.id);
}
+
gpu.PushGPUEntries(std::move(entries));
+ if (params.flags.add_increment.Value()) {
+ if (params.flags.suppress_wfi) {
+ gpu.PushGPUEntries(Tegra::CommandList{
+ BuildIncrementCommandList(params.fence_out, params.AddIncrementValue())});
+ } else {
+ gpu.PushGPUEntries(Tegra::CommandList{
+ BuildIncrementWithWfiCommandList(params.fence_out, params.AddIncrementValue())});
+ }
+ }
+
std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output,
- const std::vector<u8>& input2, IoctlVersion version) {
+NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input, std::vector<u8>& output,
+ bool kickoff) {
if (input.size() < sizeof(IoctlSubmitGpfifo)) {
UNIMPLEMENTED();
+ return NvResult::InvalidSize;
}
IoctlSubmitGpfifo params{};
std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
- LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address,
- params.num_entries, params.flags.raw);
-
Tegra::CommandList entries(params.num_entries);
- if (version == IoctlVersion::Version2) {
- std::memcpy(entries.data(), input2.data(),
- params.num_entries * sizeof(Tegra::CommandListHeader));
- } else {
- system.Memory().ReadBlock(params.address, entries.data(),
- params.num_entries * sizeof(Tegra::CommandListHeader));
- }
- UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0);
- UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0);
- auto& gpu = system.GPU();
- u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id);
- if (params.flags.increment.Value()) {
- params.fence_out.value += current_syncpoint_value;
+ if (kickoff) {
+ system.Memory().ReadBlock(params.address, entries.command_lists.data(),
+ params.num_entries * sizeof(Tegra::CommandListHeader));
} else {
- params.fence_out.value = current_syncpoint_value;
+ std::memcpy(entries.command_lists.data(), &input[sizeof(IoctlSubmitGpfifo)],
+ params.num_entries * sizeof(Tegra::CommandListHeader));
}
- gpu.PushGPUEntries(std::move(entries));
- std::memcpy(output.data(), &params, output.size());
- return 0;
+ return SubmitGPFIFOImpl(params, output, std::move(entries));
+}
+
+NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector<u8>& input,
+ const std::vector<u8>& input_inline,
+ std::vector<u8>& output) {
+ if (input.size() < sizeof(IoctlSubmitGpfifo)) {
+ UNIMPLEMENTED();
+ return NvResult::InvalidSize;
+ }
+ IoctlSubmitGpfifo params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlSubmitGpfifo));
+ Tegra::CommandList entries(params.num_entries);
+ std::memcpy(entries.command_lists.data(), input_inline.data(), input_inline.size());
+ return SubmitGPFIFOImpl(params, output, std::move(entries));
}
-u32 nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetWaitbase params{};
std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
params.value = 0; // Seems to be hard coded at 0
std::memcpy(output.data(), &params, output.size());
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlChannelSetTimeout params{};
std::memcpy(&params, input.data(), sizeof(IoctlChannelSetTimeout));
LOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout);
- return 0;
+ return NvResult::Success;
}
-u32 nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetTimeslice params{};
std::memcpy(&params, input.data(), sizeof(IoctlSetTimeslice));
LOG_INFO(Service_NVDRV, "called, timeslice=0x{:X}", params.timeslice);
channel_timeslice = params.timeslice;
- return 0;
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index 2ac74743f..e0298b4fe 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -11,46 +11,28 @@
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/nvdata.h"
+#include "video_core/dma_pusher.h"
+
+namespace Service::Nvidia {
+class SyncpointManager;
+}
namespace Service::Nvidia::Devices {
class nvmap;
-constexpr u32 NVGPU_IOCTL_MAGIC('H');
-constexpr u32 NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO(0x8);
-constexpr u32 NVGPU_IOCTL_CHANNEL_KICKOFF_PB(0x1b);
-
class nvhost_gpu final : public nvdevice {
public:
- explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
+ explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager);
~nvhost_gpu() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocSetNVMAPfdCommand = 0x40044801,
- IocAllocGPFIFOCommand = 0x40084805,
- IocSetClientDataCommand = 0x40084714,
- IocGetClientDataCommand = 0x80084715,
- IocZCullBind = 0xc010480b,
- IocSetErrorNotifierCommand = 0xC018480C,
- IocChannelSetPriorityCommand = 0x4004480D,
- IocEnableCommand = 0x0000480E,
- IocDisableCommand = 0x0000480F,
- IocPreemptCommand = 0x00004810,
- IocForceResetCommand = 0x00004811,
- IocEventIdControlCommand = 0x40084812,
- IocGetErrorNotificationCommand = 0xC0104817,
- IocAllocGPFIFOExCommand = 0x40204818,
- IocAllocGPFIFOEx2Command = 0xC020481A,
- IocAllocObjCtxCommand = 0xC0104809,
- IocChannelGetWaitbaseCommand = 0xC0080003,
- IocChannelSetTimeoutCommand = 0x40044803,
- IocChannelSetTimeslice = 0xC004481D,
- };
-
enum class CtxObjects : u32_le {
Ctx2D = 0x902D,
Ctx3D = 0xB197,
@@ -61,63 +43,63 @@ private:
};
struct IoctlSetNvmapFD {
- u32_le nvmap_fd;
+ s32_le nvmap_fd{};
};
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
struct IoctlChannelSetTimeout {
- u32_le timeout;
+ u32_le timeout{};
};
static_assert(sizeof(IoctlChannelSetTimeout) == 4, "IoctlChannelSetTimeout is incorrect size");
struct IoctlAllocGPFIFO {
- u32_le num_entries;
- u32_le flags;
+ u32_le num_entries{};
+ u32_le flags{};
};
static_assert(sizeof(IoctlAllocGPFIFO) == 8, "IoctlAllocGPFIFO is incorrect size");
struct IoctlClientData {
- u64_le data;
+ u64_le data{};
};
static_assert(sizeof(IoctlClientData) == 8, "IoctlClientData is incorrect size");
struct IoctlZCullBind {
- u64_le gpu_va;
- u32_le mode; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf
+ u64_le gpu_va{};
+ u32_le mode{}; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(IoctlZCullBind) == 16, "IoctlZCullBind is incorrect size");
struct IoctlSetErrorNotifier {
- u64_le offset;
- u64_le size;
- u32_le mem; // nvmap object handle
+ u64_le offset{};
+ u64_le size{};
+ u32_le mem{}; // nvmap object handle
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size");
struct IoctlChannelSetPriority {
- u32_le priority;
+ u32_le priority{};
};
static_assert(sizeof(IoctlChannelSetPriority) == 4,
"IoctlChannelSetPriority is incorrect size");
struct IoctlSetTimeslice {
- u32_le timeslice;
+ u32_le timeslice{};
};
static_assert(sizeof(IoctlSetTimeslice) == 4, "IoctlSetTimeslice is incorrect size");
struct IoctlEventIdControl {
- u32_le cmd; // 0=disable, 1=enable, 2=clear
- u32_le id;
+ u32_le cmd{}; // 0=disable, 1=enable, 2=clear
+ u32_le id{};
};
static_assert(sizeof(IoctlEventIdControl) == 8, "IoctlEventIdControl is incorrect size");
struct IoctlGetErrorNotification {
- u64_le timestamp;
- u32_le info32;
- u16_le info16;
- u16_le status; // always 0xFFFF
+ u64_le timestamp{};
+ u32_le info32{};
+ u16_le info16{};
+ u16_le status{}; // always 0xFFFF
};
static_assert(sizeof(IoctlGetErrorNotification) == 16,
"IoctlGetErrorNotification is incorrect size");
@@ -125,80 +107,89 @@ private:
static_assert(sizeof(Fence) == 8, "Fence is incorrect size");
struct IoctlAllocGpfifoEx {
- u32_le num_entries;
- u32_le flags;
- u32_le unk0;
- u32_le unk1;
- u32_le unk2;
- u32_le unk3;
- u32_le unk4;
- u32_le unk5;
+ u32_le num_entries{};
+ u32_le flags{};
+ u32_le unk0{};
+ u32_le unk1{};
+ u32_le unk2{};
+ u32_le unk3{};
+ u32_le unk4{};
+ u32_le unk5{};
};
static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size");
struct IoctlAllocGpfifoEx2 {
- u32_le num_entries; // in
- u32_le flags; // in
- u32_le unk0; // in (1 works)
- Fence fence_out; // out
- u32_le unk1; // in
- u32_le unk2; // in
- u32_le unk3; // in
+ u32_le num_entries{}; // in
+ u32_le flags{}; // in
+ u32_le unk0{}; // in (1 works)
+ Fence fence_out{}; // out
+ u32_le unk1{}; // in
+ u32_le unk2{}; // in
+ u32_le unk3{}; // in
};
static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size");
struct IoctlAllocObjCtx {
- u32_le class_num; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA,
- // 0xB06F=channel_gpfifo
- u32_le flags;
- u64_le obj_id; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported
+ u32_le class_num{}; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA,
+ // 0xB06F=channel_gpfifo
+ u32_le flags{};
+ u64_le obj_id{}; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported
};
static_assert(sizeof(IoctlAllocObjCtx) == 16, "IoctlAllocObjCtx is incorrect size");
struct IoctlSubmitGpfifo {
- u64_le address; // pointer to gpfifo entry structs
- u32_le num_entries; // number of fence objects being submitted
+ u64_le address{}; // pointer to gpfifo entry structs
+ u32_le num_entries{}; // number of fence objects being submitted
union {
u32_le raw;
BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list
BitField<1, 1, u32_le> add_increment; // append an increment to the list
- BitField<2, 1, u32_le> new_hw_format; // Mostly ignored
+ BitField<2, 1, u32_le> new_hw_format; // mostly ignored
+ BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt
BitField<8, 1, u32_le> increment; // increment the returned fence
} flags;
- Fence fence_out; // returned new fence object for others to wait on
+ Fence fence_out{}; // returned new fence object for others to wait on
+
+ u32 AddIncrementValue() const {
+ return flags.add_increment.Value() << 1;
+ }
};
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence),
"IoctlSubmitGpfifo is incorrect size");
struct IoctlGetWaitbase {
- u32 unknown; // seems to be ignored? Nintendo added this
- u32 value;
+ u32 unknown{}; // seems to be ignored? Nintendo added this
+ u32 value{};
};
static_assert(sizeof(IoctlGetWaitbase) == 8, "IoctlGetWaitbase is incorrect size");
- u32_le nvmap_fd{};
+ s32_le nvmap_fd{};
u64_le user_data{};
IoctlZCullBind zcull_params{};
u32_le channel_priority{};
u32_le channel_timeslice{};
- u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
- u32 SetClientData(const std::vector<u8>& input, std::vector<u8>& output);
- u32 GetClientData(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ZCullBind(const std::vector<u8>& input, std::vector<u8>& output);
- u32 SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output);
- u32 SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output);
- u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
- u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
- u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output);
- u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output,
- const std::vector<u8>& input2, IoctlVersion version);
- u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
- u32 ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetClientData(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetClientData(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ZCullBind(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetErrorNotifier(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetChannelPriority(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>& output,
+ Tegra::CommandList&& entries);
+ NvResult SubmitGPFIFOBase(const std::vector<u8>& input, std::vector<u8>& output,
+ bool kickoff = false);
+ NvResult SubmitGPFIFOBase(const std::vector<u8>& input, const std::vector<u8>& input_inline,
+ std::vector<u8>& output);
+ NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
std::shared_ptr<nvmap> nvmap_dev;
- u32 assigned_syncpoints{};
+ SyncpointManager& syncpoint_manager;
+ Fence channel_fence;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index bdae8b887..36970f828 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -2,39 +2,72 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <cstring>
-
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
+#include "video_core/memory_manager.h"
+#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
-nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {}
+nvhost_nvdec::nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager)
+ : nvhost_nvdec_common(system, std::move(nvmap_dev), syncpoint_manager) {}
nvhost_nvdec::~nvhost_nvdec() = default;
-u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocSetNVMAPfdCommand:
- return SetNVMAPfd(input, output);
+NvResult nvhost_nvdec::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x0:
+ switch (command.cmd) {
+ case 0x1:
+ return Submit(input, output);
+ case 0x2:
+ return GetSyncpoint(input, output);
+ case 0x3:
+ return GetWaitbase(input, output);
+ case 0x7:
+ return SetSubmitTimeout(input, output);
+ case 0x9:
+ return MapBuffer(input, output);
+ case 0xa: {
+ if (command.length == 0x1c) {
+ LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
+ Tegra::ChCommandHeaderList cmdlist(1);
+ cmdlist[0] = Tegra::ChCommandHeader{0xDEADB33F};
+ system.GPU().PushCommandBuffer(cmdlist);
+ }
+ return UnmapBuffer(input, output);
+ }
+ default:
+ break;
+ }
+ break;
+ case 'H':
+ switch (command.cmd) {
+ case 0x1:
+ return SetNVMAPfd(input);
+ default:
+ break;
+ }
+ break;
}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
- IoctlSetNvmapFD params{};
- std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
+NvResult nvhost_nvdec::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
- nvmap_fd = params.nvmap_fd;
- return 0;
+NvResult nvhost_nvdec::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
index cbdac8069..77ef53cdd 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
@@ -4,35 +4,22 @@
#pragma once
-#include <vector>
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "core/hle/service/nvdrv/devices/nvdevice.h"
+#include <memory>
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
namespace Service::Nvidia::Devices {
-class nvhost_nvdec final : public nvdevice {
+class nvhost_nvdec final : public nvhost_nvdec_common {
public:
- explicit nvhost_nvdec(Core::System& system);
+ explicit nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager);
~nvhost_nvdec() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
-
-private:
- enum class IoctlCommand : u32_le {
- IocSetNVMAPfdCommand = 0x40044801,
- };
-
- struct IoctlSetNvmapFD {
- u32_le nvmap_fd;
- };
- static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
-
- u32_le nvmap_fd{};
-
- u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
new file mode 100644
index 000000000..4898dc27a
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
@@ -0,0 +1,244 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cstring>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
+#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
+#include "core/memory.h"
+#include "video_core/memory_manager.h"
+#include "video_core/renderer_base.h"
+
+namespace Service::Nvidia::Devices {
+
+namespace {
+// Splice vectors will copy count amount of type T from the input vector into the dst vector.
+template <typename T>
+std::size_t SpliceVectors(const std::vector<u8>& input, std::vector<T>& dst, std::size_t count,
+ std::size_t offset) {
+ std::memcpy(dst.data(), input.data() + offset, count * sizeof(T));
+ offset += count * sizeof(T);
+ return offset;
+}
+
+// Write vectors will write data to the output buffer
+template <typename T>
+std::size_t WriteVectors(std::vector<u8>& dst, const std::vector<T>& src, std::size_t offset) {
+ std::memcpy(dst.data() + offset, src.data(), src.size() * sizeof(T));
+ offset += src.size() * sizeof(T);
+ return offset;
+}
+} // Anonymous namespace
+
+nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager)
+ : nvdevice(system), nvmap_dev(std::move(nvmap_dev)), syncpoint_manager(syncpoint_manager) {}
+nvhost_nvdec_common::~nvhost_nvdec_common() = default;
+
+NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) {
+ IoctlSetNvmapFD params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlSetNvmapFD));
+ LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
+
+ nvmap_fd = params.nvmap_fd;
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::Submit(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlSubmit params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlSubmit));
+ LOG_DEBUG(Service_NVDRV, "called NVDEC Submit, cmd_buffer_count={}", params.cmd_buffer_count);
+
+ // Instantiate param buffers
+ std::size_t offset = sizeof(IoctlSubmit);
+ std::vector<CommandBuffer> command_buffers(params.cmd_buffer_count);
+ std::vector<Reloc> relocs(params.relocation_count);
+ std::vector<u32> reloc_shifts(params.relocation_count);
+ std::vector<SyncptIncr> syncpt_increments(params.syncpoint_count);
+ std::vector<SyncptIncr> wait_checks(params.syncpoint_count);
+ std::vector<Fence> fences(params.fence_count);
+
+ // Splice input into their respective buffers
+ offset = SpliceVectors(input, command_buffers, params.cmd_buffer_count, offset);
+ offset = SpliceVectors(input, relocs, params.relocation_count, offset);
+ offset = SpliceVectors(input, reloc_shifts, params.relocation_count, offset);
+ offset = SpliceVectors(input, syncpt_increments, params.syncpoint_count, offset);
+ offset = SpliceVectors(input, wait_checks, params.syncpoint_count, offset);
+ offset = SpliceVectors(input, fences, params.fence_count, offset);
+
+ auto& gpu = system.GPU();
+ if (gpu.UseNvdec()) {
+ for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
+ const SyncptIncr& syncpt_incr = syncpt_increments[i];
+ fences[i].id = syncpt_incr.id;
+ fences[i].value =
+ syncpoint_manager.IncreaseSyncpoint(syncpt_incr.id, syncpt_incr.increments);
+ }
+ }
+ for (const auto& cmd_buffer : command_buffers) {
+ auto object = nvmap_dev->GetObject(cmd_buffer.memory_id);
+ ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
+ const auto map = FindBufferMap(object->dma_map_addr);
+ if (!map) {
+ LOG_ERROR(Service_NVDRV, "Tried to submit an invalid offset 0x{:X} dma 0x{:X}",
+ object->addr, object->dma_map_addr);
+ return NvResult::Success;
+ }
+ Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
+ gpu.MemoryManager().ReadBlock(map->StartAddr() + cmd_buffer.offset, cmdlist.data(),
+ cmdlist.size() * sizeof(u32));
+ gpu.PushCommandBuffer(cmdlist);
+ }
+ if (gpu.UseNvdec()) {
+
+ fences[0].value = syncpoint_manager.IncreaseSyncpoint(fences[0].id, 1);
+
+ Tegra::ChCommandHeaderList cmdlist{{(4 << 28) | fences[0].id}};
+ gpu.PushCommandBuffer(cmdlist);
+ }
+ std::memcpy(output.data(), &params, sizeof(IoctlSubmit));
+ // Some games expect command_buffers to be written back
+ offset = sizeof(IoctlSubmit);
+ offset = WriteVectors(output, command_buffers, offset);
+ offset = WriteVectors(output, relocs, offset);
+ offset = WriteVectors(output, reloc_shifts, offset);
+ offset = WriteVectors(output, syncpt_increments, offset);
+ offset = WriteVectors(output, wait_checks, offset);
+ offset = WriteVectors(output, fences, offset);
+
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlGetSyncpoint params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlGetSyncpoint));
+ LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
+
+ if (device_syncpoints[params.param] == 0 && system.GPU().UseNvdec()) {
+ device_syncpoints[params.param] = syncpoint_manager.AllocateSyncpoint();
+ }
+ params.value = device_syncpoints[params.param];
+ std::memcpy(output.data(), &params, sizeof(IoctlGetSyncpoint));
+
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlGetWaitbase params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
+ params.value = 0; // Seems to be hard coded at 0
+ std::memcpy(output.data(), &params, sizeof(IoctlGetWaitbase));
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlMapBuffer params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
+ std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
+
+ SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
+
+ auto& gpu = system.GPU();
+
+ for (auto& cmf_buff : cmd_buffer_handles) {
+ auto object{nvmap_dev->GetObject(cmf_buff.map_handle)};
+ if (!object) {
+ LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle);
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::InvalidState;
+ }
+ if (object->dma_map_addr == 0) {
+ // NVDEC and VIC memory is in the 32-bit address space
+ // MapAllocate32 will attempt to map a lower 32-bit value in the shared gpu memory space
+ const GPUVAddr low_addr = gpu.MemoryManager().MapAllocate32(object->addr, object->size);
+ object->dma_map_addr = static_cast<u32>(low_addr);
+ // Ensure that the dma_map_addr is indeed in the lower 32-bit address space.
+ ASSERT(object->dma_map_addr == low_addr);
+ }
+ if (!object->dma_map_addr) {
+ LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size);
+ } else {
+ cmf_buff.map_address = object->dma_map_addr;
+ AddBufferMap(object->dma_map_addr, object->size, object->addr,
+ object->status == nvmap::Object::Status::Allocated);
+ }
+ }
+ std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer));
+ std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
+ cmd_buffer_handles.size() * sizeof(MapBufferEntry));
+
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
+ IoctlMapBuffer params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
+ std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
+ SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
+
+ auto& gpu = system.GPU();
+
+ for (auto& cmf_buff : cmd_buffer_handles) {
+ const auto object{nvmap_dev->GetObject(cmf_buff.map_handle)};
+ if (!object) {
+ LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle);
+ std::memcpy(output.data(), &params, output.size());
+ return NvResult::InvalidState;
+ }
+ if (const auto size{RemoveBufferMap(object->dma_map_addr)}; size) {
+ gpu.MemoryManager().Unmap(object->dma_map_addr, *size);
+ } else {
+ // This occurs quite frequently, however does not seem to impact functionality
+ LOG_DEBUG(Service_NVDRV, "invalid offset=0x{:X} dma=0x{:X}", object->addr,
+ object->dma_map_addr);
+ }
+ object->dma_map_addr = 0;
+ }
+ std::memset(output.data(), 0, output.size());
+ return NvResult::Success;
+}
+
+NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ std::memcpy(&submit_timeout, input.data(), input.size());
+ LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ return NvResult::Success;
+}
+
+std::optional<nvhost_nvdec_common::BufferMap> nvhost_nvdec_common::FindBufferMap(
+ GPUVAddr gpu_addr) const {
+ const auto it = std::find_if(
+ buffer_mappings.begin(), buffer_mappings.upper_bound(gpu_addr), [&](const auto& entry) {
+ return (gpu_addr >= entry.second.StartAddr() && gpu_addr < entry.second.EndAddr());
+ });
+
+ ASSERT(it != buffer_mappings.end());
+ return it->second;
+}
+
+void nvhost_nvdec_common::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
+ bool is_allocated) {
+ buffer_mappings.insert_or_assign(gpu_addr, BufferMap{gpu_addr, size, cpu_addr, is_allocated});
+}
+
+std::optional<std::size_t> nvhost_nvdec_common::RemoveBufferMap(GPUVAddr gpu_addr) {
+ const auto iter{buffer_mappings.find(gpu_addr)};
+ if (iter == buffer_mappings.end()) {
+ return std::nullopt;
+ }
+ std::size_t size = 0;
+ if (iter->second.IsAllocated()) {
+ size = iter->second.Size();
+ }
+ buffer_mappings.erase(iter);
+ return size;
+}
+
+} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
new file mode 100644
index 000000000..4c9d4ba41
--- /dev/null
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
@@ -0,0 +1,170 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <map>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/nvdrv/devices/nvdevice.h"
+
+namespace Service::Nvidia {
+class SyncpointManager;
+
+namespace Devices {
+class nvmap;
+
+class nvhost_nvdec_common : public nvdevice {
+public:
+ explicit nvhost_nvdec_common(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager);
+ ~nvhost_nvdec_common() override;
+
+protected:
+ class BufferMap final {
+ public:
+ constexpr BufferMap() = default;
+
+ constexpr BufferMap(GPUVAddr start_addr, std::size_t size)
+ : start_addr{start_addr}, end_addr{start_addr + size} {}
+
+ constexpr BufferMap(GPUVAddr start_addr, std::size_t size, VAddr cpu_addr,
+ bool is_allocated)
+ : start_addr{start_addr}, end_addr{start_addr + size}, cpu_addr{cpu_addr},
+ is_allocated{is_allocated} {}
+
+ constexpr VAddr StartAddr() const {
+ return start_addr;
+ }
+
+ constexpr VAddr EndAddr() const {
+ return end_addr;
+ }
+
+ constexpr std::size_t Size() const {
+ return end_addr - start_addr;
+ }
+
+ constexpr VAddr CpuAddr() const {
+ return cpu_addr;
+ }
+
+ constexpr bool IsAllocated() const {
+ return is_allocated;
+ }
+
+ private:
+ GPUVAddr start_addr{};
+ GPUVAddr end_addr{};
+ VAddr cpu_addr{};
+ bool is_allocated{};
+ };
+
+ struct IoctlSetNvmapFD {
+ s32_le nvmap_fd{};
+ };
+ static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
+
+ struct IoctlSubmitCommandBuffer {
+ u32_le id{};
+ u32_le offset{};
+ u32_le count{};
+ };
+ static_assert(sizeof(IoctlSubmitCommandBuffer) == 0xC,
+ "IoctlSubmitCommandBuffer is incorrect size");
+ struct IoctlSubmit {
+ u32_le cmd_buffer_count{};
+ u32_le relocation_count{};
+ u32_le syncpoint_count{};
+ u32_le fence_count{};
+ };
+ static_assert(sizeof(IoctlSubmit) == 0x10, "IoctlSubmit has incorrect size");
+
+ struct CommandBuffer {
+ s32 memory_id{};
+ u32 offset{};
+ s32 word_count{};
+ };
+ static_assert(sizeof(CommandBuffer) == 0xC, "CommandBuffer has incorrect size");
+
+ struct Reloc {
+ s32 cmdbuffer_memory{};
+ s32 cmdbuffer_offset{};
+ s32 target{};
+ s32 target_offset{};
+ };
+ static_assert(sizeof(Reloc) == 0x10, "CommandBuffer has incorrect size");
+
+ struct SyncptIncr {
+ u32 id{};
+ u32 increments{};
+ };
+ static_assert(sizeof(SyncptIncr) == 0x8, "CommandBuffer has incorrect size");
+
+ struct Fence {
+ u32 id{};
+ u32 value{};
+ };
+ static_assert(sizeof(Fence) == 0x8, "CommandBuffer has incorrect size");
+
+ struct IoctlGetSyncpoint {
+ // Input
+ u32_le param{};
+ // Output
+ u32_le value{};
+ };
+ static_assert(sizeof(IoctlGetSyncpoint) == 8, "IocGetIdParams has wrong size");
+
+ struct IoctlGetWaitbase {
+ u32_le unknown{}; // seems to be ignored? Nintendo added this
+ u32_le value{};
+ };
+ static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size");
+
+ struct IoctlMapBuffer {
+ u32_le num_entries{};
+ u32_le data_address{}; // Ignored by the driver.
+ u32_le attach_host_ch_das{};
+ };
+ static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size");
+
+ struct IocGetIdParams {
+ // Input
+ u32_le param{};
+ // Output
+ u32_le value{};
+ };
+ static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
+
+ // Used for mapping and unmapping command buffers
+ struct MapBufferEntry {
+ u32_le map_handle{};
+ u32_le map_address{};
+ };
+ static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size");
+
+ /// Ioctl command implementations
+ NvResult SetNVMAPfd(const std::vector<u8>& input);
+ NvResult Submit(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult MapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
+
+ std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
+ void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
+ std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
+
+ s32_le nvmap_fd{};
+ u32_le submit_timeout{};
+ std::shared_ptr<nvmap> nvmap_dev;
+ SyncpointManager& syncpoint_manager;
+ std::array<u32, MaxSyncPoints> device_syncpoints{};
+ // This is expected to be ordered, therefore we must use a map, not unordered_map
+ std::map<GPUVAddr, BufferMap> buffer_mappings;
+};
+}; // namespace Devices
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
index 96e7b7dab..2d06955c0 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp
@@ -13,28 +13,44 @@ namespace Service::Nvidia::Devices {
nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {}
nvhost_nvjpg::~nvhost_nvjpg() = default;
-u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocSetNVMAPfdCommand:
- return SetNVMAPfd(input, output);
+NvResult nvhost_nvjpg::Ioctl1(Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ switch (command.group) {
+ case 'H':
+ switch (command.cmd) {
+ case 0x1:
+ return SetNVMAPfd(input, output);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_nvjpg::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvhost_nvjpg::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_nvjpg::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvhost_nvjpg::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlSetNvmapFD params{};
std::memcpy(&params, input.data(), input.size());
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
nvmap_fd = params.nvmap_fd;
- return 0;
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
index 98dcac52f..43948d18d 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h
@@ -16,23 +16,21 @@ public:
explicit nvhost_nvjpg(Core::System& system);
~nvhost_nvjpg() override;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
private:
- enum class IoctlCommand : u32_le {
- IocSetNVMAPfdCommand = 0x40044801,
- };
-
struct IoctlSetNvmapFD {
- u32_le nvmap_fd;
+ s32_le nvmap_fd{};
};
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
- u32_le nvmap_fd{};
+ s32_le nvmap_fd{};
- u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
index c695b8863..72499654c 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -2,39 +2,64 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <cstring>
-
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
+#include "video_core/memory_manager.h"
+#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
+nvhost_vic::nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager)
+ : nvhost_nvdec_common(system, std::move(nvmap_dev), syncpoint_manager) {}
-nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {}
nvhost_vic::~nvhost_vic() = default;
-u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}",
- command.raw, input.size(), output.size());
-
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::IocSetNVMAPfdCommand:
- return SetNVMAPfd(input, output);
+NvResult nvhost_vic::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x0:
+ switch (command.cmd) {
+ case 0x1:
+ return Submit(input, output);
+ case 0x2:
+ return GetSyncpoint(input, output);
+ case 0x3:
+ return GetWaitbase(input, output);
+ case 0x9:
+ return MapBuffer(input, output);
+ case 0xa:
+ return UnmapBuffer(input, output);
+ default:
+ break;
+ }
+ break;
+ case 'H':
+ switch (command.cmd) {
+ case 0x1:
+ return SetNVMAPfd(input);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
}
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
-u32 nvhost_vic::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
- IoctlSetNvmapFD params{};
- std::memcpy(&params, input.data(), input.size());
- LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
+NvResult nvhost_vic::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
- nvmap_fd = params.nvmap_fd;
- return 0;
+NvResult nvhost_vic::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
index bec32bea1..f401c61fa 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
@@ -4,35 +4,20 @@
#pragma once
-#include <vector>
-#include "common/common_types.h"
-#include "common/swap.h"
-#include "core/hle/service/nvdrv/devices/nvdevice.h"
+#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
namespace Service::Nvidia::Devices {
-class nvhost_vic final : public nvdevice {
+class nvhost_vic final : public nvhost_nvdec_common {
public:
- explicit nvhost_vic(Core::System& system);
- ~nvhost_vic() override;
-
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
-
-private:
- enum class IoctlCommand : u32_le {
- IocSetNVMAPfdCommand = 0x40044801,
- };
-
- struct IoctlSetNvmapFD {
- u32_le nvmap_fd;
- };
- static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
-
- u32_le nvmap_fd{};
-
- u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
+ explicit nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
+ SyncpointManager& syncpoint_manager);
+ ~nvhost_vic();
+
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
};
-
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index 9436e16ad..4015a2740 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -11,13 +11,6 @@
namespace Service::Nvidia::Devices {
-namespace NvErrCodes {
-enum {
- OperationNotPermitted = -1,
- InvalidValue = -22,
-};
-}
-
nvmap::nvmap(Core::System& system) : nvdevice(system) {
// Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to
// represent this.
@@ -26,6 +19,46 @@ nvmap::nvmap(Core::System& system) : nvdevice(system) {
nvmap::~nvmap() = default;
+NvResult nvmap::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
+ switch (command.group) {
+ case 0x1:
+ switch (command.cmd) {
+ case 0x1:
+ return IocCreate(input, output);
+ case 0x3:
+ return IocFromId(input, output);
+ case 0x4:
+ return IocAlloc(input, output);
+ case 0x5:
+ return IocFree(input, output);
+ case 0x9:
+ return IocParam(input, output);
+ case 0xe:
+ return IocGetId(input, output);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvmap::Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
+NvResult nvmap::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) {
+ UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
+ return NvResult::NotImplemented;
+}
+
VAddr nvmap::GetObjectAddress(u32 handle) const {
auto object = GetObject(handle);
ASSERT(object);
@@ -33,28 +66,6 @@ VAddr nvmap::GetObjectAddress(u32 handle) const {
return object->addr;
}
-u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- switch (static_cast<IoctlCommand>(command.raw)) {
- case IoctlCommand::Create:
- return IocCreate(input, output);
- case IoctlCommand::Alloc:
- return IocAlloc(input, output);
- case IoctlCommand::GetId:
- return IocGetId(input, output);
- case IoctlCommand::FromId:
- return IocFromId(input, output);
- case IoctlCommand::Param:
- return IocParam(input, output);
- case IoctlCommand::Free:
- return IocFree(input, output);
- }
-
- UNIMPLEMENTED_MSG("Unimplemented ioctl");
- return 0;
-}
-
u32 nvmap::CreateObject(u32 size) {
// Create a new nvmap object and obtain a handle to it.
auto object = std::make_shared<Object>();
@@ -70,35 +81,35 @@ u32 nvmap::CreateObject(u32 size) {
return handle;
}
-u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
IocCreateParams params;
std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
if (!params.size) {
LOG_ERROR(Service_NVDRV, "Size is 0");
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
params.handle = CreateObject(params.size);
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
IocAllocParams params;
std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
if (!params.handle) {
LOG_ERROR(Service_NVDRV, "Handle is 0");
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
if ((params.align - 1) & params.align) {
LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
const u32 min_alignment = 0x1000;
@@ -109,12 +120,12 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
auto object = GetObject(params.handle);
if (!object) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
if (object->status == Object::Status::Allocated) {
LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ return NvResult::InsufficientMemory;
}
object->flags = params.flags;
@@ -124,10 +135,10 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
object->status = Object::Status::Allocated;
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
@@ -135,22 +146,22 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
if (!params.handle) {
LOG_ERROR(Service_NVDRV, "Handle is zero");
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
auto object = GetObject(params.handle);
if (!object) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ return NvResult::BadValue;
}
params.id = object->id;
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
IocFromIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
@@ -160,13 +171,13 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
[&](const auto& entry) { return entry.second->id == params.id; });
if (itr == handles.end()) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
auto& object = itr->second;
if (object->status != Object::Status::Allocated) {
LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
itr->second->refcount++;
@@ -175,10 +186,10 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
params.handle = itr->first;
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
enum class ParamTypes { Size = 1, Alignment = 2, Base = 3, Heap = 4, Kind = 5, Compr = 6 };
IocParamParams params;
@@ -189,12 +200,12 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
auto object = GetObject(params.handle);
if (!object) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
if (object->status != Object::Status::Allocated) {
LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ return NvResult::BadValue;
}
switch (static_cast<ParamTypes>(params.param)) {
@@ -216,10 +227,10 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
}
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
-u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
+NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
// TODO(Subv): These flags are unconfirmed.
enum FreeFlags {
Freed = 0,
@@ -234,14 +245,14 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
auto itr = handles.find(params.handle);
if (itr == handles.end()) {
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
if (!itr->second->refcount) {
LOG_ERROR(
Service_NVDRV,
"There is no references to this object. The object is already freed. handle={:08X}",
params.handle);
- return static_cast<u32>(NvErrCodes::InvalidValue);
+ return NvResult::BadValue;
}
itr->second->refcount--;
@@ -261,7 +272,7 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
handles.erase(params.handle);
std::memcpy(output.data(), &params, sizeof(params));
- return 0;
+ return NvResult::Success;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 84624be00..4484bd79f 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -19,13 +19,15 @@ public:
explicit nvmap(Core::System& system);
~nvmap() override;
+ NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
+ NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) override;
+ NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
+ std::vector<u8>& inline_output) override;
+
/// Returns the allocated address of an nvmap object given its handle.
VAddr GetObjectAddress(u32 handle) const;
- u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) override;
-
/// Represents an nvmap object.
struct Object {
enum class Status { Created, Allocated };
@@ -37,6 +39,7 @@ public:
VAddr addr;
Status status;
u32 refcount;
+ u32 dma_map_addr;
};
std::shared_ptr<Object> GetObject(u32 handle) const {
@@ -57,76 +60,68 @@ private:
/// Mapping of currently allocated handles to the objects they represent.
std::unordered_map<u32, std::shared_ptr<Object>> handles;
- enum class IoctlCommand : u32 {
- Create = 0xC0080101,
- FromId = 0xC0080103,
- Alloc = 0xC0200104,
- Free = 0xC0180105,
- Param = 0xC00C0109,
- GetId = 0xC008010E,
- };
struct IocCreateParams {
// Input
- u32_le size;
+ u32_le size{};
// Output
- u32_le handle;
+ u32_le handle{};
};
static_assert(sizeof(IocCreateParams) == 8, "IocCreateParams has wrong size");
struct IocFromIdParams {
// Input
- u32_le id;
+ u32_le id{};
// Output
- u32_le handle;
+ u32_le handle{};
};
static_assert(sizeof(IocFromIdParams) == 8, "IocFromIdParams has wrong size");
struct IocAllocParams {
// Input
- u32_le handle;
- u32_le heap_mask;
- u32_le flags;
- u32_le align;
- u8 kind;
+ u32_le handle{};
+ u32_le heap_mask{};
+ u32_le flags{};
+ u32_le align{};
+ u8 kind{};
INSERT_PADDING_BYTES(7);
- u64_le addr;
+ u64_le addr{};
};
static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
struct IocFreeParams {
- u32_le handle;
+ u32_le handle{};
INSERT_PADDING_BYTES(4);
- u64_le address;
- u32_le size;
- u32_le flags;
+ u64_le address{};
+ u32_le size{};
+ u32_le flags{};
};
static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
struct IocParamParams {
// Input
- u32_le handle;
- u32_le param;
+ u32_le handle{};
+ u32_le param{};
// Output
- u32_le result;
+ u32_le result{};
};
static_assert(sizeof(IocParamParams) == 12, "IocParamParams has wrong size");
struct IocGetIdParams {
// Output
- u32_le id;
+ u32_le id{};
// Input
- u32_le handle;
+ u32_le handle{};
};
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
u32 CreateObject(u32 size);
- u32 IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocParam(const std::vector<u8>& input, std::vector<u8>& output);
- u32 IocFree(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output);
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index 88fbfa9b0..2e1150867 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -6,10 +6,10 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/interface.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
@@ -23,124 +23,167 @@ void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
+
const auto& buffer = ctx.ReadBuffer();
- std::string device_name(buffer.begin(), buffer.end());
+ const std::string device_name(buffer.begin(), buffer.end());
+ DeviceFD fd = nvdrv->Open(device_name);
- u32 fd = nvdrv->Open(device_name);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(fd);
- rb.Push<u32>(0);
+ rb.Push<DeviceFD>(fd);
+ rb.PushEnum(fd != INVALID_NVDRV_FD ? NvResult::Success : NvResult::FileOperationFailed);
+}
+
+void NVDRV::ServiceError(Kernel::HLERequestContext& ctx, NvResult result) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(result);
}
-void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) {
+void NVDRV::Ioctl1(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- u32 fd = rp.Pop<u32>();
- u32 command = rp.Pop<u32>();
-
- /// Ioctl 3 has 2 outputs, first in the input params, second is the result
- std::vector<u8> output(ctx.GetWriteBufferSize(0));
- std::vector<u8> output2;
- if (version == IoctlVersion::Version3) {
- output2.resize((ctx.GetWriteBufferSize(1)));
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto command = rp.PopRaw<Ioctl>();
+ LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
+
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
}
- /// Ioctl2 has 2 inputs. It's used to pass data directly instead of providing a pointer.
- /// KickOfPB uses this
- auto input = ctx.ReadBuffer(0);
+ // Check device
+ std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
+ const auto input_buffer = ctx.ReadBuffer(0);
- std::vector<u8> input2;
- if (version == IoctlVersion::Version2) {
- input2 = ctx.ReadBuffer(1);
+ const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer);
+ if (command.is_out != 0) {
+ ctx.WriteBuffer(output_buffer);
}
- IoctlCtrl ctrl{};
-
- u32 result = nvdrv->Ioctl(fd, command, input, input2, output, output2, ctrl, version);
-
- if (ctrl.must_delay) {
- ctrl.fresh_call = false;
- ctx.SleepClientThread(
- "NVServices::DelayedResponse", ctrl.timeout,
- [=, this](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx_,
- Kernel::ThreadWakeupReason reason) {
- IoctlCtrl ctrl2{ctrl};
- std::vector<u8> tmp_output = output;
- std::vector<u8> tmp_output2 = output2;
- const u32 ioctl_result = nvdrv->Ioctl(fd, command, input, input2, tmp_output,
- tmp_output2, ctrl2, version);
- ctx_.WriteBuffer(tmp_output, 0);
- if (version == IoctlVersion::Version3) {
- ctx_.WriteBuffer(tmp_output2, 1);
- }
- IPC::ResponseBuilder rb{ctx_, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push(ioctl_result);
- },
- nvdrv->GetEventWriteable(ctrl.event_id));
- } else {
- ctx.WriteBuffer(output);
- if (version == IoctlVersion::Version3) {
- ctx.WriteBuffer(output2, 1);
- }
- }
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(result);
-}
-
-void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
- IoctlBase(ctx, IoctlVersion::Version1);
+ rb.PushEnum(nv_result);
}
void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
- IoctlBase(ctx, IoctlVersion::Version2);
+ IPC::RequestParser rp{ctx};
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto command = rp.PopRaw<Ioctl>();
+ LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
+
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
+
+ const auto input_buffer = ctx.ReadBuffer(0);
+ const auto input_inlined_buffer = ctx.ReadBuffer(1);
+ std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
+
+ const auto nv_result =
+ nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer);
+ if (command.is_out != 0) {
+ ctx.WriteBuffer(output_buffer);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(nv_result);
}
void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NVDRV, "called");
- IoctlBase(ctx, IoctlVersion::Version3);
+ IPC::RequestParser rp{ctx};
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto command = rp.PopRaw<Ioctl>();
+ LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);
+
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
+
+ const auto input_buffer = ctx.ReadBuffer(0);
+ std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
+ std::vector<u8> output_buffer_inline(ctx.GetWriteBufferSize(1));
+
+ const auto nv_result =
+ nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, output_buffer_inline);
+ if (command.is_out != 0) {
+ ctx.WriteBuffer(output_buffer, 0);
+ ctx.WriteBuffer(output_buffer_inline, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(nv_result);
}
void NVDRV::Close(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
- IPC::RequestParser rp{ctx};
- u32 fd = rp.Pop<u32>();
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
- auto result = nvdrv->Close(fd);
+ IPC::RequestParser rp{ctx};
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto result = nvdrv->Close(fd);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(result);
}
void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ is_initialized = true;
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
+ rb.PushEnum(NvResult::Success);
}
void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- u32 fd = rp.Pop<u32>();
- // TODO(Blinkhawk): Figure the meaning of the flag at bit 16
- u32 event_id = rp.Pop<u32>() & 0x000000FF;
+ const auto fd = rp.Pop<DeviceFD>();
+ const auto event_id = rp.Pop<u32>() & 0x00FF;
LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
- IPC::ResponseBuilder rb{ctx, 3, 1};
- rb.Push(RESULT_SUCCESS);
+ if (!is_initialized) {
+ ServiceError(ctx, NvResult::NotInitialized);
+ LOG_ERROR(Service_NVDRV, "NvServices is not initalized!");
+ return;
+ }
+
+ const auto nv_result = nvdrv->VerifyFD(fd);
+ if (nv_result != NvResult::Success) {
+ LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd);
+ ServiceError(ctx, nv_result);
+ return;
+ }
+
if (event_id < MaxNvEvents) {
+ IPC::ResponseBuilder rb{ctx, 3, 1};
+ rb.Push(RESULT_SUCCESS);
auto event = nvdrv->GetEvent(event_id);
event->Clear();
rb.PushCopyObjects(event);
- rb.Push<u32>(NvResult::Success);
+ rb.PushEnum(NvResult::Success);
} else {
- rb.Push<u32>(0);
- rb.Push<u32>(NvResult::BadParameter);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(NvResult::BadParameter);
}
}
@@ -151,7 +194,7 @@ void NVDRV::SetAruid(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
+ rb.PushEnum(NvResult::Success);
}
void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx) {
@@ -164,8 +207,9 @@ void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ct
void NVDRV::GetStatus(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(NvResult::Success);
}
void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) {
@@ -177,11 +221,11 @@ void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
-NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
- : ServiceFramework(name), nvdrv(std::move(nvdrv)) {
+NVDRV::NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name)
+ : ServiceFramework{system_, name}, nvdrv{std::move(nvdrv_)} {
static const FunctionInfo functions[] = {
{0, &NVDRV::Open, "Open"},
- {1, &NVDRV::Ioctl, "Ioctl"},
+ {1, &NVDRV::Ioctl1, "Ioctl"},
{2, &NVDRV::Close, "Close"},
{3, &NVDRV::Initialize, "Initialize"},
{4, &NVDRV::QueryEvent, "QueryEvent"},
diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h
index 72e17a728..0e764c53f 100644
--- a/src/core/hle/service/nvdrv/interface.h
+++ b/src/core/hle/service/nvdrv/interface.h
@@ -9,21 +9,21 @@
#include "core/hle/service/service.h"
namespace Kernel {
-class WritableEvent;
+class KWritableEvent;
}
namespace Service::Nvidia {
class NVDRV final : public ServiceFramework<NVDRV> {
public:
- NVDRV(std::shared_ptr<Module> nvdrv, const char* name);
+ explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
~NVDRV() override;
- void SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value);
+ void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value);
private:
void Open(Kernel::HLERequestContext& ctx);
- void Ioctl(Kernel::HLERequestContext& ctx);
+ void Ioctl1(Kernel::HLERequestContext& ctx);
void Ioctl2(Kernel::HLERequestContext& ctx);
void Ioctl3(Kernel::HLERequestContext& ctx);
void Close(Kernel::HLERequestContext& ctx);
@@ -33,11 +33,13 @@ private:
void SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx);
void GetStatus(Kernel::HLERequestContext& ctx);
void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx);
- void IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version);
+
+ void ServiceError(Kernel::HLERequestContext& ctx, NvResult result);
std::shared_ptr<Module> nvdrv;
u64 pid{};
+ bool is_initialized{};
};
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h
index 529b03471..3294bc0e7 100644
--- a/src/core/hle/service/nvdrv/nvdata.h
+++ b/src/core/hle/service/nvdrv/nvdata.h
@@ -1,12 +1,16 @@
#pragma once
#include <array>
+#include "common/bit_field.h"
#include "common/common_types.h"
namespace Service::Nvidia {
constexpr u32 MaxSyncPoints = 192;
constexpr u32 MaxNvEvents = 64;
+using DeviceFD = s32;
+
+constexpr DeviceFD INVALID_NVDRV_FD = -1;
struct Fence {
s32 id;
@@ -20,11 +24,61 @@ struct MultiFence {
std::array<Fence, 4> fences;
};
-enum NvResult : u32 {
- Success = 0,
- BadParameter = 4,
- Timeout = 5,
- ResourceError = 15,
+enum class NvResult : u32 {
+ Success = 0x0,
+ NotImplemented = 0x1,
+ NotSupported = 0x2,
+ NotInitialized = 0x3,
+ BadParameter = 0x4,
+ Timeout = 0x5,
+ InsufficientMemory = 0x6,
+ ReadOnlyAttribute = 0x7,
+ InvalidState = 0x8,
+ InvalidAddress = 0x9,
+ InvalidSize = 0xA,
+ BadValue = 0xB,
+ AlreadyAllocated = 0xD,
+ Busy = 0xE,
+ ResourceError = 0xF,
+ CountMismatch = 0x10,
+ OverFlow = 0x11,
+ InsufficientTransferMemory = 0x1000,
+ InsufficientVideoMemory = 0x10000,
+ BadSurfaceColorScheme = 0x10001,
+ InvalidSurface = 0x10002,
+ SurfaceNotSupported = 0x10003,
+ DispInitFailed = 0x20000,
+ DispAlreadyAttached = 0x20001,
+ DispTooManyDisplays = 0x20002,
+ DispNoDisplaysAttached = 0x20003,
+ DispModeNotSupported = 0x20004,
+ DispNotFound = 0x20005,
+ DispAttachDissallowed = 0x20006,
+ DispTypeNotSupported = 0x20007,
+ DispAuthenticationFailed = 0x20008,
+ DispNotAttached = 0x20009,
+ DispSamePwrState = 0x2000A,
+ DispEdidFailure = 0x2000B,
+ DispDsiReadAckError = 0x2000C,
+ DispDsiReadInvalidResp = 0x2000D,
+ FileWriteFailed = 0x30000,
+ FileReadFailed = 0x30001,
+ EndOfFile = 0x30002,
+ FileOperationFailed = 0x30003,
+ DirOperationFailed = 0x30004,
+ EndOfDirList = 0x30005,
+ ConfigVarNotFound = 0x30006,
+ InvalidConfigVar = 0x30007,
+ LibraryNotFound = 0x30008,
+ SymbolNotFound = 0x30009,
+ MemoryMapFailed = 0x3000A,
+ IoctlFailed = 0x3000F,
+ AccessDenied = 0x30010,
+ DeviceNotFound = 0x30011,
+ KernelDriverNotFound = 0x30012,
+ FileNotFound = 0x30013,
+ PathAlreadyExists = 0x30014,
+ ModuleNotPresent = 0xA000E,
};
enum class EventState {
@@ -34,21 +88,13 @@ enum class EventState {
Busy = 3,
};
-enum class IoctlVersion : u32 {
- Version1,
- Version2,
- Version3,
-};
-
-struct IoctlCtrl {
- // First call done to the servioce for services that call itself again after a call.
- bool fresh_call{true};
- // Tells the Ioctl Wrapper that it must delay the IPC response and send the thread to sleep
- bool must_delay{};
- // Timeout for the delay
- s64 timeout{};
- // NV Event Id
- s32 event_id{-1};
+union Ioctl {
+ u32_le raw;
+ BitField<0, 8, u32> cmd;
+ BitField<8, 8, u32> group;
+ BitField<16, 14, u32> length;
+ BitField<30, 1, u32> is_in;
+ BitField<31, 1, u32> is_out;
};
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index 197c77db0..abba80112 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -5,9 +5,11 @@
#include <utility>
#include <fmt/format.h>
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
@@ -21,6 +23,7 @@
#include "core/hle/service/nvdrv/interface.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvmemp.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/hle/service/nvflinger/nvflinger.h"
namespace Service::Nvidia {
@@ -28,66 +31,136 @@ namespace Service::Nvidia {
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
Core::System& system) {
auto module_ = std::make_shared<Module>(system);
- std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager);
- std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager);
- std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager);
- std::make_shared<NVDRV>(module_, "nvdrv:t")->InstallAsService(service_manager);
- std::make_shared<NVMEMP>()->InstallAsService(service_manager);
+ std::make_shared<NVDRV>(system, module_, "nvdrv")->InstallAsService(service_manager);
+ std::make_shared<NVDRV>(system, module_, "nvdrv:a")->InstallAsService(service_manager);
+ std::make_shared<NVDRV>(system, module_, "nvdrv:s")->InstallAsService(service_manager);
+ std::make_shared<NVDRV>(system, module_, "nvdrv:t")->InstallAsService(service_manager);
+ std::make_shared<NVMEMP>(system)->InstallAsService(service_manager);
nvflinger.SetNVDrvInstance(module_);
}
-Module::Module(Core::System& system) {
+Module::Module(Core::System& system) : syncpoint_manager{system.GPU()} {
auto& kernel = system.Kernel();
for (u32 i = 0; i < MaxNvEvents; i++) {
std::string event_label = fmt::format("NVDRV::NvEvent_{}", i);
- events_interface.events[i] = Kernel::WritableEvent::CreateEventPair(kernel, event_label);
+ events_interface.events[i] = {Kernel::KEvent::Create(kernel, std::move(event_label))};
+ events_interface.events[i].event->Initialize();
events_interface.status[i] = EventState::Free;
events_interface.registered[i] = false;
}
auto nvmap_dev = std::make_shared<Devices::nvmap>(system);
devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev);
- devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev);
+ devices["/dev/nvhost-gpu"] =
+ std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev, syncpoint_manager);
devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system);
devices["/dev/nvmap"] = nvmap_dev;
devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev);
- devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(system, events_interface);
- devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(system);
+ devices["/dev/nvhost-ctrl"] =
+ std::make_shared<Devices::nvhost_ctrl>(system, events_interface, syncpoint_manager);
+ devices["/dev/nvhost-nvdec"] =
+ std::make_shared<Devices::nvhost_nvdec>(system, nvmap_dev, syncpoint_manager);
devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system);
- devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(system);
+ devices["/dev/nvhost-vic"] =
+ std::make_shared<Devices::nvhost_vic>(system, nvmap_dev, syncpoint_manager);
}
Module::~Module() = default;
-u32 Module::Open(const std::string& device_name) {
- ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device {}",
- device_name);
+NvResult Module::VerifyFD(DeviceFD fd) const {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
+
+ if (open_files.find(fd) == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ return NvResult::Success;
+}
+
+DeviceFD Module::Open(const std::string& device_name) {
+ if (devices.find(device_name) == devices.end()) {
+ LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name);
+ return INVALID_NVDRV_FD;
+ }
auto device = devices[device_name];
- const u32 fd = next_fd++;
+ const DeviceFD fd = next_fd++;
open_files[fd] = std::move(device);
return fd;
}
-u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version) {
- auto itr = open_files.find(fd);
- ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
+NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
- auto& device = itr->second;
- return device->ioctl({command}, input, input2, output, output2, ctrl, version);
+ const auto itr = open_files.find(fd);
+
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ return itr->second->Ioctl1(command, input, output);
}
-ResultCode Module::Close(u32 fd) {
- auto itr = open_files.find(fd);
- ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device");
+NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
+
+ const auto itr = open_files.find(fd);
+
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ return itr->second->Ioctl2(command, input, inline_input, output);
+}
+
+NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output, std::vector<u8>& inline_output) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
+
+ const auto itr = open_files.find(fd);
+
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
+
+ return itr->second->Ioctl3(command, input, output, inline_output);
+}
+
+NvResult Module::Close(DeviceFD fd) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
+ }
+
+ const auto itr = open_files.find(fd);
+
+ if (itr == open_files.end()) {
+ LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
+ return NvResult::NotImplemented;
+ }
open_files.erase(itr);
- // TODO(flerovium): return correct result code if operation failed.
- return RESULT_SUCCESS;
+ return NvResult::Success;
}
void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
@@ -95,17 +168,17 @@ void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
if (events_interface.assigned_syncpt[i] == syncpoint_id &&
events_interface.assigned_value[i] == value) {
events_interface.LiberateEvent(i);
- events_interface.events[i].writable->Signal();
+ events_interface.events[i].event->GetWritableEvent()->Signal();
}
}
}
-std::shared_ptr<Kernel::ReadableEvent> Module::GetEvent(const u32 event_id) const {
- return events_interface.events[event_id].readable;
+std::shared_ptr<Kernel::KReadableEvent> Module::GetEvent(const u32 event_id) const {
+ return events_interface.events[event_id].event->GetReadableEvent();
}
-std::shared_ptr<Kernel::WritableEvent> Module::GetEventWriteable(const u32 event_id) const {
- return events_interface.events[event_id].writable;
+std::shared_ptr<Kernel::KWritableEvent> Module::GetEventWriteable(const u32 event_id) const {
+ return events_interface.events[event_id].event->GetWritableEvent();
}
} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index d7a1bef91..53719aadd 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -7,30 +7,43 @@
#include <memory>
#include <unordered_map>
#include <vector>
+
#include "common/common_types.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
+namespace Kernel {
+class KEvent;
+}
+
namespace Service::NVFlinger {
class NVFlinger;
}
namespace Service::Nvidia {
+class SyncpointManager;
+
namespace Devices {
class nvdevice;
}
+/// Represents an Nvidia event
+struct NvEvent {
+ std::shared_ptr<Kernel::KEvent> event;
+ Fence fence{};
+};
+
struct EventInterface {
// Mask representing currently busy events
u64 events_mask{};
// Each kernel event associated to an NV event
- std::array<Kernel::EventPair, MaxNvEvents> events;
+ std::array<NvEvent, MaxNvEvents> events;
// The status of the current NVEvent
std::array<EventState, MaxNvEvents> status{};
// Tells if an NVEvent is registered or not
@@ -54,7 +67,7 @@ struct EventInterface {
}
mask = mask >> 1;
}
- return {};
+ return std::nullopt;
}
void SetEventStatus(const u32 event_id, EventState new_status) {
EventState old_status = status[event_id];
@@ -91,7 +104,7 @@ struct EventInterface {
class Module final {
public:
- Module(Core::System& system);
+ explicit Module(Core::System& system_);
~Module();
/// Returns a pointer to one of the available devices, identified by its name.
@@ -103,27 +116,39 @@ public:
return std::static_pointer_cast<T>(itr->second);
}
+ NvResult VerifyFD(DeviceFD fd) const;
+
/// Opens a device node and returns a file descriptor to it.
- u32 Open(const std::string& device_name);
+ DeviceFD Open(const std::string& device_name);
+
/// Sends an ioctl command to the specified file descriptor.
- u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2,
- std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl,
- IoctlVersion version);
+ NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output);
+
+ NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ const std::vector<u8>& inline_input, std::vector<u8>& output);
+
+ NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
+ std::vector<u8>& output, std::vector<u8>& inline_output);
+
/// Closes a device file descriptor and returns operation success.
- ResultCode Close(u32 fd);
+ NvResult Close(DeviceFD fd);
void SignalSyncpt(const u32 syncpoint_id, const u32 value);
- std::shared_ptr<Kernel::ReadableEvent> GetEvent(u32 event_id) const;
+ std::shared_ptr<Kernel::KReadableEvent> GetEvent(u32 event_id) const;
- std::shared_ptr<Kernel::WritableEvent> GetEventWriteable(u32 event_id) const;
+ std::shared_ptr<Kernel::KWritableEvent> GetEventWriteable(u32 event_id) const;
private:
+ /// Manages syncpoints on the host
+ SyncpointManager syncpoint_manager;
+
/// Id to use for the next open file descriptor.
- u32 next_fd = 1;
+ DeviceFD next_fd = 1;
/// Mapping of file descriptors to the devices they reference.
- std::unordered_map<u32, std::shared_ptr<Devices::nvdevice>> open_files;
+ std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>> open_files;
/// Mapping of device node names to their implementation.
std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices;
diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp
index 73b37e805..331c02243 100644
--- a/src/core/hle/service/nvdrv/nvmemp.cpp
+++ b/src/core/hle/service/nvdrv/nvmemp.cpp
@@ -8,7 +8,7 @@
namespace Service::Nvidia {
-NVMEMP::NVMEMP() : ServiceFramework("nvmemp") {
+NVMEMP::NVMEMP(Core::System& system_) : ServiceFramework{system_, "nvmemp"} {
static const FunctionInfo functions[] = {
{0, &NVMEMP::Open, "Open"},
{1, &NVMEMP::GetAruid, "GetAruid"},
diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h
index c453ee4db..724c27ef9 100644
--- a/src/core/hle/service/nvdrv/nvmemp.h
+++ b/src/core/hle/service/nvdrv/nvmemp.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Nvidia {
class NVMEMP final : public ServiceFramework<NVMEMP> {
public:
- NVMEMP();
+ explicit NVMEMP(Core::System& system_);
~NVMEMP() override;
private:
diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/syncpoint_manager.cpp
new file mode 100644
index 000000000..0151a03b7
--- /dev/null
+++ b/src/core/hle/service/nvdrv/syncpoint_manager.cpp
@@ -0,0 +1,39 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/hle/service/nvdrv/syncpoint_manager.h"
+#include "video_core/gpu.h"
+
+namespace Service::Nvidia {
+
+SyncpointManager::SyncpointManager(Tegra::GPU& gpu) : gpu{gpu} {}
+
+SyncpointManager::~SyncpointManager() = default;
+
+u32 SyncpointManager::RefreshSyncpoint(u32 syncpoint_id) {
+ syncpoints[syncpoint_id].min = gpu.GetSyncpointValue(syncpoint_id);
+ return GetSyncpointMin(syncpoint_id);
+}
+
+u32 SyncpointManager::AllocateSyncpoint() {
+ for (u32 syncpoint_id = 1; syncpoint_id < MaxSyncPoints; syncpoint_id++) {
+ if (!syncpoints[syncpoint_id].is_allocated) {
+ syncpoints[syncpoint_id].is_allocated = true;
+ return syncpoint_id;
+ }
+ }
+ UNREACHABLE_MSG("No more available syncpoints!");
+ return {};
+}
+
+u32 SyncpointManager::IncreaseSyncpoint(u32 syncpoint_id, u32 value) {
+ for (u32 index = 0; index < value; ++index) {
+ syncpoints[syncpoint_id].max.fetch_add(1, std::memory_order_relaxed);
+ }
+
+ return GetSyncpointMax(syncpoint_id);
+}
+
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.h b/src/core/hle/service/nvdrv/syncpoint_manager.h
new file mode 100644
index 000000000..d395c5d0b
--- /dev/null
+++ b/src/core/hle/service/nvdrv/syncpoint_manager.h
@@ -0,0 +1,85 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <atomic>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra {
+class GPU;
+}
+
+namespace Service::Nvidia {
+
+class SyncpointManager final {
+public:
+ explicit SyncpointManager(Tegra::GPU& gpu);
+ ~SyncpointManager();
+
+ /**
+ * Returns true if the specified syncpoint is expired for the given value.
+ * @param syncpoint_id Syncpoint ID to check.
+ * @param value Value to check against the specified syncpoint.
+ * @returns True if the specified syncpoint is expired for the given value, otherwise False.
+ */
+ bool IsSyncpointExpired(u32 syncpoint_id, u32 value) const {
+ return (GetSyncpointMax(syncpoint_id) - value) >= (GetSyncpointMin(syncpoint_id) - value);
+ }
+
+ /**
+ * Gets the lower bound for the specified syncpoint.
+ * @param syncpoint_id Syncpoint ID to get the lower bound for.
+ * @returns The lower bound for the specified syncpoint.
+ */
+ u32 GetSyncpointMin(u32 syncpoint_id) const {
+ return syncpoints.at(syncpoint_id).min.load(std::memory_order_relaxed);
+ }
+
+ /**
+ * Gets the uper bound for the specified syncpoint.
+ * @param syncpoint_id Syncpoint ID to get the upper bound for.
+ * @returns The upper bound for the specified syncpoint.
+ */
+ u32 GetSyncpointMax(u32 syncpoint_id) const {
+ return syncpoints.at(syncpoint_id).max.load(std::memory_order_relaxed);
+ }
+
+ /**
+ * Refreshes the minimum value for the specified syncpoint.
+ * @param syncpoint_id Syncpoint ID to be refreshed.
+ * @returns The new syncpoint minimum value.
+ */
+ u32 RefreshSyncpoint(u32 syncpoint_id);
+
+ /**
+ * Allocates a new syncoint.
+ * @returns The syncpoint ID for the newly allocated syncpoint.
+ */
+ u32 AllocateSyncpoint();
+
+ /**
+ * Increases the maximum value for the specified syncpoint.
+ * @param syncpoint_id Syncpoint ID to be increased.
+ * @param value Value to increase the specified syncpoint by.
+ * @returns The new syncpoint maximum value.
+ */
+ u32 IncreaseSyncpoint(u32 syncpoint_id, u32 value);
+
+private:
+ struct Syncpoint {
+ std::atomic<u32> min;
+ std::atomic<u32> max;
+ std::atomic<bool> is_allocated;
+ };
+
+ std::array<Syncpoint, MaxSyncPoints> syncpoints{};
+
+ Tegra::GPU& gpu;
+};
+
+} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index 637b310d7..7842a82ed 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -7,151 +7,194 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
namespace Service::NVFlinger {
BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id, u64 layer_id)
: id(id), layer_id(layer_id) {
- buffer_wait_event = Kernel::WritableEvent::CreateEventPair(kernel, "BufferQueue NativeHandle");
+ buffer_wait_event = Kernel::KEvent::Create(kernel, "BufferQueue:WaitEvent");
+ buffer_wait_event->Initialize();
}
BufferQueue::~BufferQueue() = default;
void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
+ ASSERT(slot < buffer_slots);
LOG_WARNING(Service, "Adding graphics buffer {}", slot);
- free_buffers.push_back(slot);
- queue.push_back({
+ {
+ std::unique_lock lock{free_buffers_mutex};
+ free_buffers.push_back(slot);
+ }
+ free_buffers_condition.notify_one();
+
+ buffers[slot] = {
.slot = slot,
.status = Buffer::Status::Free,
.igbp_buffer = igbp_buffer,
- });
+ .transform = {},
+ .crop_rect = {},
+ .swap_interval = 0,
+ .multi_fence = {},
+ };
- buffer_wait_event.writable->Signal();
+ buffer_wait_event->GetWritableEvent()->Signal();
}
std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width,
u32 height) {
+ // Wait for first request before trying to dequeue
+ {
+ std::unique_lock lock{free_buffers_mutex};
+ free_buffers_condition.wait(lock, [this] { return !free_buffers.empty() || !is_connect; });
+ }
- if (free_buffers.empty()) {
+ if (!is_connect) {
+ // Buffer was disconnected while the thread was blocked, this is most likely due to
+ // emulation being stopped
return std::nullopt;
}
+ std::unique_lock lock{free_buffers_mutex};
+
auto f_itr = free_buffers.begin();
- auto itr = queue.end();
+ auto slot = buffers.size();
while (f_itr != free_buffers.end()) {
- auto slot = *f_itr;
- itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
- // Only consider free buffers. Buffers become free once again after they've been
- // Acquired and Released by the compositor, see the NVFlinger::Compose method.
- if (buffer.status != Buffer::Status::Free) {
- return false;
- }
-
- if (buffer.slot != slot) {
- return false;
- }
-
- // Make sure that the parameters match.
- return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height;
- });
-
- if (itr != queue.end()) {
+ const Buffer& buffer = buffers[*f_itr];
+ if (buffer.status == Buffer::Status::Free && buffer.igbp_buffer.width == width &&
+ buffer.igbp_buffer.height == height) {
+ slot = *f_itr;
free_buffers.erase(f_itr);
break;
}
++f_itr;
}
-
- if (itr == queue.end()) {
+ if (slot == buffers.size()) {
return std::nullopt;
}
-
- itr->status = Buffer::Status::Dequeued;
- return {{itr->slot, &itr->multi_fence}};
+ buffers[slot].status = Buffer::Status::Dequeued;
+ return {{buffers[slot].slot, &buffers[slot].multi_fence}};
}
const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
- auto itr = std::find_if(queue.begin(), queue.end(),
- [&](const Buffer& buffer) { return buffer.slot == slot; });
- ASSERT(itr != queue.end());
- ASSERT(itr->status == Buffer::Status::Dequeued);
- return itr->igbp_buffer;
+ ASSERT(slot < buffers.size());
+ ASSERT(buffers[slot].status == Buffer::Status::Dequeued);
+ ASSERT(buffers[slot].slot == slot);
+
+ return buffers[slot].igbp_buffer;
}
void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect, u32 swap_interval,
Service::Nvidia::MultiFence& multi_fence) {
- auto itr = std::find_if(queue.begin(), queue.end(),
- [&](const Buffer& buffer) { return buffer.slot == slot; });
- ASSERT(itr != queue.end());
- ASSERT(itr->status == Buffer::Status::Dequeued);
- itr->status = Buffer::Status::Queued;
- itr->transform = transform;
- itr->crop_rect = crop_rect;
- itr->swap_interval = swap_interval;
- itr->multi_fence = multi_fence;
+ ASSERT(slot < buffers.size());
+ ASSERT(buffers[slot].status == Buffer::Status::Dequeued);
+ ASSERT(buffers[slot].slot == slot);
+
+ buffers[slot].status = Buffer::Status::Queued;
+ buffers[slot].transform = transform;
+ buffers[slot].crop_rect = crop_rect;
+ buffers[slot].swap_interval = swap_interval;
+ buffers[slot].multi_fence = multi_fence;
+ std::unique_lock lock{queue_sequence_mutex};
queue_sequence.push_back(slot);
}
+void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence) {
+ ASSERT(slot < buffers.size());
+ ASSERT(buffers[slot].status != Buffer::Status::Free);
+ ASSERT(buffers[slot].slot == slot);
+
+ buffers[slot].status = Buffer::Status::Free;
+ buffers[slot].multi_fence = multi_fence;
+ buffers[slot].swap_interval = 0;
+
+ {
+ std::unique_lock lock{free_buffers_mutex};
+ free_buffers.push_back(slot);
+ }
+ free_buffers_condition.notify_one();
+
+ buffer_wait_event->GetWritableEvent()->Signal();
+}
+
std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
- auto itr = queue.end();
+ std::unique_lock lock{queue_sequence_mutex};
+ std::size_t buffer_slot = buffers.size();
// Iterate to find a queued buffer matching the requested slot.
- while (itr == queue.end() && !queue_sequence.empty()) {
- const u32 slot = queue_sequence.front();
- itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) {
- return buffer.status == Buffer::Status::Queued && buffer.slot == slot;
- });
+ while (buffer_slot == buffers.size() && !queue_sequence.empty()) {
+ const auto slot = static_cast<std::size_t>(queue_sequence.front());
+ ASSERT(slot < buffers.size());
+ if (buffers[slot].status == Buffer::Status::Queued) {
+ ASSERT(buffers[slot].slot == slot);
+ buffer_slot = slot;
+ }
queue_sequence.pop_front();
}
- if (itr == queue.end()) {
+ if (buffer_slot == buffers.size()) {
return std::nullopt;
}
- itr->status = Buffer::Status::Acquired;
- return *itr;
+ buffers[buffer_slot].status = Buffer::Status::Acquired;
+ return {{buffers[buffer_slot]}};
}
void BufferQueue::ReleaseBuffer(u32 slot) {
- auto itr = std::find_if(queue.begin(), queue.end(),
- [&](const Buffer& buffer) { return buffer.slot == slot; });
- ASSERT(itr != queue.end());
- ASSERT(itr->status == Buffer::Status::Acquired);
- itr->status = Buffer::Status::Free;
- free_buffers.push_back(slot);
-
- buffer_wait_event.writable->Signal();
+ ASSERT(slot < buffers.size());
+ ASSERT(buffers[slot].status == Buffer::Status::Acquired);
+ ASSERT(buffers[slot].slot == slot);
+
+ buffers[slot].status = Buffer::Status::Free;
+ {
+ std::unique_lock lock{free_buffers_mutex};
+ free_buffers.push_back(slot);
+ }
+ free_buffers_condition.notify_one();
+
+ buffer_wait_event->GetWritableEvent()->Signal();
}
-void BufferQueue::Disconnect() {
- queue.clear();
+void BufferQueue::Connect() {
+ std::unique_lock lock{queue_sequence_mutex};
queue_sequence.clear();
- id = 1;
- layer_id = 1;
+ is_connect = true;
+}
+
+void BufferQueue::Disconnect() {
+ buffers.fill({});
+ {
+ std::unique_lock lock{queue_sequence_mutex};
+ queue_sequence.clear();
+ }
+ buffer_wait_event->GetWritableEvent()->Signal();
+ is_connect = false;
+ free_buffers_condition.notify_one();
}
u32 BufferQueue::Query(QueryType type) {
- LOG_WARNING(Service, "(STUBBED) called type={}", static_cast<u32>(type));
+ LOG_WARNING(Service, "(STUBBED) called type={}", type);
switch (type) {
case QueryType::NativeWindowFormat:
return static_cast<u32>(PixelFormat::RGBA8888);
+ case QueryType::NativeWindowWidth:
+ case QueryType::NativeWindowHeight:
+ break;
}
-
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unimplemented query type={}", type);
return 0;
}
-std::shared_ptr<Kernel::WritableEvent> BufferQueue::GetWritableBufferWaitEvent() const {
- return buffer_wait_event.writable;
+std::shared_ptr<Kernel::KWritableEvent> BufferQueue::GetWritableBufferWaitEvent() const {
+ return buffer_wait_event->GetWritableEvent();
}
-std::shared_ptr<Kernel::ReadableEvent> BufferQueue::GetBufferWaitEvent() const {
- return buffer_wait_event.readable;
+std::shared_ptr<Kernel::KReadableEvent> BufferQueue::GetBufferWaitEvent() const {
+ return buffer_wait_event->GetReadableEvent();
}
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
index 8a837e5aa..163fa4c54 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.h
+++ b/src/core/hle/service/nvflinger/buffer_queue.h
@@ -4,7 +4,9 @@
#pragma once
+#include <condition_variable>
#include <list>
+#include <mutex>
#include <optional>
#include <vector>
@@ -12,15 +14,18 @@
#include "common/math_util.h"
#include "common/swap.h"
#include "core/hle/kernel/object.h"
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
namespace Kernel {
class KernelCore;
-}
+class KEvent;
+class KReadableEvent;
+class KWritableEvent;
+} // namespace Kernel
namespace Service::NVFlinger {
+constexpr u32 buffer_slots = 0x40;
struct IGBPBuffer {
u32_le magic;
u32_le width;
@@ -95,8 +100,10 @@ public:
void QueueBuffer(u32 slot, BufferTransformFlags transform,
const Common::Rectangle<int>& crop_rect, u32 swap_interval,
Service::Nvidia::MultiFence& multi_fence);
+ void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence);
std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
void ReleaseBuffer(u32 slot);
+ void Connect();
void Disconnect();
u32 Query(QueryType type);
@@ -104,18 +111,30 @@ public:
return id;
}
- std::shared_ptr<Kernel::WritableEvent> GetWritableBufferWaitEvent() const;
+ bool IsConnected() const {
+ return is_connect;
+ }
+
+ std::shared_ptr<Kernel::KWritableEvent> GetWritableBufferWaitEvent() const;
- std::shared_ptr<Kernel::ReadableEvent> GetBufferWaitEvent() const;
+ std::shared_ptr<Kernel::KReadableEvent> GetBufferWaitEvent() const;
private:
- u32 id;
- u64 layer_id;
+ BufferQueue(const BufferQueue&) = delete;
+
+ u32 id{};
+ u64 layer_id{};
+ std::atomic_bool is_connect{};
std::list<u32> free_buffers;
- std::vector<Buffer> queue;
+ std::array<Buffer, buffer_slots> buffers;
std::list<u32> queue_sequence;
- Kernel::EventPair buffer_wait_event;
+ std::shared_ptr<Kernel::KEvent> buffer_wait_event;
+
+ std::mutex free_buffers_mutex;
+ std::condition_variable free_buffers_condition;
+
+ std::mutex queue_sequence_mutex;
};
} // namespace Service::NVFlinger
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index f644a460d..ac2906e5b 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -14,8 +14,8 @@
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hardware_properties.h"
+#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/readable_event.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
@@ -38,6 +38,10 @@ void NVFlinger::SplitVSync() {
system.RegisterHostThread();
std::string name = "yuzu:VSyncThread";
MicroProfileOnThreadCreate(name.c_str());
+
+ // Cleanup
+ SCOPE_EXIT({ MicroProfileOnThreadExit(); });
+
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
s64 delay = 0;
@@ -88,6 +92,10 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) {
}
NVFlinger::~NVFlinger() {
+ for (auto& buffer_queue : buffer_queues) {
+ buffer_queue->Disconnect();
+ }
+
if (system.IsMulticore()) {
is_running = false;
wait_event->Set();
@@ -104,6 +112,8 @@ void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
}
std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
+ const auto guard = Lock();
+
LOG_DEBUG(Service, "Opening \"{}\" display", name);
// TODO(Subv): Currently we only support the Default display.
@@ -114,43 +124,49 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
[&](const VI::Display& display) { return display.GetName() == name; });
if (itr == displays.end()) {
- return {};
+ return std::nullopt;
}
return itr->GetID();
}
std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
+ const auto guard = Lock();
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
- return {};
+ return std::nullopt;
}
const u64 layer_id = next_layer_id++;
const u32 buffer_queue_id = next_buffer_queue_id++;
- buffer_queues.emplace_back(system.Kernel(), buffer_queue_id, layer_id);
- display->CreateLayer(layer_id, buffer_queues.back());
+ buffer_queues.emplace_back(
+ std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id));
+ display->CreateLayer(layer_id, *buffer_queues.back());
return layer_id;
}
void NVFlinger::CloseLayer(u64 layer_id) {
+ const auto guard = Lock();
+
for (auto& display : displays) {
display.CloseLayer(layer_id);
}
}
std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) const {
+ const auto guard = Lock();
const auto* const layer = FindLayer(display_id, layer_id);
if (layer == nullptr) {
- return {};
+ return std::nullopt;
}
return layer->GetBufferQueue().GetId();
}
-std::shared_ptr<Kernel::ReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id) const {
+std::shared_ptr<Kernel::KReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id) const {
+ const auto guard = Lock();
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
@@ -160,20 +176,16 @@ std::shared_ptr<Kernel::ReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id)
return display->GetVSyncEvent();
}
-BufferQueue& NVFlinger::FindBufferQueue(u32 id) {
+BufferQueue* NVFlinger::FindBufferQueue(u32 id) {
+ const auto guard = Lock();
const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
- [id](const auto& queue) { return queue.GetId() == id; });
+ [id](const auto& queue) { return queue->GetId() == id; });
- ASSERT(itr != buffer_queues.end());
- return *itr;
-}
-
-const BufferQueue& NVFlinger::FindBufferQueue(u32 id) const {
- const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
- [id](const auto& queue) { return queue.GetId() == id; });
+ if (itr == buffer_queues.end()) {
+ return nullptr;
+ }
- ASSERT(itr != buffer_queues.end());
- return *itr;
+ return itr->get();
}
VI::Display* NVFlinger::FindDisplay(u64 display_id) {
@@ -242,6 +254,10 @@ void NVFlinger::Compose() {
const auto& igbp_buffer = buffer->get().igbp_buffer;
+ if (!system.IsPoweredOn()) {
+ return; // We are likely shutting down
+ }
+
auto& gpu = system.GPU();
const auto& multi_fence = buffer->get().multi_fence;
guard->unlock();
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 1ebe949c0..6fe2c7f2a 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -26,8 +26,8 @@ struct EventType;
} // namespace Core::Timing
namespace Kernel {
-class ReadableEvent;
-class WritableEvent;
+class KReadableEvent;
+class KWritableEvent;
} // namespace Kernel
namespace Service::Nvidia {
@@ -72,13 +72,10 @@ public:
/// Gets the vsync event for the specified display.
///
/// If an invalid display ID is provided, then nullptr is returned.
- [[nodiscard]] std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const;
+ [[nodiscard]] std::shared_ptr<Kernel::KReadableEvent> FindVsyncEvent(u64 display_id) const;
/// Obtains a buffer queue identified by the ID.
- [[nodiscard]] BufferQueue& FindBufferQueue(u32 id);
-
- /// Obtains a buffer queue identified by the ID.
- [[nodiscard]] const BufferQueue& FindBufferQueue(u32 id) const;
+ [[nodiscard]] BufferQueue* FindBufferQueue(u32 id);
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
@@ -86,11 +83,11 @@ public:
[[nodiscard]] s64 GetNextTicks() const;
+private:
[[nodiscard]] std::unique_lock<std::mutex> Lock() const {
return std::unique_lock{*guard};
}
-private:
/// Finds the display identified by the specified ID.
[[nodiscard]] VI::Display* FindDisplay(u64 display_id);
@@ -110,7 +107,7 @@ private:
std::shared_ptr<Nvidia::Module> nvdrv;
std::vector<VI::Display> displays;
- std::vector<BufferQueue> buffer_queues;
+ std::vector<std::unique_ptr<BufferQueue>> buffer_queues;
/// Id to use for the next layer that is created, this counter is shared among all displays.
u64 next_layer_id = 1;
diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp
new file mode 100644
index 000000000..e2ac71fa1
--- /dev/null
+++ b/src/core/hle/service/olsc/olsc.cpp
@@ -0,0 +1,80 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/service/olsc/olsc.h"
+#include "core/hle/service/service.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::OLSC {
+
+class OLSC final : public ServiceFramework<OLSC> {
+public:
+ explicit OLSC(Core::System& system_) : ServiceFramework{system_, "olsc:u"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &OLSC::Initialize, "Initialize"},
+ {10, nullptr, "VerifySaveDataBackupLicenseAsync"},
+ {13, &OLSC::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"},
+ {14, &OLSC::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"},
+ {15, nullptr, "SetCustomData"},
+ {16, nullptr, "DeleteSaveDataBackupSetting"},
+ {18, nullptr, "GetSaveDataBackupInfoCache"},
+ {19, nullptr, "UpdateSaveDataBackupInfoCacheAsync"},
+ {22, nullptr, "DeleteSaveDataBackupAsync"},
+ {25, nullptr, "ListDownloadableSaveDataBackupInfoAsync"},
+ {26, nullptr, "DownloadSaveDataBackupAsync"},
+ {9010, nullptr, "VerifySaveDataBackupLicenseAsyncForDebug"},
+ {9013, nullptr, "GetSaveDataBackupSettingForDebug"},
+ {9014, nullptr, "SetSaveDataBackupSettingEnabledForDebug"},
+ {9015, nullptr, "SetCustomDataForDebug"},
+ {9016, nullptr, "DeleteSaveDataBackupSettingForDebug"},
+ {9018, nullptr, "GetSaveDataBackupInfoCacheForDebug"},
+ {9019, nullptr, "UpdateSaveDataBackupInfoCacheAsyncForDebug"},
+ {9022, nullptr, "DeleteSaveDataBackupAsyncForDebug"},
+ {9025, nullptr, "ListDownloadableSaveDataBackupInfoAsyncForDebug"},
+ {9026, nullptr, "DownloadSaveDataBackupAsyncForDebug"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Initialize(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_OLSC, "(STUBBED) called");
+
+ initialized = true;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetSaveDataBackupSetting(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_OLSC, "(STUBBED) called");
+
+ // backup_setting is set to 0 since real value is unknown
+ constexpr u64 backup_setting = 0;
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(backup_setting);
+ }
+
+ void SetSaveDataBackupSettingEnabled(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_OLSC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ bool initialized{};
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<OLSC>(system)->InstallAsService(service_manager);
+}
+
+} // namespace Service::OLSC
diff --git a/src/core/hle/service/olsc/olsc.h b/src/core/hle/service/olsc/olsc.h
new file mode 100644
index 000000000..24f24ca6b
--- /dev/null
+++ b/src/core/hle/service/olsc/olsc.h
@@ -0,0 +1,20 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Core {
+class System;
+}
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Service::OLSC {
+
+/// Registers all SSL services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
+
+} // namespace Service::OLSC
diff --git a/src/core/hle/service/pcie/pcie.cpp b/src/core/hle/service/pcie/pcie.cpp
index c568a0adc..f6686fc4d 100644
--- a/src/core/hle/service/pcie/pcie.cpp
+++ b/src/core/hle/service/pcie/pcie.cpp
@@ -12,7 +12,7 @@ namespace Service::PCIe {
class ISession final : public ServiceFramework<ISession> {
public:
- explicit ISession() : ServiceFramework{"ISession"} {
+ explicit ISession(Core::System& system_) : ServiceFramework{system_, "ISession"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "QueryFunctions"},
@@ -48,7 +48,7 @@ public:
class PCIe final : public ServiceFramework<PCIe> {
public:
- explicit PCIe() : ServiceFramework{"pcie"} {
+ explicit PCIe(Core::System& system_) : ServiceFramework{system_, "pcie"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RegisterClassDriver"},
@@ -60,8 +60,8 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<PCIe>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<PCIe>(system)->InstallAsService(sm);
}
} // namespace Service::PCIe
diff --git a/src/core/hle/service/pcie/pcie.h b/src/core/hle/service/pcie/pcie.h
index 59c22ca45..e5709a72f 100644
--- a/src/core/hle/service/pcie/pcie.h
+++ b/src/core/hle/service/pcie/pcie.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::PCIe {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::PCIe
diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp
index caf14ed61..f9089bf2f 100644
--- a/src/core/hle/service/pctl/module.cpp
+++ b/src/core/hle/service/pctl/module.cpp
@@ -11,7 +11,8 @@ namespace Service::PCTL {
class IParentalControlService final : public ServiceFramework<IParentalControlService> {
public:
- IParentalControlService() : ServiceFramework("IParentalControlService") {
+ explicit IParentalControlService(Core::System& system_)
+ : ServiceFramework{system_, "IParentalControlService"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, &IParentalControlService::Initialize, "Initialize"},
@@ -49,11 +50,11 @@ public:
{1046, nullptr, "DisableFeaturesForReset"},
{1047, nullptr, "NotifyApplicationDownloadStarted"},
{1048, nullptr, "NotifyNetworkProfileCreated"},
- {1061, nullptr, "ConfirmStereoVisionRestrictionConfigurable"},
- {1062, nullptr, "GetStereoVisionRestriction"},
- {1063, nullptr, "SetStereoVisionRestriction"},
- {1064, nullptr, "ResetConfirmedStereoVisionPermission"},
- {1065, nullptr, "IsStereoVisionPermitted"},
+ {1061, &IParentalControlService::ConfirmStereoVisionRestrictionConfigurable, "ConfirmStereoVisionRestrictionConfigurable"},
+ {1062, &IParentalControlService::GetStereoVisionRestriction, "GetStereoVisionRestriction"},
+ {1063, &IParentalControlService::SetStereoVisionRestriction, "SetStereoVisionRestriction"},
+ {1064, &IParentalControlService::ResetConfirmedStereoVisionPermission, "ResetConfirmedStereoVisionPermission"},
+ {1065, &IParentalControlService::IsStereoVisionPermitted, "IsStereoVisionPermitted"},
{1201, nullptr, "UnlockRestrictionTemporarily"},
{1202, nullptr, "UnlockSystemSettingsRestriction"},
{1203, nullptr, "SetPinCode"},
@@ -113,6 +114,7 @@ public:
{2015, nullptr, "FinishSynchronizeParentalControlSettingsWithLastUpdated"},
{2016, nullptr, "RequestUpdateExemptionListAsync"},
};
+ // clang-format on
RegisterHandlers(functions);
}
@@ -130,6 +132,49 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+
+ void ConfirmStereoVisionRestrictionConfigurable(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PCTL, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void IsStereoVisionPermitted(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PCTL, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(true);
+ }
+
+ void SetStereoVisionRestriction(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto can_use = rp.Pop<bool>();
+ LOG_WARNING(Service_PCTL, "(STUBBED) called, can_use={}", can_use);
+
+ can_use_stereo_vision = can_use;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetStereoVisionRestriction(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PCTL, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(can_use_stereo_vision);
+ }
+
+ void ResetConfirmedStereoVisionPermission(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PCTL, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ bool can_use_stereo_vision = true;
};
void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) {
@@ -137,7 +182,7 @@ void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IParentalControlService>();
+ rb.PushIpcInterface<IParentalControlService>(system);
}
void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) {
@@ -145,20 +190,21 @@ void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IParentalControlService>();
+ rb.PushIpcInterface<IParentalControlService>(system);
}
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
- : ServiceFramework(name), module(std::move(module)) {}
+Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
+ const char* name)
+ : ServiceFramework{system_, name}, module{std::move(module_)} {}
Module::Interface::~Interface() = default;
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
auto module = std::make_shared<Module>();
- std::make_shared<PCTL>(module, "pctl")->InstallAsService(service_manager);
- std::make_shared<PCTL>(module, "pctl:a")->InstallAsService(service_manager);
- std::make_shared<PCTL>(module, "pctl:r")->InstallAsService(service_manager);
- std::make_shared<PCTL>(module, "pctl:s")->InstallAsService(service_manager);
+ std::make_shared<PCTL>(system, module, "pctl")->InstallAsService(service_manager);
+ std::make_shared<PCTL>(system, module, "pctl:a")->InstallAsService(service_manager);
+ std::make_shared<PCTL>(system, module, "pctl:r")->InstallAsService(service_manager);
+ std::make_shared<PCTL>(system, module, "pctl:s")->InstallAsService(service_manager);
}
} // namespace Service::PCTL
diff --git a/src/core/hle/service/pctl/module.h b/src/core/hle/service/pctl/module.h
index 3e449110d..4c7e09a3b 100644
--- a/src/core/hle/service/pctl/module.h
+++ b/src/core/hle/service/pctl/module.h
@@ -6,13 +6,18 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::PCTL {
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, const char* name);
+ explicit Interface(Core::System& system_, std::shared_ptr<Module> module_,
+ const char* name);
~Interface() override;
void CreateService(Kernel::HLERequestContext& ctx);
@@ -24,6 +29,6 @@ public:
};
/// Registers all PCTL services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::PCTL
diff --git a/src/core/hle/service/pctl/pctl.cpp b/src/core/hle/service/pctl/pctl.cpp
index af9d1433a..16dd34f90 100644
--- a/src/core/hle/service/pctl/pctl.cpp
+++ b/src/core/hle/service/pctl/pctl.cpp
@@ -6,8 +6,8 @@
namespace Service::PCTL {
-PCTL::PCTL(std::shared_ptr<Module> module, const char* name)
- : Module::Interface(std::move(module), name) {
+PCTL::PCTL(Core::System& system_, std::shared_ptr<Module> module_, const char* name)
+ : Interface{system_, std::move(module_), name} {
static const FunctionInfo functions[] = {
{0, &PCTL::CreateService, "CreateService"},
{1, &PCTL::CreateServiceWithoutInitialize, "CreateServiceWithoutInitialize"},
diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h
index c33ea80b6..275d23007 100644
--- a/src/core/hle/service/pctl/pctl.h
+++ b/src/core/hle/service/pctl/pctl.h
@@ -6,11 +6,15 @@
#include "core/hle/service/pctl/module.h"
+namespace Core {
+class System;
+}
+
namespace Service::PCTL {
class PCTL final : public Module::Interface {
public:
- explicit PCTL(std::shared_ptr<Module> module, const char* name);
+ explicit PCTL(Core::System& system_, std::shared_ptr<Module> module_, const char* name);
~PCTL() override;
};
diff --git a/src/core/hle/service/pcv/pcv.cpp b/src/core/hle/service/pcv/pcv.cpp
index 8bfc0276e..68b2c4178 100644
--- a/src/core/hle/service/pcv/pcv.cpp
+++ b/src/core/hle/service/pcv/pcv.cpp
@@ -12,7 +12,7 @@ namespace Service::PCV {
class PCV final : public ServiceFramework<PCV> {
public:
- explicit PCV() : ServiceFramework{"pcv"} {
+ explicit PCV(Core::System& system_) : ServiceFramework{system_, "pcv"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SetPowerEnabled"},
@@ -54,7 +54,7 @@ public:
class PCV_ARB final : public ServiceFramework<PCV_ARB> {
public:
- explicit PCV_ARB() : ServiceFramework{"pcv:arb"} {
+ explicit PCV_ARB(Core::System& system_) : ServiceFramework{system_, "pcv:arb"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "ReleaseControl"},
@@ -67,7 +67,7 @@ public:
class PCV_IMM final : public ServiceFramework<PCV_IMM> {
public:
- explicit PCV_IMM() : ServiceFramework{"pcv:imm"} {
+ explicit PCV_IMM(Core::System& system_) : ServiceFramework{system_, "pcv:imm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SetClockRate"},
@@ -78,10 +78,10 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<PCV>()->InstallAsService(sm);
- std::make_shared<PCV_ARB>()->InstallAsService(sm);
- std::make_shared<PCV_IMM>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<PCV>(system)->InstallAsService(sm);
+ std::make_shared<PCV_ARB>(system)->InstallAsService(sm);
+ std::make_shared<PCV_IMM>(system)->InstallAsService(sm);
}
} // namespace Service::PCV
diff --git a/src/core/hle/service/pcv/pcv.h b/src/core/hle/service/pcv/pcv.h
index 219a893c3..c61a0b591 100644
--- a/src/core/hle/service/pcv/pcv.h
+++ b/src/core/hle/service/pcv/pcv.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::PCV {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::PCV
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index f43122ad2..68736c40c 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
@@ -43,7 +44,7 @@ void GetApplicationPidGeneric(Kernel::HLERequestContext& ctx,
class BootMode final : public ServiceFramework<BootMode> {
public:
- explicit BootMode() : ServiceFramework{"pm:bm"} {
+ explicit BootMode(Core::System& system_) : ServiceFramework{system_, "pm:bm"} {
static const FunctionInfo functions[] = {
{0, &BootMode::GetBootMode, "GetBootMode"},
{1, &BootMode::SetMaintenanceBoot, "SetMaintenanceBoot"},
@@ -74,8 +75,8 @@ private:
class DebugMonitor final : public ServiceFramework<DebugMonitor> {
public:
- explicit DebugMonitor(const Kernel::KernelCore& kernel)
- : ServiceFramework{"pm:dmnt"}, kernel(kernel) {
+ explicit DebugMonitor(Core::System& system_)
+ : ServiceFramework{system_, "pm:dmnt"}, kernel{system_.Kernel()} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetJitDebugProcessIdList"},
@@ -124,8 +125,9 @@ private:
class Info final : public ServiceFramework<Info> {
public:
- explicit Info(const std::vector<std::shared_ptr<Kernel::Process>>& process_list)
- : ServiceFramework{"pm:info"}, process_list(process_list) {
+ explicit Info(Core::System& system_,
+ const std::vector<std::shared_ptr<Kernel::Process>>& process_list_)
+ : ServiceFramework{system_, "pm:info"}, process_list{process_list_} {
static const FunctionInfo functions[] = {
{0, &Info::GetTitleId, "GetTitleId"},
};
@@ -159,8 +161,8 @@ private:
class Shell final : public ServiceFramework<Shell> {
public:
- explicit Shell(const Kernel::KernelCore& kernel)
- : ServiceFramework{"pm:shell"}, kernel(kernel) {
+ explicit Shell(Core::System& system_)
+ : ServiceFramework{system_, "pm:shell"}, kernel{system_.Kernel()} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "LaunchProgram"},
@@ -189,11 +191,11 @@ private:
};
void InstallInterfaces(Core::System& system) {
- std::make_shared<BootMode>()->InstallAsService(system.ServiceManager());
- std::make_shared<DebugMonitor>(system.Kernel())->InstallAsService(system.ServiceManager());
- std::make_shared<Info>(system.Kernel().GetProcessList())
+ std::make_shared<BootMode>(system)->InstallAsService(system.ServiceManager());
+ std::make_shared<DebugMonitor>(system)->InstallAsService(system.ServiceManager());
+ std::make_shared<Info>(system, system.Kernel().GetProcessList())
->InstallAsService(system.ServiceManager());
- std::make_shared<Shell>(system.Kernel())->InstallAsService(system.ServiceManager());
+ std::make_shared<Shell>(system)->InstallAsService(system.ServiceManager());
}
} // namespace Service::PM
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index cde3312da..d5b3b17a5 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -4,6 +4,7 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/acc/profile_manager.h"
@@ -15,19 +16,18 @@ namespace Service::PlayReport {
class PlayReport final : public ServiceFramework<PlayReport> {
public:
- explicit PlayReport(const char* name, Core::System& system)
- : ServiceFramework{name}, system(system) {
+ explicit PlayReport(const char* name, Core::System& system_) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{10100, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old>, "SaveReportOld"},
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
{10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"},
{10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"},
- {10104, nullptr, "SaveReport"},
- {10105, nullptr, "SaveReportWithUser"},
- {10200, nullptr, "RequestImmediateTransmission"},
- {10300, nullptr, "GetTransmissionStatus"},
- {10400, nullptr, "GetSystemSessionId"},
+ {10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
+ {10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
+ {10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"},
+ {10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"},
+ {10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"},
{20100, &PlayReport::SaveSystemReport, "SaveSystemReport"},
{20101, &PlayReport::SaveSystemReportWithUser, "SaveSystemReportWithUser"},
{20200, nullptr, "SetOperationMode"},
@@ -59,16 +59,22 @@ private:
IPC::RequestParser rp{ctx};
const auto process_id = rp.PopRaw<u64>();
- std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)};
- if constexpr (Type == Core::Reporter::PlayReportType::Old2) {
- data.emplace_back(ctx.ReadBuffer(1));
- }
+ const auto data1 = ctx.ReadBuffer(0);
+ const auto data2 = [ctx] {
+ if (ctx.CanReadBuffer(1)) {
+ return ctx.ReadBuffer(1);
+ }
- LOG_DEBUG(Service_PREPO, "called, type={:02X}, process_id={:016X}, data1_size={:016X}",
- static_cast<u8>(Type), process_id, data[0].size());
+ return std::vector<u8>{};
+ }();
+
+ LOG_DEBUG(Service_PREPO,
+ "called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}",
+ Type, process_id, data1.size(), data2.size());
const auto& reporter{system.GetReporter()};
- reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), data, process_id);
+ reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2},
+ process_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -79,35 +85,67 @@ private:
IPC::RequestParser rp{ctx};
const auto user_id = rp.PopRaw<u128>();
const auto process_id = rp.PopRaw<u64>();
- std::vector<std::vector<u8>> data{ctx.ReadBuffer(0)};
- if constexpr (Type == Core::Reporter::PlayReportType::Old2) {
- const auto read_buffer_count =
- ctx.BufferDescriptorX().size() + ctx.BufferDescriptorA().size();
- if (read_buffer_count > 1) {
- data.emplace_back(ctx.ReadBuffer(1));
+ const auto data1 = ctx.ReadBuffer(0);
+ const auto data2 = [ctx] {
+ if (ctx.CanReadBuffer(1)) {
+ return ctx.ReadBuffer(1);
}
- }
- LOG_DEBUG(
- Service_PREPO,
- "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, data1_size={:016X}",
- static_cast<u8>(Type), user_id[1], user_id[0], process_id, data[0].size());
+ return std::vector<u8>{};
+ }();
+
+ LOG_DEBUG(Service_PREPO,
+ "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, "
+ "data1_size={:016X}, data2_size={:016X}",
+ Type, user_id[1], user_id[0], process_id, data1.size(), data2.size());
const auto& reporter{system.GetReporter()};
- reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), data, process_id,
- user_id);
+ reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2},
+ process_id, user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+ void RequestImmediateTransmission(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PREPO, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetTransmissionStatus(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PREPO, "(STUBBED) called");
+
+ constexpr s32 status = 0;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(status);
+ }
+
+ void GetSystemSessionId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PREPO, "(STUBBED) called");
+
+ constexpr u64 system_session_id = 0;
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(system_session_id);
+ }
+
void SaveSystemReport(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>();
const auto data1 = ctx.ReadBuffer(0);
- const auto data2 = ctx.ReadBuffer(1);
+ const auto data2 = [ctx] {
+ if (ctx.CanReadBuffer(1)) {
+ return ctx.ReadBuffer(1);
+ }
+
+ return std::vector<u8>{};
+ }();
LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}",
title_id, data1.size(), data2.size());
@@ -125,7 +163,13 @@ private:
const auto title_id = rp.PopRaw<u64>();
const auto data1 = ctx.ReadBuffer(0);
- const auto data2 = ctx.ReadBuffer(1);
+ const auto data2 = [ctx] {
+ if (ctx.CanReadBuffer(1)) {
+ return ctx.ReadBuffer(1);
+ }
+
+ return std::vector<u8>{};
+ }();
LOG_DEBUG(Service_PREPO,
"called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, "
@@ -139,8 +183,6 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
-
- Core::System& system;
};
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h
index a5682ee26..395b57ead 100644
--- a/src/core/hle/service/prepo/prepo.h
+++ b/src/core/hle/service/prepo/prepo.h
@@ -4,14 +4,14 @@
#pragma once
-namespace Service::SM {
-class ServiceManager;
-}
-
namespace Core {
class System;
}
+namespace Service::SM {
+class ServiceManager;
+}
+
namespace Service::PlayReport {
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
diff --git a/src/core/hle/service/psc/psc.cpp b/src/core/hle/service/psc/psc.cpp
index 99e1c9042..5a52b2b05 100644
--- a/src/core/hle/service/psc/psc.cpp
+++ b/src/core/hle/service/psc/psc.cpp
@@ -14,7 +14,7 @@ namespace Service::PSC {
class PSC_C final : public ServiceFramework<PSC_C> {
public:
- explicit PSC_C() : ServiceFramework{"psc:c"} {
+ explicit PSC_C(Core::System& system_) : ServiceFramework{system_, "psc:c"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -35,7 +35,7 @@ public:
class IPmModule final : public ServiceFramework<IPmModule> {
public:
- explicit IPmModule() : ServiceFramework{"IPmModule"} {
+ explicit IPmModule(Core::System& system_) : ServiceFramework{system_, "IPmModule"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -52,7 +52,7 @@ public:
class PSC_M final : public ServiceFramework<PSC_M> {
public:
- explicit PSC_M() : ServiceFramework{"psc:m"} {
+ explicit PSC_M(Core::System& system_) : ServiceFramework{system_, "psc:m"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &PSC_M::GetPmModule, "GetPmModule"},
@@ -68,13 +68,13 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IPmModule>();
+ rb.PushIpcInterface<IPmModule>(system);
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<PSC_C>()->InstallAsService(sm);
- std::make_shared<PSC_M>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<PSC_C>(system)->InstallAsService(sm);
+ std::make_shared<PSC_M>(system)->InstallAsService(sm);
}
} // namespace Service::PSC
diff --git a/src/core/hle/service/psc/psc.h b/src/core/hle/service/psc/psc.h
index 5052eb02c..89344f32d 100644
--- a/src/core/hle/service/psc/psc.h
+++ b/src/core/hle/service/psc/psc.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::PSC {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::PSC
diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp
index 6d9e6bd09..26ed52273 100644
--- a/src/core/hle/service/ptm/psm.cpp
+++ b/src/core/hle/service/ptm/psm.cpp
@@ -5,16 +5,121 @@
#include <memory>
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/service/ptm/psm.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::PSM {
+class IPsmSession final : public ServiceFramework<IPsmSession> {
+public:
+ explicit IPsmSession(Core::System& system_) : ServiceFramework{system_, "IPsmSession"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IPsmSession::BindStateChangeEvent, "BindStateChangeEvent"},
+ {1, &IPsmSession::UnbindStateChangeEvent, "UnbindStateChangeEvent"},
+ {2, &IPsmSession::SetChargerTypeChangeEventEnabled, "SetChargerTypeChangeEventEnabled"},
+ {3, &IPsmSession::SetPowerSupplyChangeEventEnabled, "SetPowerSupplyChangeEventEnabled"},
+ {4, &IPsmSession::SetBatteryVoltageStateChangeEventEnabled, "SetBatteryVoltageStateChangeEventEnabled"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ state_change_event =
+ Kernel::KEvent::Create(system_.Kernel(), "IPsmSession::state_change_event");
+ state_change_event->Initialize();
+ }
+
+ ~IPsmSession() override = default;
+
+ void SignalChargerTypeChanged() {
+ if (should_signal && should_signal_charger_type) {
+ state_change_event->GetWritableEvent()->Signal();
+ }
+ }
+
+ void SignalPowerSupplyChanged() {
+ if (should_signal && should_signal_power_supply) {
+ state_change_event->GetWritableEvent()->Signal();
+ }
+ }
+
+ void SignalBatteryVoltageStateChanged() {
+ if (should_signal && should_signal_battery_voltage) {
+ state_change_event->GetWritableEvent()->Signal();
+ }
+ }
+
+private:
+ void BindStateChangeEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PSM, "called");
+
+ should_signal = true;
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(state_change_event->GetReadableEvent());
+ }
+
+ void UnbindStateChangeEvent(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PSM, "called");
+
+ should_signal = false;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetChargerTypeChangeEventEnabled(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto state = rp.Pop<bool>();
+ LOG_DEBUG(Service_PSM, "called, state={}", state);
+
+ should_signal_charger_type = state;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetPowerSupplyChangeEventEnabled(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto state = rp.Pop<bool>();
+ LOG_DEBUG(Service_PSM, "called, state={}", state);
+
+ should_signal_power_supply = state;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetBatteryVoltageStateChangeEventEnabled(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto state = rp.Pop<bool>();
+ LOG_DEBUG(Service_PSM, "called, state={}", state);
+
+ should_signal_battery_voltage = state;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ bool should_signal_charger_type{};
+ bool should_signal_power_supply{};
+ bool should_signal_battery_voltage{};
+ bool should_signal{};
+ std::shared_ptr<Kernel::KEvent> state_change_event;
+};
+
class PSM final : public ServiceFramework<PSM> {
public:
- explicit PSM() : ServiceFramework{"psm"} {
+ explicit PSM(Core::System& system_) : ServiceFramework{system_, "psm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"},
@@ -24,7 +129,7 @@ public:
{4, nullptr, "IsBatteryChargingEnabled"},
{5, nullptr, "AcquireControllerPowerSupply"},
{6, nullptr, "ReleaseControllerPowerSupply"},
- {7, nullptr, "OpenSession"},
+ {7, &PSM::OpenSession, "OpenSession"},
{8, nullptr, "EnableEnoughPowerChargeEmulation"},
{9, nullptr, "DisableEnoughPowerChargeEmulation"},
{10, nullptr, "EnableFastBatteryCharging"},
@@ -61,6 +166,14 @@ private:
rb.PushEnum(charger_type);
}
+ void OpenSession(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_PSM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface<IPsmSession>(system);
+ }
+
enum class ChargerType : u32 {
Unplugged = 0,
RegularCharger = 1,
@@ -72,8 +185,8 @@ private:
ChargerType charger_type{ChargerType::RegularCharger};
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<PSM>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<PSM>(system)->InstallAsService(sm);
}
} // namespace Service::PSM
diff --git a/src/core/hle/service/ptm/psm.h b/src/core/hle/service/ptm/psm.h
index a286793ae..2930ce26a 100644
--- a/src/core/hle/service/ptm/psm.h
+++ b/src/core/hle/service/ptm/psm.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::PSM {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::PSM
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index fa5347af9..1da56bc27 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -11,10 +11,10 @@
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_port.h"
-#include "core/hle/kernel/thread.h"
#include "core/hle/service/acc/acc.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/aoc/aoc_u.h"
@@ -51,6 +51,7 @@
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/hle/service/olsc/olsc.h"
#include "core/hle/service/pcie/pcie.h"
#include "core/hle/service/pctl/module.h"
#include "core/hle/service/pcv/pcv.h"
@@ -89,15 +90,19 @@ namespace Service {
return function_string;
}
-////////////////////////////////////////////////////////////////////////////////////////////////////
+ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_,
+ u32 max_sessions_, InvokerFn* handler_invoker_)
+ : system{system_}, service_name{service_name_}, max_sessions{max_sessions_},
+ handler_invoker{handler_invoker_} {}
-ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions,
- InvokerFn* handler_invoker)
- : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {}
-
-ServiceFrameworkBase::~ServiceFrameworkBase() = default;
+ServiceFrameworkBase::~ServiceFrameworkBase() {
+ // Wait for other threads to release access before destroying
+ const auto guard = LockService();
+}
void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) {
+ const auto guard = LockService();
+
ASSERT(!port_installed);
auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap();
@@ -105,10 +110,11 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager)
port_installed = true;
}
-void ServiceFrameworkBase::InstallAsNamedPort() {
+void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) {
+ const auto guard = LockService();
+
ASSERT(!port_installed);
- auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
server_port->SetHleHandler(shared_from_this());
@@ -116,18 +122,6 @@ void ServiceFrameworkBase::InstallAsNamedPort() {
port_installed = true;
}
-std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() {
- ASSERT(!port_installed);
-
- auto& kernel = Core::System::GetInstance().Kernel();
- auto [server_port, client_port] =
- Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
- auto port = MakeResult(std::move(server_port)).Unwrap();
- port->SetHleHandler(shared_from_this());
- port_installed = true;
- return client_port;
-}
-
void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) {
handlers.reserve(handlers.size() + n);
for (std::size_t i = 0; i < n; ++i) {
@@ -149,8 +143,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
}
buf.push_back('}');
- Core::System::GetInstance().GetReporter().SaveUnimplementedFunctionReport(
- ctx, ctx.GetCommand(), function_name, service_name);
+ system.GetReporter().SaveUnimplementedFunctionReport(ctx, ctx.GetCommand(), function_name,
+ service_name);
UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));
}
@@ -166,6 +160,8 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {
}
ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) {
+ const auto guard = LockService();
+
switch (context.GetCommandType()) {
case IPC::CommandType::Close: {
IPC::ResponseBuilder rb{context, 2};
@@ -174,7 +170,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
}
case IPC::CommandType::ControlWithContext:
case IPC::CommandType::Control: {
- Core::System::GetInstance().ServiceManager().InvokeControlRequest(context);
+ system.ServiceManager().InvokeControlRequest(context);
break;
}
case IPC::CommandType::RequestWithContext:
@@ -183,82 +179,82 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
break;
}
default:
- UNIMPLEMENTED_MSG("command_type={}", static_cast<int>(context.GetCommandType()));
+ UNIMPLEMENTED_MSG("command_type={}", context.GetCommandType());
}
- context.WriteToOutgoingCommandBuffer(context.GetThread());
+ // If emulation was shutdown, we are closing service threads, do not write the response back to
+ // memory that may be shutting down as well.
+ if (system.IsPoweredOn()) {
+ context.WriteToOutgoingCommandBuffer(context.GetThread());
+ }
return RESULT_SUCCESS;
}
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Module interface
+/// Initialize Services
+Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system)
+ : nv_flinger{std::make_unique<NVFlinger::NVFlinger>(system)} {
-/// Initialize ServiceManager
-void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
// here and pass it into the respective InstallInterfaces functions.
- auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system);
+
system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
- SM::ServiceManager::InstallInterfaces(sm, system.Kernel());
+ SM::ServiceManager::InstallInterfaces(sm, system);
Account::InstallInterfaces(system);
- AM::InstallInterfaces(*sm, nv_flinger, system);
+ AM::InstallInterfaces(*sm, *nv_flinger, system);
AOC::InstallInterfaces(*sm, system);
APM::InstallInterfaces(system);
Audio::InstallInterfaces(*sm, system);
BCAT::InstallInterfaces(system);
- BPC::InstallInterfaces(*sm);
+ BPC::InstallInterfaces(*sm, system);
BtDrv::InstallInterfaces(*sm, system);
BTM::InstallInterfaces(*sm, system);
- Capture::InstallInterfaces(*sm);
- ERPT::InstallInterfaces(*sm);
- ES::InstallInterfaces(*sm);
- EUPLD::InstallInterfaces(*sm);
+ Capture::InstallInterfaces(*sm, system);
+ ERPT::InstallInterfaces(*sm, system);
+ ES::InstallInterfaces(*sm, system);
+ EUPLD::InstallInterfaces(*sm, system);
Fatal::InstallInterfaces(*sm, system);
- FGM::InstallInterfaces(*sm);
+ FGM::InstallInterfaces(*sm, system);
FileSystem::InstallInterfaces(system);
Friend::InstallInterfaces(*sm, system);
Glue::InstallInterfaces(system);
- GRC::InstallInterfaces(*sm);
+ GRC::InstallInterfaces(*sm, system);
HID::InstallInterfaces(*sm, system);
- LBL::InstallInterfaces(*sm);
- LDN::InstallInterfaces(*sm);
+ LBL::InstallInterfaces(*sm, system);
+ LDN::InstallInterfaces(*sm, system);
LDR::InstallInterfaces(*sm, system);
LM::InstallInterfaces(system);
- Migration::InstallInterfaces(*sm);
- Mii::InstallInterfaces(*sm);
- MM::InstallInterfaces(*sm);
- NCM::InstallInterfaces(*sm);
- NFC::InstallInterfaces(*sm);
+ Migration::InstallInterfaces(*sm, system);
+ Mii::InstallInterfaces(*sm, system);
+ MM::InstallInterfaces(*sm, system);
+ NCM::InstallInterfaces(*sm, system);
+ NFC::InstallInterfaces(*sm, system);
NFP::InstallInterfaces(*sm, system);
NIFM::InstallInterfaces(*sm, system);
NIM::InstallInterfaces(*sm, system);
- NPNS::InstallInterfaces(*sm);
+ NPNS::InstallInterfaces(*sm, system);
NS::InstallInterfaces(*sm, system);
Nvidia::InstallInterfaces(*sm, *nv_flinger, system);
- PCIe::InstallInterfaces(*sm);
- PCTL::InstallInterfaces(*sm);
- PCV::InstallInterfaces(*sm);
+ OLSC::InstallInterfaces(*sm, system);
+ PCIe::InstallInterfaces(*sm, system);
+ PCTL::InstallInterfaces(*sm, system);
+ PCV::InstallInterfaces(*sm, system);
PlayReport::InstallInterfaces(*sm, system);
PM::InstallInterfaces(system);
- PSC::InstallInterfaces(*sm);
- PSM::InstallInterfaces(*sm);
- Set::InstallInterfaces(*sm);
- Sockets::InstallInterfaces(*sm);
- SPL::InstallInterfaces(*sm);
- SSL::InstallInterfaces(*sm);
+ PSC::InstallInterfaces(*sm, system);
+ PSM::InstallInterfaces(*sm, system);
+ Set::InstallInterfaces(*sm, system);
+ Sockets::InstallInterfaces(*sm, system);
+ SPL::InstallInterfaces(*sm, system);
+ SSL::InstallInterfaces(*sm, system);
Time::InstallInterfaces(system);
- USB::InstallInterfaces(*sm);
- VI::InstallInterfaces(*sm, nv_flinger);
- WLAN::InstallInterfaces(*sm);
-
- LOG_DEBUG(Service, "initialized OK");
+ USB::InstallInterfaces(*sm, system);
+ VI::InstallInterfaces(*sm, system, *nv_flinger);
+ WLAN::InstallInterfaces(*sm, system);
}
-/// Shutdown ServiceManager
-void Shutdown() {
- LOG_DEBUG(Service, "shutdown OK");
-}
+Services::~Services() = default;
+
} // namespace Service
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 022d885b6..916445517 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -5,9 +5,11 @@
#pragma once
#include <cstddef>
+#include <mutex>
#include <string>
#include <boost/container/flat_map.hpp>
#include "common/common_types.h"
+#include "common/spin_lock.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h"
@@ -29,7 +31,11 @@ namespace Service {
namespace FileSystem {
class FileSystemController;
-} // namespace FileSystem
+}
+
+namespace NVFlinger {
+class NVFlinger;
+}
namespace SM {
class ServiceManager;
@@ -63,12 +69,10 @@ public:
/// Creates a port pair and registers this service with the given ServiceManager.
void InstallAsService(SM::ServiceManager& service_manager);
/// Creates a port pair and registers it on the kernel's global port registry.
- void InstallAsNamedPort();
- /// Creates and returns an unregistered port for the service.
- std::shared_ptr<Kernel::ClientPort> CreatePort();
-
+ void InstallAsNamedPort(Kernel::KernelCore& kernel);
+ /// Invokes a service request routine.
void InvokeRequest(Kernel::HLERequestContext& ctx);
-
+ /// Handles a synchronization request for the service.
ResultCode HandleSyncRequest(Kernel::HLERequestContext& context) override;
protected:
@@ -76,6 +80,14 @@ protected:
template <typename Self>
using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&);
+ /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
+ [[nodiscard]] std::scoped_lock<Common::SpinLock> LockService() {
+ return std::scoped_lock{lock_service};
+ }
+
+ /// System context that the service operates under.
+ Core::System& system;
+
private:
template <typename T>
friend class ServiceFramework;
@@ -89,7 +101,8 @@ private:
using InvokerFn = void(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member,
Kernel::HLERequestContext& ctx);
- ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker);
+ explicit ServiceFrameworkBase(Core::System& system_, const char* service_name_,
+ u32 max_sessions_, InvokerFn* handler_invoker_);
~ServiceFrameworkBase() override;
void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n);
@@ -107,6 +120,9 @@ private:
/// Function used to safely up-cast pointers to the derived class before invoking a handler.
InvokerFn* handler_invoker;
boost::container::flat_map<u32, FunctionInfoBase> handlers;
+
+ /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
+ Common::SpinLock lock_service;
};
/**
@@ -147,11 +163,15 @@ protected:
/**
* Initializes the handler with no functions installed.
- * @param max_sessions Maximum number of sessions that can be
- * connected to this service at the same time.
+ *
+ * @param system_ The system context to construct this service under.
+ * @param service_name_ Name of the service.
+ * @param max_sessions_ Maximum number of sessions that can be
+ * connected to this service at the same time.
*/
- explicit ServiceFramework(const char* service_name, u32 max_sessions = DefaultMaxSessions)
- : ServiceFrameworkBase(service_name, max_sessions, Invoker) {}
+ explicit ServiceFramework(Core::System& system_, const char* service_name_,
+ u32 max_sessions_ = DefaultMaxSessions)
+ : ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {}
/// Registers handlers in the service.
template <std::size_t N>
@@ -181,10 +201,17 @@ private:
}
};
-/// Initialize ServiceManager
-void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system);
+/**
+ * The purpose of this class is to own any objects that need to be shared across the other service
+ * implementations. Will be torn down when the global system instance is shutdown.
+ */
+class Services final {
+public:
+ explicit Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system);
+ ~Services();
-/// Shutdown ServiceManager
-void Shutdown();
+private:
+ std::unique_ptr<NVFlinger::NVFlinger> nv_flinger;
+};
} // namespace Service
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index e64777668..d953b4303 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -188,7 +188,7 @@ void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) {
GetKeyCodeMapImpl(ctx);
}
-SET::SET() : ServiceFramework("set") {
+SET::SET(Core::System& system_) : ServiceFramework{system_, "set"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &SET::GetLanguageCode, "GetLanguageCode"},
@@ -202,6 +202,7 @@ SET::SET() : ServiceFramework("set") {
{8, &SET::GetQuestFlag, "GetQuestFlag"},
{9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"},
{10, nullptr, "GetFirmwareVersionForDebug"},
+ {11, nullptr, "GetDeviceNickName"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 8ac9c169d..d5bd7828d 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Set {
/// This is "nn::settings::LanguageCode", which is a NUL-terminated string stored in a u64.
@@ -32,7 +36,7 @@ LanguageCode GetLanguageCodeFromIndex(std::size_t idx);
class SET final : public ServiceFramework<SET> {
public:
- explicit SET();
+ explicit SET(Core::System& system_);
~SET() override;
private:
diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp
index 3fbfecc9e..b2aa7bc0c 100644
--- a/src/core/hle/service/set/set_cal.cpp
+++ b/src/core/hle/service/set/set_cal.cpp
@@ -6,7 +6,7 @@
namespace Service::Set {
-SET_CAL::SET_CAL() : ServiceFramework("set:cal") {
+SET_CAL::SET_CAL(Core::System& system_) : ServiceFramework{system_, "set:cal"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetBluetoothBdAddress"},
diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h
index a0677e815..a29fc3ddd 100644
--- a/src/core/hle/service/set/set_cal.h
+++ b/src/core/hle/service/set/set_cal.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Set {
class SET_CAL final : public ServiceFramework<SET_CAL> {
public:
- explicit SET_CAL();
+ explicit SET_CAL(Core::System& system_);
~SET_CAL() override;
};
diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp
index 565882a31..f04dc5047 100644
--- a/src/core/hle/service/set/set_fd.cpp
+++ b/src/core/hle/service/set/set_fd.cpp
@@ -6,7 +6,7 @@
namespace Service::Set {
-SET_FD::SET_FD() : ServiceFramework("set:fd") {
+SET_FD::SET_FD(Core::System& system_) : ServiceFramework{system_, "set:fd"} {
// clang-format off
static const FunctionInfo functions[] = {
{2, nullptr, "SetSettingsItemValue"},
diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h
index 216e65f1f..c28cb301e 100644
--- a/src/core/hle/service/set/set_fd.h
+++ b/src/core/hle/service/set/set_fd.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Set {
class SET_FD final : public ServiceFramework<SET_FD> {
public:
- explicit SET_FD();
+ explicit SET_FD(Core::System& system_);
~SET_FD() override;
};
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 8bd4c7e79..b58b2c8c5 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -34,9 +34,9 @@ void GetFirmwareVersionImpl(Kernel::HLERequestContext& ctx, GetFirmwareVersionTy
// consistence (currently reports as 5.1.0-0.0)
const auto archive = FileSys::SystemArchive::SystemVersion();
- const auto early_exit_failure = [&ctx](const std::string& desc, ResultCode code) {
+ const auto early_exit_failure = [&ctx](std::string_view desc, ResultCode code) {
LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
- desc.c_str());
+ desc);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(code);
};
@@ -103,7 +103,7 @@ void SET_SYS::SetColorSetId(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
-SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
+SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SetLanguageCode"},
@@ -300,6 +300,8 @@ SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
{198, nullptr, "SetButtonConfigRegisteredSettingsEmbedded"},
{199, nullptr, "GetButtonConfigRegisteredSettings"},
{200, nullptr, "SetButtonConfigRegisteredSettings"},
+ {201, nullptr, "GetFieldTestingFlag"},
+ {202, nullptr, "SetFieldTestingFlag"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 13ee2cf46..edb185a68 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Set {
class SET_SYS final : public ServiceFramework<SET_SYS> {
public:
- explicit SET_SYS();
+ explicit SET_SYS(Core::System& system_);
~SET_SYS() override;
private:
diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp
index cf5541ca8..212ebc427 100644
--- a/src/core/hle/service/set/settings.cpp
+++ b/src/core/hle/service/set/settings.cpp
@@ -7,14 +7,15 @@
#include "core/hle/service/set/set_fd.h"
#include "core/hle/service/set/set_sys.h"
#include "core/hle/service/set/settings.h"
+#include "core/hle/service/sm/sm.h"
namespace Service::Set {
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<SET>()->InstallAsService(service_manager);
- std::make_shared<SET_CAL>()->InstallAsService(service_manager);
- std::make_shared<SET_FD>()->InstallAsService(service_manager);
- std::make_shared<SET_SYS>()->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<SET>(system)->InstallAsService(service_manager);
+ std::make_shared<SET_CAL>(system)->InstallAsService(service_manager);
+ std::make_shared<SET_FD>(system)->InstallAsService(service_manager);
+ std::make_shared<SET_SYS>(system)->InstallAsService(service_manager);
}
} // namespace Service::Set
diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h
index 6606ce776..7a6950dd0 100644
--- a/src/core/hle/service/set/settings.h
+++ b/src/core/hle/service/set/settings.h
@@ -4,11 +4,17 @@
#pragma once
-#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
+namespace Service::SM {
+class ServiceManager;
+}
namespace Service::Set {
/// Registers all Settings services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::Set
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp
index 972aaa6d9..916177efd 100644
--- a/src/core/hle/service/sm/controller.cpp
+++ b/src/core/hle/service/sm/controller.cpp
@@ -48,7 +48,7 @@ void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) {
}
// https://switchbrew.org/wiki/IPC_Marshalling
-Controller::Controller() : ServiceFramework("IpcController") {
+Controller::Controller(Core::System& system_) : ServiceFramework{system_, "IpcController"} {
static const FunctionInfo functions[] = {
{0, &Controller::ConvertCurrentObjectToDomain, "ConvertCurrentObjectToDomain"},
{1, nullptr, "CopyFromCurrentDomain"},
diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h
index 180c6da50..7494f898d 100644
--- a/src/core/hle/service/sm/controller.h
+++ b/src/core/hle/service/sm/controller.h
@@ -6,11 +6,15 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class Controller final : public ServiceFramework<Controller> {
public:
- Controller();
+ explicit Controller(Core::System& system_);
~Controller() override;
private:
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index d872de16c..2b91a89d1 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -19,7 +19,7 @@ constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::SM, 4);
constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6);
constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7);
-ServiceManager::ServiceManager() = default;
+ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {}
ServiceManager::~ServiceManager() = default;
void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
@@ -27,29 +27,28 @@ void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
}
static ResultCode ValidateServiceName(const std::string& name) {
- if (name.size() <= 0 || name.size() > 8) {
+ if (name.empty() || name.size() > 8) {
LOG_ERROR(Service_SM, "Invalid service name! service={}", name);
return ERR_INVALID_NAME;
}
- if (name.find('\0') != std::string::npos) {
+ if (name.rfind('\0') != std::string::npos) {
LOG_ERROR(Service_SM, "A non null terminated service was passed");
return ERR_INVALID_NAME;
}
return RESULT_SUCCESS;
}
-void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self,
- Kernel::KernelCore& kernel) {
+void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self, Core::System& system) {
ASSERT(self->sm_interface.expired());
- auto sm = std::make_shared<SM>(self, kernel);
- sm->InstallAsNamedPort();
+ auto sm = std::make_shared<SM>(self, system);
+ sm->InstallAsNamedPort(system.Kernel());
self->sm_interface = sm;
- self->controller_interface = std::make_unique<Controller>();
+ self->controller_interface = std::make_unique<Controller>(system);
}
-ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(
- std::string name, unsigned int max_sessions) {
+ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(std::string name,
+ u32 max_sessions) {
CASCADE_CODE(ValidateServiceName(name));
@@ -58,7 +57,6 @@ ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(
return ERR_ALREADY_REGISTERED;
}
- auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);
@@ -141,9 +139,6 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
server_port->AppendPendingSession(server);
}
- // Wake the threads waiting on the ServerPort
- server_port->Signal();
-
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, client->GetObjectId());
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(RESULT_SUCCESS);
@@ -191,8 +186,9 @@ void SM::UnregisterService(Kernel::HLERequestContext& ctx) {
rb.Push(service_manager->UnregisterService(name));
}
-SM::SM(std::shared_ptr<ServiceManager> service_manager, Kernel::KernelCore& kernel)
- : ServiceFramework{"sm:", 4}, service_manager{std::move(service_manager)}, kernel{kernel} {
+SM::SM(std::shared_ptr<ServiceManager> service_manager_, Core::System& system_)
+ : ServiceFramework{system_, "sm:", 4},
+ service_manager{std::move(service_manager_)}, kernel{system_.Kernel()} {
static const FunctionInfo functions[] = {
{0x00000000, &SM::Initialize, "Initialize"},
{0x00000001, &SM::GetService, "GetService"},
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index aabf166b7..3f46ae44f 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -16,6 +16,10 @@
#include "core/hle/result.h"
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class ClientPort;
class ClientSession;
@@ -31,7 +35,7 @@ class Controller;
/// Interface to "sm:" service
class SM final : public ServiceFramework<SM> {
public:
- explicit SM(std::shared_ptr<ServiceManager> service_manager, Kernel::KernelCore& kernel);
+ explicit SM(std::shared_ptr<ServiceManager> service_manager_, Core::System& system_);
~SM() override;
private:
@@ -46,13 +50,13 @@ private:
class ServiceManager {
public:
- static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel);
+ static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Core::System& system);
- ServiceManager();
+ explicit ServiceManager(Kernel::KernelCore& kernel_);
~ServiceManager();
ResultVal<std::shared_ptr<Kernel::ServerPort>> RegisterService(std::string name,
- unsigned int max_sessions);
+ u32 max_sessions);
ResultCode UnregisterService(const std::string& name);
ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name);
ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name);
@@ -79,6 +83,9 @@ private:
/// Map of registered services, retrieved using GetServicePort or ConnectToService.
std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services;
+
+ /// Kernel context
+ Kernel::KernelCore& kernel;
};
} // namespace Service::SM
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 8d4952c0e..78e9cd708 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -2,18 +2,138 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/microprofile.h"
+#include "common/thread.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/service/sockets/bsd.h"
+#include "core/hle/service/sockets/sockets_translate.h"
+#include "core/network/network.h"
+#include "core/network/sockets.h"
namespace Service::Sockets {
+namespace {
+
+bool IsConnectionBased(Type type) {
+ switch (type) {
+ case Type::STREAM:
+ return true;
+ case Type::DGRAM:
+ return false;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented type={}", type);
+ return false;
+ }
+}
+
+} // Anonymous namespace
+
+void BSD::PollWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->PollImpl(write_buffer, read_buffer, nfds, timeout);
+}
+
+void BSD::PollWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::AcceptWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->AcceptImpl(fd, write_buffer);
+}
+
+void BSD::AcceptWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
+}
+
+void BSD::ConnectWork::Execute(BSD* bsd) {
+ bsd_errno = bsd->ConnectImpl(fd, addr);
+}
+
+void BSD::ConnectWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::RecvWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->RecvImpl(fd, flags, message);
+}
+
+void BSD::RecvWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(message);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::RecvFromWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->RecvFromImpl(fd, flags, message, addr);
+}
+
+void BSD::RecvFromWork::Response(Kernel::HLERequestContext& ctx) {
+ ctx.WriteBuffer(message, 0);
+ if (!addr.empty()) {
+ ctx.WriteBuffer(addr, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(addr.size()));
+}
+
+void BSD::SendWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->SendImpl(fd, flags, message);
+}
+
+void BSD::SendWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
+void BSD::SendToWork::Execute(BSD* bsd) {
+ std::tie(ret, bsd_errno) = bsd->SendToImpl(fd, flags, message, addr);
+}
+
+void BSD::SendToWork::Response(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
+}
+
void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(0); // bsd errno
}
void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
@@ -26,20 +146,19 @@ void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
void BSD::Socket(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
+ const u32 domain = rp.Pop<u32>();
+ const u32 type = rp.Pop<u32>();
+ const u32 protocol = rp.Pop<u32>();
- u32 domain = rp.Pop<u32>();
- u32 type = rp.Pop<u32>();
- u32 protocol = rp.Pop<u32>();
-
- LOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type, protocol);
+ LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
- u32 fd = next_fd++;
+ const auto [fd, bsd_errno] = SocketImpl(static_cast<Domain>(domain), static_cast<Type>(type),
+ static_cast<Protocol>(protocol));
IPC::ResponseBuilder rb{ctx, 4};
-
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(fd);
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(fd);
+ rb.PushEnum(bsd_errno);
}
void BSD::Select(Kernel::HLERequestContext& ctx) {
@@ -52,67 +171,659 @@ void BSD::Select(Kernel::HLERequestContext& ctx) {
rb.Push<u32>(0); // bsd errno
}
+void BSD::Poll(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 nfds = rp.Pop<s32>();
+ const s32 timeout = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout);
+
+ ExecuteWork(ctx, PollWork{
+ .nfds = nfds,
+ .timeout = timeout,
+ .read_buffer = ctx.ReadBuffer(),
+ .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
+void BSD::Accept(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ ExecuteWork(ctx, AcceptWork{
+ .fd = fd,
+ .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
void BSD::Bind(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer()));
}
void BSD::Connect(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, ConnectWork{
+ .fd = fd,
+ .addr = ctx.ReadBuffer(),
+ });
+}
+
+void BSD::GetPeerName(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
+ const Errno bsd_errno = GetPeerNameImpl(fd, write_buffer);
+
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
+}
+
+void BSD::GetSockName(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
+ const Errno bsd_errno = GetSockNameImpl(fd, write_buffer);
+
+ ctx.WriteBuffer(write_buffer);
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
+ rb.PushEnum(bsd_errno);
+ rb.Push<u32>(static_cast<u32>(write_buffer.size()));
+}
+
+void BSD::GetSockOpt(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const u32 level = rp.Pop<u32>();
+ const auto optname = static_cast<OptName>(rp.Pop<u32>());
+
+ LOG_WARNING(Service, "(STUBBED) called. fd={} level={} optname=0x{:x}", fd, level, optname);
+
+ std::vector<u8> optval(ctx.GetWriteBufferSize());
+
+ ctx.WriteBuffer(optval);
+
+ IPC::ResponseBuilder rb{ctx, 5};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<s32>(-1);
+ rb.PushEnum(Errno::NOTCONN);
+ rb.Push<u32>(static_cast<u32>(optval.size()));
}
void BSD::Listen(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const s32 backlog = rp.Pop<s32>();
- IPC::ResponseBuilder rb{ctx, 4};
+ LOG_DEBUG(Service, "called. fd={} backlog={}", fd, backlog);
+ BuildErrnoResponse(ctx, ListenImpl(fd, backlog));
+}
+
+void BSD::Fcntl(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const s32 cmd = rp.Pop<s32>();
+ const s32 arg = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
+
+ const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
+
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(ret);
+ rb.PushEnum(bsd_errno);
}
void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
- IPC::ResponseBuilder rb{ctx, 4};
+ const s32 fd = rp.Pop<s32>();
+ const u32 level = rp.Pop<u32>();
+ const OptName optname = static_cast<OptName>(rp.Pop<u32>());
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ const std::vector<u8> buffer = ctx.ReadBuffer();
+ const u8* optval = buffer.empty() ? nullptr : buffer.data();
+ size_t optlen = buffer.size();
+
+ std::array<u64, 2> values;
+ if ((optname == OptName::SNDTIMEO || optname == OptName::RCVTIMEO) && buffer.size() == 8) {
+ std::memcpy(values.data(), buffer.data(), sizeof(values));
+ optlen = sizeof(values);
+ optval = reinterpret_cast<const u8*>(values.data());
+ }
+
+ LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level,
+ static_cast<u32>(optname), optlen);
+
+ BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optlen, optval));
+}
+
+void BSD::Shutdown(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const s32 how = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={} how={}", fd, how);
+
+ BuildErrnoResponse(ctx, ShutdownImpl(fd, how));
+}
+
+void BSD::Recv(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize());
+
+ ExecuteWork(ctx, RecvWork{
+ .fd = fd,
+ .flags = flags,
+ .message = std::vector<u8>(ctx.GetWriteBufferSize()),
+ });
+}
+
+void BSD::RecvFrom(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags,
+ ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1));
+
+ ExecuteWork(ctx, RecvFromWork{
+ .fd = fd,
+ .flags = flags,
+ .message = std::vector<u8>(ctx.GetWriteBufferSize(0)),
+ .addr = std::vector<u8>(ctx.GetWriteBufferSize(1)),
+ });
+}
+
+void BSD::Send(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, SendWork{
+ .fd = fd,
+ .flags = flags,
+ .message = ctx.ReadBuffer(),
+ });
}
void BSD::SendTo(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags,
+ ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1));
+
+ ExecuteWork(ctx, SendToWork{
+ .fd = fd,
+ .flags = flags,
+ .message = ctx.ReadBuffer(0),
+ .addr = ctx.ReadBuffer(1),
+ });
+}
- IPC::ResponseBuilder rb{ctx, 4};
+void BSD::Write(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize());
+
+ ExecuteWork(ctx, SendWork{
+ .fd = fd,
+ .flags = 0,
+ .message = ctx.ReadBuffer(),
+ });
}
void BSD::Close(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const s32 fd = rp.Pop<s32>();
+
+ LOG_DEBUG(Service, "called. fd={}", fd);
+
+ BuildErrnoResponse(ctx, CloseImpl(fd));
+}
+
+void BSD::EventFd(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 initval = rp.Pop<u64>();
+ const u32 flags = rp.Pop<u32>();
+
+ LOG_WARNING(Service, "(STUBBED) called. initval={}, flags={}", initval, flags);
+
+ BuildErrnoResponse(ctx, Errno::SUCCESS);
+}
+
+template <typename Work>
+void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, Work work) {
+ work.Execute(this);
+ work.Response(ctx);
+}
+std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) {
+ if (type == Type::SEQPACKET) {
+ UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management");
+ } else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) {
+ UNIMPLEMENTED_MSG("SOCK_RAW errno management");
+ }
+
+ [[maybe_unused]] const bool unk_flag = (static_cast<u32>(type) & 0x20000000) != 0;
+ UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type");
+ type = static_cast<Type>(static_cast<u32>(type) & ~0x20000000);
+
+ const s32 fd = FindFreeFileDescriptorHandle();
+ if (fd < 0) {
+ LOG_ERROR(Service, "No more file descriptors available");
+ return {-1, Errno::MFILE};
+ }
+
+ file_descriptors[fd] = FileDescriptor{};
+ FileDescriptor& descriptor = *file_descriptors[fd];
+ // ENONMEM might be thrown here
+
+ LOG_INFO(Service, "New socket fd={}", fd);
+
+ descriptor.socket = std::make_unique<Network::Socket>();
+ descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
+ descriptor.is_connection_based = IsConnectionBased(type);
+
+ return {fd, Errno::SUCCESS};
+}
+
+std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
+ s32 nfds, s32 timeout) {
+ if (write_buffer.size() < nfds * sizeof(PollFD)) {
+ return {-1, Errno::INVAL};
+ }
+
+ if (nfds == 0) {
+ // When no entries are provided, -1 is returned with errno zero
+ return {-1, Errno::SUCCESS};
+ }
+
+ const size_t length = std::min(read_buffer.size(), write_buffer.size());
+ std::vector<PollFD> fds(nfds);
+ std::memcpy(fds.data(), read_buffer.data(), length);
+
+ if (timeout >= 0) {
+ const s64 seconds = timeout / 1000;
+ const u64 nanoseconds = 1'000'000 * (static_cast<u64>(timeout) % 1000);
+
+ if (seconds < 0) {
+ return {-1, Errno::INVAL};
+ }
+ if (nanoseconds > 999'999'999) {
+ return {-1, Errno::INVAL};
+ }
+ } else if (timeout != -1) {
+ return {-1, Errno::INVAL};
+ }
+
+ for (PollFD& pollfd : fds) {
+ ASSERT(False(pollfd.revents));
+
+ if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) {
+ LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd);
+ pollfd.revents = PollEvents{};
+ return {0, Errno::SUCCESS};
+ }
+
+ const std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd];
+ if (!descriptor) {
+ LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd);
+ pollfd.revents = PollEvents::Nval;
+ return {0, Errno::SUCCESS};
+ }
+ }
+
+ std::vector<Network::PollFD> host_pollfds(fds.size());
+ std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) {
+ Network::PollFD result;
+ result.socket = file_descriptors[pollfd.fd]->socket.get();
+ result.events = TranslatePollEventsToHost(pollfd.events);
+ result.revents = Network::PollEvents{};
+ return result;
+ });
+
+ const auto result = Network::Poll(host_pollfds, timeout);
+
+ const size_t num = host_pollfds.size();
+ for (size_t i = 0; i < num; ++i) {
+ fds[i].revents = TranslatePollEventsToGuest(host_pollfds[i].revents);
+ }
+ std::memcpy(write_buffer.data(), fds.data(), length);
+
+ return Translate(result);
+}
+
+std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ const s32 new_fd = FindFreeFileDescriptorHandle();
+ if (new_fd < 0) {
+ LOG_ERROR(Service, "No more file descriptors available");
+ return {-1, Errno::MFILE};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+ auto [result, bsd_errno] = descriptor.socket->Accept();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return {-1, Translate(bsd_errno)};
+ }
+
+ file_descriptors[new_fd] = FileDescriptor{};
+ FileDescriptor& new_descriptor = *file_descriptors[new_fd];
+ new_descriptor.socket = std::move(result.socket);
+ new_descriptor.is_connection_based = descriptor.is_connection_based;
+
+ ASSERT(write_buffer.size() == sizeof(SockAddrIn));
+ const SockAddrIn guest_addr_in = Translate(result.sockaddr_in);
+ std::memcpy(write_buffer.data(), &guest_addr_in, sizeof(guest_addr_in));
+
+ return {new_fd, Errno::SUCCESS};
+}
+
+Errno BSD::BindImpl(s32 fd, const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ ASSERT(addr.size() == sizeof(SockAddrIn));
+ SockAddrIn addr_in;
+ std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
+
+ return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
+}
+
+Errno BSD::ConnectImpl(s32 fd, const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn));
+ SockAddrIn addr_in;
+ std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
+
+ return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
+}
+
+Errno BSD::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return Translate(bsd_errno);
+ }
+ const SockAddrIn guest_addrin = Translate(addr_in);
+
+ ASSERT(write_buffer.size() == sizeof(guest_addrin));
+ std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
+ return Translate(bsd_errno);
+}
+
+Errno BSD::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName();
+ if (bsd_errno != Network::Errno::SUCCESS) {
+ return Translate(bsd_errno);
+ }
+ const SockAddrIn guest_addrin = Translate(addr_in);
+
+ ASSERT(write_buffer.size() == sizeof(guest_addrin));
+ std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
+ return Translate(bsd_errno);
+}
+
+Errno BSD::ListenImpl(s32 fd, s32 backlog) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ return Translate(file_descriptors[fd]->socket->Listen(backlog));
+}
+
+std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+
+ switch (cmd) {
+ case FcntlCmd::GETFL:
+ ASSERT(arg == 0);
+ return {descriptor.flags, Errno::SUCCESS};
+ case FcntlCmd::SETFL: {
+ const bool enable = (arg & FLAG_O_NONBLOCK) != 0;
+ const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable));
+ if (bsd_errno != Errno::SUCCESS) {
+ return {-1, bsd_errno};
+ }
+ descriptor.flags = arg;
+ return {0, Errno::SUCCESS};
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented cmd={}", cmd);
+ return {-1, Errno::SUCCESS};
+ }
+}
+
+Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) {
+ UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET
+
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ Network::Socket* const socket = file_descriptors[fd]->socket.get();
+
+ if (optname == OptName::LINGER) {
+ ASSERT(optlen == sizeof(Linger));
+ Linger linger;
+ std::memcpy(&linger, optval, sizeof(linger));
+ ASSERT(linger.onoff == 0 || linger.onoff == 1);
+
+ return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
+ }
+
+ ASSERT(optlen == sizeof(u32));
+ u32 value;
+ std::memcpy(&value, optval, sizeof(value));
+
+ switch (optname) {
+ case OptName::REUSEADDR:
+ ASSERT(value == 0 || value == 1);
+ return Translate(socket->SetReuseAddr(value != 0));
+ case OptName::BROADCAST:
+ ASSERT(value == 0 || value == 1);
+ return Translate(socket->SetBroadcast(value != 0));
+ case OptName::SNDBUF:
+ return Translate(socket->SetSndBuf(value));
+ case OptName::RCVBUF:
+ return Translate(socket->SetRcvBuf(value));
+ case OptName::SNDTIMEO:
+ return Translate(socket->SetSndTimeo(value));
+ case OptName::RCVTIMEO:
+ return Translate(socket->SetRcvTimeo(value));
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
+ return Errno::SUCCESS;
+ }
+}
+
+Errno BSD::ShutdownImpl(s32 fd, s32 how) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+ const Network::ShutdownHow host_how = Translate(static_cast<ShutdownHow>(how));
+ return Translate(file_descriptors[fd]->socket->Shutdown(host_how));
+}
+
+std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+ return Translate(file_descriptors[fd]->socket->Recv(flags, message));
+}
+
+std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
+ std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ FileDescriptor& descriptor = *file_descriptors[fd];
+
+ Network::SockAddrIn addr_in{};
+ Network::SockAddrIn* p_addr_in = nullptr;
+ if (descriptor.is_connection_based) {
+ // Connection based file descriptors (e.g. TCP) zero addr
+ addr.clear();
+ } else {
+ p_addr_in = &addr_in;
+ }
+
+ // Apply flags
+ if ((flags & FLAG_MSG_DONTWAIT) != 0) {
+ flags &= ~FLAG_MSG_DONTWAIT;
+ if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
+ descriptor.socket->SetNonBlock(true);
+ }
+ }
+
+ const auto [ret, bsd_errno] = Translate(descriptor.socket->RecvFrom(flags, message, p_addr_in));
+
+ // Restore original state
+ if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
+ descriptor.socket->SetNonBlock(false);
+ }
+
+ if (p_addr_in) {
+ if (ret < 0) {
+ addr.clear();
+ } else {
+ ASSERT(addr.size() == sizeof(SockAddrIn));
+ const SockAddrIn result = Translate(addr_in);
+ std::memcpy(addr.data(), &result, sizeof(result));
+ }
+ }
+
+ return {ret, bsd_errno};
+}
+
+std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, const std::vector<u8>& message) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+ return Translate(file_descriptors[fd]->socket->Send(message, flags));
+}
+
+std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
+ const std::vector<u8>& addr) {
+ if (!IsFileDescriptorValid(fd)) {
+ return {-1, Errno::BADF};
+ }
+
+ Network::SockAddrIn addr_in;
+ Network::SockAddrIn* p_addr_in = nullptr;
+ if (!addr.empty()) {
+ ASSERT(addr.size() == sizeof(SockAddrIn));
+ SockAddrIn guest_addr_in;
+ std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in));
+ addr_in = Translate(guest_addr_in);
+ p_addr_in = &addr_in;
+ }
+
+ return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in));
+}
+
+Errno BSD::CloseImpl(s32 fd) {
+ if (!IsFileDescriptorValid(fd)) {
+ return Errno::BADF;
+ }
+
+ const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close());
+ if (bsd_errno != Errno::SUCCESS) {
+ return bsd_errno;
+ }
+
+ LOG_INFO(Service, "Close socket fd={}", fd);
+
+ file_descriptors[fd].reset();
+ return bsd_errno;
+}
+
+s32 BSD::FindFreeFileDescriptorHandle() noexcept {
+ for (s32 fd = 0; fd < static_cast<s32>(file_descriptors.size()); ++fd) {
+ if (!file_descriptors[fd]) {
+ return fd;
+ }
+ }
+ return -1;
+}
+
+bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
+ if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
+ LOG_ERROR(Service, "Invalid file descriptor handle={}", fd);
+ return false;
+ }
+ if (!file_descriptors[fd]) {
+ LOG_ERROR(Service, "File descriptor handle={} is not allocated", fd);
+ return false;
+ }
+ return true;
+}
+
+void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0); // ret
- rb.Push<u32>(0); // bsd errno
+ rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1);
+ rb.PushEnum(bsd_errno);
}
-BSD::BSD(const char* name) : ServiceFramework(name) {
+BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"},
@@ -121,32 +832,32 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
{3, nullptr, "SocketExempt"},
{4, nullptr, "Open"},
{5, &BSD::Select, "Select"},
- {6, nullptr, "Poll"},
+ {6, &BSD::Poll, "Poll"},
{7, nullptr, "Sysctl"},
- {8, nullptr, "Recv"},
- {9, nullptr, "RecvFrom"},
- {10, nullptr, "Send"},
+ {8, &BSD::Recv, "Recv"},
+ {9, &BSD::RecvFrom, "RecvFrom"},
+ {10, &BSD::Send, "Send"},
{11, &BSD::SendTo, "SendTo"},
- {12, nullptr, "Accept"},
+ {12, &BSD::Accept, "Accept"},
{13, &BSD::Bind, "Bind"},
{14, &BSD::Connect, "Connect"},
- {15, nullptr, "GetPeerName"},
- {16, nullptr, "GetSockName"},
- {17, nullptr, "GetSockOpt"},
+ {15, &BSD::GetPeerName, "GetPeerName"},
+ {16, &BSD::GetSockName, "GetSockName"},
+ {17, &BSD::GetSockOpt, "GetSockOpt"},
{18, &BSD::Listen, "Listen"},
{19, nullptr, "Ioctl"},
- {20, nullptr, "Fcntl"},
+ {20, &BSD::Fcntl, "Fcntl"},
{21, &BSD::SetSockOpt, "SetSockOpt"},
- {22, nullptr, "Shutdown"},
+ {22, &BSD::Shutdown, "Shutdown"},
{23, nullptr, "ShutdownAllSockets"},
- {24, nullptr, "Write"},
+ {24, &BSD::Write, "Write"},
{25, nullptr, "Read"},
{26, &BSD::Close, "Close"},
{27, nullptr, "DuplicateSocket"},
{28, nullptr, "GetResourceStatistics"},
{29, nullptr, "RecvMMsg"},
{30, nullptr, "SendMMsg"},
- {31, nullptr, "EventFd"},
+ {31, &BSD::EventFd, "EventFd"},
{32, nullptr, "RegisterResourceStatisticsName"},
{33, nullptr, "Initialize2"},
};
@@ -157,7 +868,7 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
BSD::~BSD() = default;
-BSDCFG::BSDCFG() : ServiceFramework{"bsdcfg"} {
+BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SetIfUp"},
diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h
index 3098e3baf..1d2df9c61 100644
--- a/src/core/hle/service/sockets/bsd.h
+++ b/src/core/hle/service/sockets/bsd.h
@@ -4,35 +4,174 @@
#pragma once
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "common/common_types.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/sockets/sockets.h"
+
+namespace Core {
+class System;
+}
+
+namespace Network {
+class Socket;
+}
namespace Service::Sockets {
class BSD final : public ServiceFramework<BSD> {
public:
- explicit BSD(const char* name);
+ explicit BSD(Core::System& system_, const char* name);
~BSD() override;
private:
+ /// Maximum number of file descriptors
+ static constexpr size_t MAX_FD = 128;
+
+ struct FileDescriptor {
+ std::unique_ptr<Network::Socket> socket;
+ s32 flags = 0;
+ bool is_connection_based = false;
+ };
+
+ struct PollWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 nfds;
+ s32 timeout;
+ std::vector<u8> read_buffer;
+ std::vector<u8> write_buffer;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct AcceptWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ std::vector<u8> write_buffer;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct ConnectWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ std::vector<u8> addr;
+ Errno bsd_errno{};
+ };
+
+ struct RecvWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct RecvFromWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ std::vector<u8> addr;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct SendWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
+ struct SendToWork {
+ void Execute(BSD* bsd);
+ void Response(Kernel::HLERequestContext& ctx);
+
+ s32 fd;
+ u32 flags;
+ std::vector<u8> message;
+ std::vector<u8> addr;
+ s32 ret{};
+ Errno bsd_errno{};
+ };
+
void RegisterClient(Kernel::HLERequestContext& ctx);
void StartMonitoring(Kernel::HLERequestContext& ctx);
void Socket(Kernel::HLERequestContext& ctx);
void Select(Kernel::HLERequestContext& ctx);
+ void Poll(Kernel::HLERequestContext& ctx);
+ void Accept(Kernel::HLERequestContext& ctx);
void Bind(Kernel::HLERequestContext& ctx);
void Connect(Kernel::HLERequestContext& ctx);
+ void GetPeerName(Kernel::HLERequestContext& ctx);
+ void GetSockName(Kernel::HLERequestContext& ctx);
+ void GetSockOpt(Kernel::HLERequestContext& ctx);
void Listen(Kernel::HLERequestContext& ctx);
+ void Fcntl(Kernel::HLERequestContext& ctx);
void SetSockOpt(Kernel::HLERequestContext& ctx);
+ void Shutdown(Kernel::HLERequestContext& ctx);
+ void Recv(Kernel::HLERequestContext& ctx);
+ void RecvFrom(Kernel::HLERequestContext& ctx);
+ void Send(Kernel::HLERequestContext& ctx);
void SendTo(Kernel::HLERequestContext& ctx);
+ void Write(Kernel::HLERequestContext& ctx);
void Close(Kernel::HLERequestContext& ctx);
+ void EventFd(Kernel::HLERequestContext& ctx);
+
+ template <typename Work>
+ void ExecuteWork(Kernel::HLERequestContext& ctx, Work work);
+
+ std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol);
+ std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
+ s32 nfds, s32 timeout);
+ std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno BindImpl(s32 fd, const std::vector<u8>& addr);
+ Errno ConnectImpl(s32 fd, const std::vector<u8>& addr);
+ Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer);
+ Errno ListenImpl(s32 fd, s32 backlog);
+ std::pair<s32, Errno> FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg);
+ Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval);
+ Errno ShutdownImpl(s32 fd, s32 how);
+ std::pair<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message);
+ std::pair<s32, Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
+ std::vector<u8>& addr);
+ std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, const std::vector<u8>& message);
+ std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
+ const std::vector<u8>& addr);
+ Errno CloseImpl(s32 fd);
+
+ s32 FindFreeFileDescriptorHandle() noexcept;
+ bool IsFileDescriptorValid(s32 fd) const noexcept;
+
+ void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
- /// Id to use for the next open file descriptor.
- u32 next_fd = 1;
+ std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
};
class BSDCFG final : public ServiceFramework<BSDCFG> {
public:
- explicit BSDCFG();
+ explicit BSDCFG(Core::System& system_);
~BSDCFG() override;
};
diff --git a/src/core/hle/service/sockets/ethc.cpp b/src/core/hle/service/sockets/ethc.cpp
index abbeb4c50..05681ca2d 100644
--- a/src/core/hle/service/sockets/ethc.cpp
+++ b/src/core/hle/service/sockets/ethc.cpp
@@ -6,7 +6,7 @@
namespace Service::Sockets {
-ETHC_C::ETHC_C() : ServiceFramework{"ethc:c"} {
+ETHC_C::ETHC_C(Core::System& system_) : ServiceFramework{system_, "ethc:c"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -23,7 +23,7 @@ ETHC_C::ETHC_C() : ServiceFramework{"ethc:c"} {
ETHC_C::~ETHC_C() = default;
-ETHC_I::ETHC_I() : ServiceFramework{"ethc:i"} {
+ETHC_I::ETHC_I(Core::System& system_) : ServiceFramework{system_, "ethc:i"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetReadableHandle"},
diff --git a/src/core/hle/service/sockets/ethc.h b/src/core/hle/service/sockets/ethc.h
index da2c7f741..71884182e 100644
--- a/src/core/hle/service/sockets/ethc.h
+++ b/src/core/hle/service/sockets/ethc.h
@@ -6,17 +6,21 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Sockets {
class ETHC_C final : public ServiceFramework<ETHC_C> {
public:
- explicit ETHC_C();
+ explicit ETHC_C(Core::System& system_);
~ETHC_C() override;
};
class ETHC_I final : public ServiceFramework<ETHC_I> {
public:
- explicit ETHC_I();
+ explicit ETHC_I(Core::System& system_);
~ETHC_I() override;
};
diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp
index 40d781124..51c3739bb 100644
--- a/src/core/hle/service/sockets/nsd.cpp
+++ b/src/core/hle/service/sockets/nsd.cpp
@@ -6,7 +6,7 @@
namespace Service::Sockets {
-NSD::NSD(const char* name) : ServiceFramework(name) {
+NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
// clang-format off
static const FunctionInfo functions[] = {
{10, nullptr, "GetSettingName"},
diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h
index d842e3232..becf93125 100644
--- a/src/core/hle/service/sockets/nsd.h
+++ b/src/core/hle/service/sockets/nsd.h
@@ -4,14 +4,17 @@
#pragma once
-#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Sockets {
class NSD final : public ServiceFramework<NSD> {
public:
- explicit NSD(const char* name);
+ explicit NSD(Core::System& system_, const char* name);
~NSD() override;
};
diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp
index e3017451f..3a6329f56 100644
--- a/src/core/hle/service/sockets/sfdnsres.cpp
+++ b/src/core/hle/service/sockets/sfdnsres.cpp
@@ -7,25 +7,7 @@
namespace Service::Sockets {
-void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) {
- struct Parameters {
- u8 use_nsd_resolve;
- u32 unknown;
- u64 process_id;
- };
-
- IPC::RequestParser rp{ctx};
- const auto parameters = rp.PopRaw<Parameters>();
-
- LOG_WARNING(Service,
- "(STUBBED) called. use_nsd_resolve={}, unknown=0x{:08X}, process_id=0x{:016X}",
- parameters.use_nsd_resolve, parameters.unknown, parameters.process_id);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-}
-
-SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") {
+SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"} {
static const FunctionInfo functions[] = {
{0, nullptr, "SetDnsAddressesPrivate"},
{1, nullptr, "GetDnsAddressPrivate"},
@@ -49,4 +31,22 @@ SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") {
SFDNSRES::~SFDNSRES() = default;
+void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) {
+ struct Parameters {
+ u8 use_nsd_resolve;
+ u32 unknown;
+ u64 process_id;
+ };
+
+ IPC::RequestParser rp{ctx};
+ const auto parameters = rp.PopRaw<Parameters>();
+
+ LOG_WARNING(Service,
+ "(STUBBED) called. use_nsd_resolve={}, unknown=0x{:08X}, process_id=0x{:016X}",
+ parameters.use_nsd_resolve, parameters.unknown, parameters.process_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h
index acd3647bb..faa6b7d0d 100644
--- a/src/core/hle/service/sockets/sfdnsres.h
+++ b/src/core/hle/service/sockets/sfdnsres.h
@@ -7,11 +7,15 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Sockets {
class SFDNSRES final : public ServiceFramework<SFDNSRES> {
public:
- explicit SFDNSRES();
+ explicit SFDNSRES(Core::System& system_);
~SFDNSRES() override;
private:
diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp
index 08d2d306a..96f73bce3 100644
--- a/src/core/hle/service/sockets/sockets.cpp
+++ b/src/core/hle/service/sockets/sockets.cpp
@@ -10,18 +10,18 @@
namespace Service::Sockets {
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager);
- std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager);
- std::make_shared<BSDCFG>()->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<BSD>(system, "bsd:s")->InstallAsService(service_manager);
+ std::make_shared<BSD>(system, "bsd:u")->InstallAsService(service_manager);
+ std::make_shared<BSDCFG>(system)->InstallAsService(service_manager);
- std::make_shared<ETHC_C>()->InstallAsService(service_manager);
- std::make_shared<ETHC_I>()->InstallAsService(service_manager);
+ std::make_shared<ETHC_C>(system)->InstallAsService(service_manager);
+ std::make_shared<ETHC_I>(system)->InstallAsService(service_manager);
- std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager);
- std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager);
+ std::make_shared<NSD>(system, "nsd:a")->InstallAsService(service_manager);
+ std::make_shared<NSD>(system, "nsd:u")->InstallAsService(service_manager);
- std::make_shared<SFDNSRES>()->InstallAsService(service_manager);
+ std::make_shared<SFDNSRES>(system)->InstallAsService(service_manager);
}
} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h
index ca8a6a7e0..5a65ed2a9 100644
--- a/src/core/hle/service/sockets/sockets.h
+++ b/src/core/hle/service/sockets/sockets.h
@@ -4,11 +4,99 @@
#pragma once
+#include "common/common_types.h"
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Sockets {
+enum class Errno : u32 {
+ SUCCESS = 0,
+ BADF = 9,
+ AGAIN = 11,
+ INVAL = 22,
+ MFILE = 24,
+ NOTCONN = 107,
+};
+
+enum class Domain : u32 {
+ INET = 2,
+};
+
+enum class Type : u32 {
+ STREAM = 1,
+ DGRAM = 2,
+ RAW = 3,
+ SEQPACKET = 5,
+};
+
+enum class Protocol : u32 {
+ UNSPECIFIED = 0,
+ ICMP = 1,
+ TCP = 6,
+ UDP = 17,
+};
+
+enum class OptName : u32 {
+ REUSEADDR = 0x4,
+ BROADCAST = 0x20,
+ LINGER = 0x80,
+ SNDBUF = 0x1001,
+ RCVBUF = 0x1002,
+ SNDTIMEO = 0x1005,
+ RCVTIMEO = 0x1006,
+};
+
+enum class ShutdownHow : s32 {
+ RD = 0,
+ WR = 1,
+ RDWR = 2,
+};
+
+enum class FcntlCmd : s32 {
+ GETFL = 3,
+ SETFL = 4,
+};
+
+struct SockAddrIn {
+ u8 len;
+ u8 family;
+ u16 portno;
+ std::array<u8, 4> ip;
+ std::array<u8, 8> zeroes;
+};
+
+enum class PollEvents : u16 {
+ // Using Pascal case because IN is a macro on Windows.
+ In = 1 << 0,
+ Pri = 1 << 1,
+ Out = 1 << 2,
+ Err = 1 << 3,
+ Hup = 1 << 4,
+ Nval = 1 << 5,
+};
+
+DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
+
+struct PollFD {
+ s32 fd;
+ PollEvents events;
+ PollEvents revents;
+};
+
+struct Linger {
+ u32 onoff;
+ u32 linger;
+};
+
+constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
+
+constexpr u32 FLAG_O_NONBLOCK = 0x800;
+
/// Registers all Sockets services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
new file mode 100644
index 000000000..ca61d72ca
--- /dev/null
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -0,0 +1,166 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <utility>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/service/sockets/sockets.h"
+#include "core/hle/service/sockets/sockets_translate.h"
+#include "core/network/network.h"
+
+namespace Service::Sockets {
+
+Errno Translate(Network::Errno value) {
+ switch (value) {
+ case Network::Errno::SUCCESS:
+ return Errno::SUCCESS;
+ case Network::Errno::BADF:
+ return Errno::BADF;
+ case Network::Errno::AGAIN:
+ return Errno::AGAIN;
+ case Network::Errno::INVAL:
+ return Errno::INVAL;
+ case Network::Errno::MFILE:
+ return Errno::MFILE;
+ case Network::Errno::NOTCONN:
+ return Errno::NOTCONN;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
+ return Errno::SUCCESS;
+ }
+}
+
+std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value) {
+ return {value.first, Translate(value.second)};
+}
+
+Network::Domain Translate(Domain domain) {
+ switch (domain) {
+ case Domain::INET:
+ return Network::Domain::INET;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", domain);
+ return {};
+ }
+}
+
+Domain Translate(Network::Domain domain) {
+ switch (domain) {
+ case Network::Domain::INET:
+ return Domain::INET;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", domain);
+ return {};
+ }
+}
+
+Network::Type Translate(Type type) {
+ switch (type) {
+ case Type::STREAM:
+ return Network::Type::STREAM;
+ case Type::DGRAM:
+ return Network::Type::DGRAM;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented type={}", type);
+ return Network::Type{};
+ }
+}
+
+Network::Protocol Translate(Type type, Protocol protocol) {
+ switch (protocol) {
+ case Protocol::UNSPECIFIED:
+ LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type");
+ switch (type) {
+ case Type::DGRAM:
+ return Network::Protocol::UDP;
+ case Type::STREAM:
+ return Network::Protocol::TCP;
+ default:
+ return Network::Protocol::TCP;
+ }
+ case Protocol::TCP:
+ return Network::Protocol::TCP;
+ case Protocol::UDP:
+ return Network::Protocol::UDP;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
+ return Network::Protocol::TCP;
+ }
+}
+
+Network::PollEvents TranslatePollEventsToHost(PollEvents flags) {
+ Network::PollEvents result{};
+ const auto translate = [&result, &flags](PollEvents from, Network::PollEvents to) {
+ if (True(flags & from)) {
+ flags &= ~from;
+ result |= to;
+ }
+ };
+ translate(PollEvents::In, Network::PollEvents::In);
+ translate(PollEvents::Pri, Network::PollEvents::Pri);
+ translate(PollEvents::Out, Network::PollEvents::Out);
+ translate(PollEvents::Err, Network::PollEvents::Err);
+ translate(PollEvents::Hup, Network::PollEvents::Hup);
+ translate(PollEvents::Nval, Network::PollEvents::Nval);
+
+ UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags);
+ return result;
+}
+
+PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) {
+ PollEvents result{};
+ const auto translate = [&result, &flags](Network::PollEvents from, PollEvents to) {
+ if (True(flags & from)) {
+ flags &= ~from;
+ result |= to;
+ }
+ };
+
+ translate(Network::PollEvents::In, PollEvents::In);
+ translate(Network::PollEvents::Pri, PollEvents::Pri);
+ translate(Network::PollEvents::Out, PollEvents::Out);
+ translate(Network::PollEvents::Err, PollEvents::Err);
+ translate(Network::PollEvents::Hup, PollEvents::Hup);
+ translate(Network::PollEvents::Nval, PollEvents::Nval);
+
+ UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags);
+ return result;
+}
+
+Network::SockAddrIn Translate(SockAddrIn value) {
+ ASSERT(value.len == 0 || value.len == sizeof(value));
+
+ return {
+ .family = Translate(static_cast<Domain>(value.family)),
+ .ip = value.ip,
+ .portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
+ };
+}
+
+SockAddrIn Translate(Network::SockAddrIn value) {
+ return {
+ .len = sizeof(SockAddrIn),
+ .family = static_cast<u8>(Translate(value.family)),
+ .portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
+ .ip = value.ip,
+ .zeroes = {},
+ };
+}
+
+Network::ShutdownHow Translate(ShutdownHow how) {
+ switch (how) {
+ case ShutdownHow::RD:
+ return Network::ShutdownHow::RD;
+ case ShutdownHow::WR:
+ return Network::ShutdownHow::WR;
+ case ShutdownHow::RDWR:
+ return Network::ShutdownHow::RDWR;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented how={}", how);
+ return {};
+ }
+}
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h
new file mode 100644
index 000000000..057d1ff22
--- /dev/null
+++ b/src/core/hle/service/sockets/sockets_translate.h
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <utility>
+
+#include "common/common_types.h"
+#include "core/hle/service/sockets/sockets.h"
+#include "core/network/network.h"
+
+namespace Service::Sockets {
+
+/// Translate abstract errno to guest errno
+Errno Translate(Network::Errno value);
+
+/// Translate abstract return value errno pair to guest return value errno pair
+std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value);
+
+/// Translate guest domain to abstract domain
+Network::Domain Translate(Domain domain);
+
+/// Translate abstract domain to guest domain
+Domain Translate(Network::Domain domain);
+
+/// Translate guest type to abstract type
+Network::Type Translate(Type type);
+
+/// Translate guest protocol to abstract protocol
+Network::Protocol Translate(Type type, Protocol protocol);
+
+/// Translate abstract poll event flags to guest poll event flags
+Network::PollEvents TranslatePollEventsToHost(PollEvents flags);
+
+/// Translate guest poll event flags to abstract poll event flags
+PollEvents TranslatePollEventsToGuest(Network::PollEvents flags);
+
+/// Translate guest socket address structure to abstract socket address structure
+Network::SockAddrIn Translate(SockAddrIn value);
+
+/// Translate abstract socket address structure to guest socket address structure
+SockAddrIn Translate(Network::SockAddrIn value);
+
+/// Translate guest shutdown mode to abstract shutdown mode
+Network::ShutdownHow Translate(ShutdownHow how);
+
+} // namespace Service::Sockets
diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp
index 674928798..1beca417c 100644
--- a/src/core/hle/service/spl/csrng.cpp
+++ b/src/core/hle/service/spl/csrng.cpp
@@ -6,7 +6,8 @@
namespace Service::SPL {
-CSRNG::CSRNG(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "csrng") {
+CSRNG::CSRNG(Core::System& system_, std::shared_ptr<Module> module_)
+ : Interface(system_, std::move(module_), "csrng") {
static const FunctionInfo functions[] = {
{0, &CSRNG::GetRandomBytes, "GetRandomBytes"},
};
diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h
index 764d5ceb0..5c0bd2199 100644
--- a/src/core/hle/service/spl/csrng.h
+++ b/src/core/hle/service/spl/csrng.h
@@ -6,11 +6,15 @@
#include "core/hle/service/spl/module.h"
+namespace Core {
+class System;
+}
+
namespace Service::SPL {
class CSRNG final : public Module::Interface {
public:
- explicit CSRNG(std::shared_ptr<Module> module);
+ explicit CSRNG(Core::System& system_, std::shared_ptr<Module> module_);
~CSRNG() override;
};
diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp
index 865ed3b91..dea6b0fe0 100644
--- a/src/core/hle/service/spl/module.cpp
+++ b/src/core/hle/service/spl/module.cpp
@@ -17,8 +17,9 @@
namespace Service::SPL {
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
- : ServiceFramework(name), module(std::move(module)),
+Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_,
+ const char* name)
+ : ServiceFramework{system_, name}, module{std::move(module_)},
rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {}
Module::Interface::~Interface() = default;
@@ -38,10 +39,10 @@ void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
-void InstallInterfaces(SM::ServiceManager& service_manager) {
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
auto module = std::make_shared<Module>();
- std::make_shared<CSRNG>(module)->InstallAsService(service_manager);
- std::make_shared<SPL>(module)->InstallAsService(service_manager);
+ std::make_shared<CSRNG>(system, module)->InstallAsService(service_manager);
+ std::make_shared<SPL>(system, module)->InstallAsService(service_manager);
}
} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h
index afa1f0295..71855c1bf 100644
--- a/src/core/hle/service/spl/module.h
+++ b/src/core/hle/service/spl/module.h
@@ -7,13 +7,18 @@
#include <random>
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::SPL {
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, const char* name);
+ explicit Interface(Core::System& system_, std::shared_ptr<Module> module_,
+ const char* name);
~Interface() override;
void GetRandomBytes(Kernel::HLERequestContext& ctx);
@@ -27,6 +32,6 @@ public:
};
/// Registers all SPL services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::SPL
diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp
index 773551464..3fabc2c79 100644
--- a/src/core/hle/service/spl/spl.cpp
+++ b/src/core/hle/service/spl/spl.cpp
@@ -6,7 +6,8 @@
namespace Service::SPL {
-SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "spl:") {
+SPL::SPL(Core::System& system_, std::shared_ptr<Module> module_)
+ : Interface(system_, std::move(module_), "spl:") {
static const FunctionInfo functions[] = {
{0, nullptr, "GetConfig"},
{1, nullptr, "ModularExponentiate"},
diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h
index 3637d1623..d27d16b86 100644
--- a/src/core/hle/service/spl/spl.h
+++ b/src/core/hle/service/spl/spl.h
@@ -6,11 +6,15 @@
#include "core/hle/service/spl/module.h"
+namespace Core {
+class System;
+}
+
namespace Service::SPL {
class SPL final : public Module::Interface {
public:
- explicit SPL(std::shared_ptr<Module> module);
+ explicit SPL(Core::System& system_, std::shared_ptr<Module> module_);
~SPL() override;
};
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index 1ba8c19a0..dc2baca4a 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -12,7 +12,7 @@ namespace Service::SSL {
class ISslConnection final : public ServiceFramework<ISslConnection> {
public:
- ISslConnection() : ServiceFramework("ISslConnection") {
+ explicit ISslConnection(Core::System& system_) : ServiceFramework{system_, "ISslConnection"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "SetSocketDescriptor"},
@@ -52,7 +52,7 @@ public:
class ISslContext final : public ServiceFramework<ISslContext> {
public:
- ISslContext() : ServiceFramework("ISslContext") {
+ explicit ISslContext(Core::System& system_) : ServiceFramework{system_, "ISslContext"} {
static const FunctionInfo functions[] = {
{0, &ISslContext::SetOption, "SetOption"},
{1, nullptr, "GetOption"},
@@ -92,13 +92,13 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISslConnection>();
+ rb.PushIpcInterface<ISslConnection>(system);
}
};
class SSL final : public ServiceFramework<SSL> {
public:
- explicit SSL() : ServiceFramework{"ssl"} {
+ explicit SSL(Core::System& system_) : ServiceFramework{system_, "ssl"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &SSL::CreateContext, "CreateContext"},
@@ -123,7 +123,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISslContext>();
+ rb.PushIpcInterface<ISslContext>(system);
}
void SetInterfaceVersion(Kernel::HLERequestContext& ctx) {
@@ -137,8 +137,8 @@ private:
}
};
-void InstallInterfaces(SM::ServiceManager& service_manager) {
- std::make_shared<SSL>()->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
+ std::make_shared<SSL>(system)->InstallAsService(service_manager);
}
} // namespace Service::SSL
diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h
index 5cb04c3b9..a3aa4b4b5 100644
--- a/src/core/hle/service/ssl/ssl.h
+++ b/src/core/hle/service/ssl/ssl.h
@@ -4,6 +4,10 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
@@ -11,6 +15,6 @@ class ServiceManager;
namespace Service::SSL {
/// Registers all SSL services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::SSL
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
index 72e1921ec..b78892223 100644
--- a/src/core/hle/service/time/clock_types.h
+++ b/src/core/hle/service/time/clock_types.h
@@ -73,19 +73,19 @@ struct TimeSpanType {
static_assert(sizeof(TimeSpanType) == 8, "TimeSpanType is incorrect size");
struct ClockSnapshot {
- SystemClockContext user_context{};
- SystemClockContext network_context{};
- s64 user_time{};
- s64 network_time{};
- TimeZone::CalendarTime user_calendar_time{};
- TimeZone::CalendarTime network_calendar_time{};
- TimeZone::CalendarAdditionalInfo user_calendar_additional_time{};
- TimeZone::CalendarAdditionalInfo network_calendar_additional_time{};
- SteadyClockTimePoint steady_clock_time_point{};
- TimeZone::LocationName location_name{};
- u8 is_automatic_correction_enabled{};
- u8 type{};
- INSERT_PADDING_BYTES(0x2);
+ SystemClockContext user_context;
+ SystemClockContext network_context;
+ s64 user_time;
+ s64 network_time;
+ TimeZone::CalendarTime user_calendar_time;
+ TimeZone::CalendarTime network_calendar_time;
+ TimeZone::CalendarAdditionalInfo user_calendar_additional_time;
+ TimeZone::CalendarAdditionalInfo network_calendar_additional_time;
+ SteadyClockTimePoint steady_clock_time_point;
+ TimeZone::LocationName location_name;
+ u8 is_automatic_correction_enabled;
+ u8 type;
+ INSERT_PADDING_BYTES_NOINIT(0x2);
static ResultCode GetCurrentTime(s64& current_time,
const SteadyClockTimePoint& steady_clock_time_point,
diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp
index ba8fd6152..a01d9e0ff 100644
--- a/src/core/hle/service/time/interface.cpp
+++ b/src/core/hle/service/time/interface.cpp
@@ -7,7 +7,7 @@
namespace Service::Time {
Time::Time(std::shared_ptr<Module> module, Core::System& system, const char* name)
- : Module::Interface(std::move(module), system, name) {
+ : Interface(std::move(module), system, name) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
diff --git a/src/core/hle/service/time/standard_user_system_clock_core.cpp b/src/core/hle/service/time/standard_user_system_clock_core.cpp
index 8af17091c..b9faa474e 100644
--- a/src/core/hle/service/time/standard_user_system_clock_core.cpp
+++ b/src/core/hle/service/time/standard_user_system_clock_core.cpp
@@ -4,7 +4,7 @@
#include "common/assert.h"
#include "core/core.h"
-#include "core/hle/kernel/writable_event.h"
+#include "core/hle/kernel/k_event.h"
#include "core/hle/service/time/standard_local_system_clock_core.h"
#include "core/hle/service/time/standard_network_system_clock_core.h"
#include "core/hle/service/time/standard_user_system_clock_core.h"
@@ -18,8 +18,10 @@ StandardUserSystemClockCore::StandardUserSystemClockCore(
local_system_clock_core{local_system_clock_core},
network_system_clock_core{network_system_clock_core}, auto_correction_enabled{},
auto_correction_time{SteadyClockTimePoint::GetRandom()},
- auto_correction_event{Kernel::WritableEvent::CreateEventPair(
- system.Kernel(), "StandardUserSystemClockCore:AutoCorrectionEvent")} {}
+ auto_correction_event{Kernel::KEvent::Create(
+ system.Kernel(), "StandardUserSystemClockCore:AutoCorrectionEvent")} {
+ auto_correction_event->Initialize();
+}
ResultCode StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(Core::System& system,
bool value) {
diff --git a/src/core/hle/service/time/standard_user_system_clock_core.h b/src/core/hle/service/time/standard_user_system_clock_core.h
index ef3d468b7..aac44d72f 100644
--- a/src/core/hle/service/time/standard_user_system_clock_core.h
+++ b/src/core/hle/service/time/standard_user_system_clock_core.h
@@ -4,7 +4,6 @@
#pragma once
-#include "core/hle/kernel/writable_event.h"
#include "core/hle/service/time/clock_types.h"
#include "core/hle/service/time/system_clock_core.h"
@@ -12,6 +11,10 @@ namespace Core {
class System;
}
+namespace Kernel {
+class KEvent;
+}
+
namespace Service::Time::Clock {
class StandardLocalSystemClockCore;
@@ -51,7 +54,7 @@ private:
StandardNetworkSystemClockCore& network_system_clock_core;
bool auto_correction_enabled{};
SteadyClockTimePoint auto_correction_time;
- Kernel::EventPair auto_correction_event;
+ std::shared_ptr<Kernel::KEvent> auto_correction_event;
};
} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/system_clock_context_update_callback.cpp b/src/core/hle/service/time/system_clock_context_update_callback.cpp
index 5cdb80703..bca7d869e 100644
--- a/src/core/hle/service/time/system_clock_context_update_callback.cpp
+++ b/src/core/hle/service/time/system_clock_context_update_callback.cpp
@@ -2,7 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/hle/kernel/writable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/time/errors.h"
#include "core/hle/service/time/system_clock_context_update_callback.h"
@@ -21,7 +21,7 @@ bool SystemClockContextUpdateCallback::NeedUpdate(const SystemClockContext& valu
}
void SystemClockContextUpdateCallback::RegisterOperationEvent(
- std::shared_ptr<Kernel::WritableEvent>&& writable_event) {
+ std::shared_ptr<Kernel::KWritableEvent>&& writable_event) {
operation_event_list.emplace_back(std::move(writable_event));
}
diff --git a/src/core/hle/service/time/system_clock_context_update_callback.h b/src/core/hle/service/time/system_clock_context_update_callback.h
index 2b0fa7e75..797954958 100644
--- a/src/core/hle/service/time/system_clock_context_update_callback.h
+++ b/src/core/hle/service/time/system_clock_context_update_callback.h
@@ -9,7 +9,7 @@
#include "core/hle/service/time/clock_types.h"
namespace Kernel {
-class WritableEvent;
+class KWritableEvent;
}
namespace Service::Time::Clock {
@@ -24,7 +24,7 @@ public:
bool NeedUpdate(const SystemClockContext& value) const;
- void RegisterOperationEvent(std::shared_ptr<Kernel::WritableEvent>&& writable_event);
+ void RegisterOperationEvent(std::shared_ptr<Kernel::KWritableEvent>&& writable_event);
void BroadcastOperationEvent();
@@ -37,7 +37,7 @@ protected:
private:
bool has_context{};
- std::vector<std::shared_ptr<Kernel::WritableEvent>> operation_event_list;
+ std::vector<std::shared_ptr<Kernel::KWritableEvent>> operation_event_list;
};
} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index ee4fa4b48..18629dd7e 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -10,7 +10,8 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
-#include "core/hle/kernel/scheduler.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/service/time/interface.h"
#include "core/hle/service/time/time.h"
#include "core/hle/service/time/time_sharedmemory.h"
@@ -20,8 +21,8 @@ namespace Service::Time {
class ISystemClock final : public ServiceFramework<ISystemClock> {
public:
- explicit ISystemClock(Clock::SystemClockCore& clock_core, Core::System& system)
- : ServiceFramework("ISystemClock"), clock_core{clock_core}, system{system} {
+ explicit ISystemClock(Clock::SystemClockCore& clock_core_, Core::System& system_)
+ : ServiceFramework{system_, "ISystemClock"}, clock_core{clock_core_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemClock::GetCurrentTime, "GetCurrentTime"},
@@ -81,13 +82,12 @@ private:
}
Clock::SystemClockCore& clock_core;
- Core::System& system;
};
class ISteadyClock final : public ServiceFramework<ISteadyClock> {
public:
- explicit ISteadyClock(Clock::SteadyClockCore& clock_core, Core::System& system)
- : ServiceFramework("ISteadyClock"), clock_core{clock_core}, system{system} {
+ explicit ISteadyClock(Clock::SteadyClockCore& clock_core_, Core::System& system_)
+ : ServiceFramework{system_, "ISteadyClock"}, clock_core{clock_core_} {
static const FunctionInfo functions[] = {
{0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"},
{2, nullptr, "GetTestOffset"},
@@ -118,14 +118,13 @@ private:
}
Clock::SteadyClockCore& clock_core;
- Core::System& system;
};
ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
- Kernel::Thread* thread, Clock::SystemClockContext user_context,
+ Kernel::KThread* thread, Clock::SystemClockContext user_context,
Clock::SystemClockContext network_context, u8 type, Clock::ClockSnapshot& clock_snapshot) {
- auto& time_manager{module->GetTimeManager()};
+ auto& time_manager{system.GetTimeManager()};
clock_snapshot.is_automatic_correction_enabled =
time_manager.GetStandardUserSystemClockCore().IsAutomaticCorrectionEnabled();
@@ -182,7 +181,7 @@ void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ct
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardUserSystemClockCore(),
+ rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardUserSystemClockCore(),
system);
}
@@ -190,7 +189,7 @@ void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext&
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardNetworkSystemClockCore(),
+ rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardNetworkSystemClockCore(),
system);
}
@@ -198,29 +197,29 @@ void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISteadyClock>(module->GetTimeManager().GetStandardSteadyClockCore(),
- system);
+ rb.PushIpcInterface<ISteadyClock>(system.GetTimeManager().GetStandardSteadyClockCore(), system);
}
void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ITimeZoneService>(module->GetTimeManager().GetTimeZoneContentManager());
+ rb.PushIpcInterface<ITimeZoneService>(system,
+ system.GetTimeManager().GetTimeZoneContentManager());
}
void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardLocalSystemClockCore(),
+ rb.PushIpcInterface<ISystemClock>(system.GetTimeManager().GetStandardLocalSystemClockCore(),
system);
}
void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient(
Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
- auto& clock_core{module->GetTimeManager().GetStandardNetworkSystemClockCore()};
+ auto& clock_core{system.GetTimeManager().GetStandardNetworkSystemClockCore()};
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(clock_core.IsStandardNetworkSystemClockAccuracySufficient(system));
@@ -229,7 +228,7 @@ void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient(
void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
- auto& steady_clock_core{module->GetTimeManager().GetStandardSteadyClockCore()};
+ auto& steady_clock_core{system.GetTimeManager().GetStandardSteadyClockCore()};
if (!steady_clock_core.IsInitialized()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_UNINITIALIZED_CLOCK);
@@ -262,8 +261,8 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
Clock::SystemClockContext user_context{};
if (const ResultCode result{
- module->GetTimeManager().GetStandardUserSystemClockCore().GetClockContext(
- system, user_context)};
+ system.GetTimeManager().GetStandardUserSystemClockCore().GetClockContext(system,
+ user_context)};
result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
@@ -271,7 +270,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
}
Clock::SystemClockContext network_context{};
if (const ResultCode result{
- module->GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext(
+ system.GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext(
system, network_context)};
result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -372,16 +371,17 @@ void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& c
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(module->GetTimeManager().GetSharedMemory().GetSharedMemoryHolder());
+ rb.PushCopyObjects(SharedFrom(&system.Kernel().GetTimeSharedMem()));
}
-Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
- : ServiceFramework(name), module{std::move(module)}, system{system} {}
+Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
+ const char* name)
+ : ServiceFramework{system_, name}, module{std::move(module_)} {}
Module::Interface::~Interface() = default;
void InstallInterfaces(Core::System& system) {
- auto module{std::make_shared<Module>(system)};
+ auto module{std::make_shared<Module>()};
std::make_shared<Time>(module, system, "time:a")->InstallAsService(system.ServiceManager());
std::make_shared<Time>(module, system, "time:s")->InstallAsService(system.ServiceManager());
std::make_shared<Time>(module, system, "time:u")->InstallAsService(system.ServiceManager());
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index 41f3002e9..4154c7ee9 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.h
@@ -16,11 +16,12 @@ namespace Service::Time {
class Module final {
public:
- Module(Core::System& system) : time_manager{system} {}
+ Module() = default;
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name);
+ explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
+ const char* name);
~Interface() override;
void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx);
@@ -38,21 +39,13 @@ public:
private:
ResultCode GetClockSnapshotFromSystemClockContextInternal(
- Kernel::Thread* thread, Clock::SystemClockContext user_context,
+ Kernel::KThread* thread, Clock::SystemClockContext user_context,
Clock::SystemClockContext network_context, u8 type,
Clock::ClockSnapshot& cloc_snapshot);
protected:
std::shared_ptr<Module> module;
- Core::System& system;
};
-
- TimeManager& GetTimeManager() {
- return time_manager;
- }
-
-private:
- TimeManager time_manager;
};
/// Registers all Time services with the specified service manager.
diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp
index b4dfe45e5..858623e2b 100644
--- a/src/core/hle/service/time/time_manager.cpp
+++ b/src/core/hle/service/time/time_manager.cpp
@@ -22,125 +22,282 @@ static std::chrono::seconds GetSecondsSinceEpoch() {
Settings::values.custom_rtc_differential;
}
-static s64 GetExternalTimeZoneOffset() {
- // With "auto" timezone setting, we use the external system's timezone offset
- if (Settings::GetTimeZoneString() == "auto") {
- return Common::TimeZone::GetCurrentOffsetSeconds().count();
- }
- return 0;
-}
-
static s64 GetExternalRtcValue() {
- return GetSecondsSinceEpoch().count() + GetExternalTimeZoneOffset();
-}
-
-TimeManager::TimeManager(Core::System& system)
- : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core},
- standard_network_system_clock_core{standard_steady_clock_core},
- standard_user_system_clock_core{standard_local_system_clock_core,
- standard_network_system_clock_core, system},
- ephemeral_network_system_clock_core{tick_based_steady_clock_core},
- local_system_clock_context_writer{
- std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)},
- network_system_clock_context_writer{
- std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)},
- ephemeral_network_system_clock_context_writer{
- std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()},
- time_zone_content_manager{*this, system} {
-
- const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())};
- SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {});
- SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds());
- SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy);
- SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom());
- SetupEphemeralNetworkSystemClock();
+ return GetSecondsSinceEpoch().count() + TimeManager::GetExternalTimeZoneOffset();
}
-TimeManager::~TimeManager() = default;
+struct TimeManager::Impl final {
+ explicit Impl(Core::System& system)
+ : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core},
+ standard_network_system_clock_core{standard_steady_clock_core},
+ standard_user_system_clock_core{standard_local_system_clock_core,
+ standard_network_system_clock_core, system},
+ ephemeral_network_system_clock_core{tick_based_steady_clock_core},
+ local_system_clock_context_writer{
+ std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)},
+ network_system_clock_context_writer{
+ std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)},
+ ephemeral_network_system_clock_context_writer{
+ std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()},
+ time_zone_content_manager{system} {
-void TimeManager::SetupTimeZoneManager(std::string location_name,
- Clock::SteadyClockTimePoint time_zone_updated_time_point,
- std::size_t total_location_name_count,
- u128 time_zone_rule_version,
- FileSys::VirtualFile& vfs_file) {
- if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule(
- location_name, vfs_file) != RESULT_SUCCESS) {
- UNREACHABLE();
- return;
- }
-
- time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point);
- time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount(
- total_location_name_count);
- time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion(time_zone_rule_version);
- time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized();
-}
-
-void TimeManager::SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id,
- Clock::TimeSpanType setup_value,
- Clock::TimeSpanType internal_offset,
- bool is_rtc_reset_detected) {
- standard_steady_clock_core.SetClockSourceId(clock_source_id);
- standard_steady_clock_core.SetSetupValue(setup_value);
- standard_steady_clock_core.SetInternalOffset(internal_offset);
- standard_steady_clock_core.MarkAsInitialized();
-
- const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)};
- shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point);
-}
-
-void TimeManager::SetupStandardLocalSystemClock(Core::System& system,
- Clock::SystemClockContext clock_context,
- s64 posix_time) {
- standard_local_system_clock_core.SetUpdateCallbackInstance(local_system_clock_context_writer);
-
- const auto current_time_point{
- standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)};
- if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) {
- standard_local_system_clock_core.SetSystemClockContext(clock_context);
- } else {
- if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) != RESULT_SUCCESS) {
+ const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())};
+ SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {});
+ SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds());
+ SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy);
+ SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom());
+ SetupEphemeralNetworkSystemClock();
+ }
+
+ ~Impl() = default;
+
+ Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() {
+ return standard_steady_clock_core;
+ }
+
+ const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const {
+ return standard_steady_clock_core;
+ }
+
+ Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() {
+ return standard_local_system_clock_core;
+ }
+
+ const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const {
+ return standard_local_system_clock_core;
+ }
+
+ Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() {
+ return standard_network_system_clock_core;
+ }
+
+ const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const {
+ return standard_network_system_clock_core;
+ }
+
+ Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() {
+ return standard_user_system_clock_core;
+ }
+
+ const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const {
+ return standard_user_system_clock_core;
+ }
+
+ TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() {
+ return time_zone_content_manager;
+ }
+
+ const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const {
+ return time_zone_content_manager;
+ }
+
+ SharedMemory& GetSharedMemory() {
+ return shared_memory;
+ }
+
+ const SharedMemory& GetSharedMemory() const {
+ return shared_memory;
+ }
+
+ void SetupTimeZoneManager(std::string location_name,
+ Clock::SteadyClockTimePoint time_zone_updated_time_point,
+ std::size_t total_location_name_count, u128 time_zone_rule_version,
+ FileSys::VirtualFile& vfs_file) {
+ if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule(
+ location_name, vfs_file) != RESULT_SUCCESS) {
UNREACHABLE();
return;
}
+
+ time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point);
+ time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount(
+ total_location_name_count);
+ time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion(
+ time_zone_rule_version);
+ time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized();
}
- standard_local_system_clock_core.MarkAsInitialized();
-}
+ static s64 GetExternalTimeZoneOffset() {
+ // With "auto" timezone setting, we use the external system's timezone offset
+ if (Settings::GetTimeZoneString() == "auto") {
+ return Common::TimeZone::GetCurrentOffsetSeconds().count();
+ }
+ return 0;
+ }
-void TimeManager::SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context,
- Clock::TimeSpanType sufficient_accuracy) {
- standard_network_system_clock_core.SetUpdateCallbackInstance(
- network_system_clock_context_writer);
+ void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id,
+ Clock::TimeSpanType setup_value,
+ Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected) {
+ standard_steady_clock_core.SetClockSourceId(clock_source_id);
+ standard_steady_clock_core.SetSetupValue(setup_value);
+ standard_steady_clock_core.SetInternalOffset(internal_offset);
+ standard_steady_clock_core.MarkAsInitialized();
- if (standard_network_system_clock_core.SetSystemClockContext(clock_context) != RESULT_SUCCESS) {
- UNREACHABLE();
- return;
+ const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)};
+ shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point);
}
- standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy(
- sufficient_accuracy);
- standard_network_system_clock_core.MarkAsInitialized();
-}
+ void SetupStandardLocalSystemClock(Core::System& system,
+ Clock::SystemClockContext clock_context, s64 posix_time) {
+ standard_local_system_clock_core.SetUpdateCallbackInstance(
+ local_system_clock_context_writer);
+
+ const auto current_time_point{
+ standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)};
+ if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) {
+ standard_local_system_clock_core.SetSystemClockContext(clock_context);
+ } else {
+ if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) !=
+ RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+ }
+
+ standard_local_system_clock_core.MarkAsInitialized();
+ }
+
+ void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context,
+ Clock::TimeSpanType sufficient_accuracy) {
+ standard_network_system_clock_core.SetUpdateCallbackInstance(
+ network_system_clock_context_writer);
-void TimeManager::SetupStandardUserSystemClock(
- Core::System& system, bool is_automatic_correction_enabled,
- Clock::SteadyClockTimePoint steady_clock_time_point) {
- if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled(
- system, is_automatic_correction_enabled) != RESULT_SUCCESS) {
- UNREACHABLE();
- return;
+ if (standard_network_system_clock_core.SetSystemClockContext(clock_context) !=
+ RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+
+ standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy(
+ sufficient_accuracy);
+ standard_network_system_clock_core.MarkAsInitialized();
}
- standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point);
- standard_user_system_clock_core.MarkAsInitialized();
- shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled);
+ void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled,
+ Clock::SteadyClockTimePoint steady_clock_time_point) {
+ if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled(
+ system, is_automatic_correction_enabled) != RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+
+ standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point);
+ standard_user_system_clock_core.MarkAsInitialized();
+ shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled);
+ }
+
+ void SetupEphemeralNetworkSystemClock() {
+ ephemeral_network_system_clock_core.SetUpdateCallbackInstance(
+ ephemeral_network_system_clock_context_writer);
+ ephemeral_network_system_clock_core.MarkAsInitialized();
+ }
+
+ void UpdateLocalSystemClockTime(Core::System& system, s64 posix_time) {
+ const auto timespan{Service::Time::Clock::TimeSpanType::FromSeconds(posix_time)};
+ if (GetStandardLocalSystemClockCore()
+ .SetCurrentTime(system, timespan.ToSeconds())
+ .IsError()) {
+ UNREACHABLE();
+ return;
+ }
+ }
+
+ SharedMemory shared_memory;
+
+ Clock::StandardSteadyClockCore standard_steady_clock_core;
+ Clock::TickBasedSteadyClockCore tick_based_steady_clock_core;
+ Clock::StandardLocalSystemClockCore standard_local_system_clock_core;
+ Clock::StandardNetworkSystemClockCore standard_network_system_clock_core;
+ Clock::StandardUserSystemClockCore standard_user_system_clock_core;
+ Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core;
+
+ std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer;
+ std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer;
+ std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter>
+ ephemeral_network_system_clock_context_writer;
+
+ TimeZone::TimeZoneContentManager time_zone_content_manager;
+};
+
+TimeManager::TimeManager(Core::System& system) : system{system} {}
+
+TimeManager::~TimeManager() = default;
+
+void TimeManager::Initialize() {
+ impl = std::make_unique<Impl>(system);
+
+ // Time zones can only be initialized after impl is valid
+ impl->time_zone_content_manager.Initialize(*this);
+}
+
+Clock::StandardSteadyClockCore& TimeManager::GetStandardSteadyClockCore() {
+ return impl->standard_steady_clock_core;
+}
+
+const Clock::StandardSteadyClockCore& TimeManager::GetStandardSteadyClockCore() const {
+ return impl->standard_steady_clock_core;
+}
+
+Clock::StandardLocalSystemClockCore& TimeManager::GetStandardLocalSystemClockCore() {
+ return impl->standard_local_system_clock_core;
+}
+
+const Clock::StandardLocalSystemClockCore& TimeManager::GetStandardLocalSystemClockCore() const {
+ return impl->standard_local_system_clock_core;
+}
+
+Clock::StandardNetworkSystemClockCore& TimeManager::GetStandardNetworkSystemClockCore() {
+ return impl->standard_network_system_clock_core;
}
-void TimeManager::SetupEphemeralNetworkSystemClock() {
- ephemeral_network_system_clock_core.SetUpdateCallbackInstance(
- ephemeral_network_system_clock_context_writer);
- ephemeral_network_system_clock_core.MarkAsInitialized();
+const Clock::StandardNetworkSystemClockCore& TimeManager::GetStandardNetworkSystemClockCore()
+ const {
+ return impl->standard_network_system_clock_core;
+}
+
+Clock::StandardUserSystemClockCore& TimeManager::GetStandardUserSystemClockCore() {
+ return impl->standard_user_system_clock_core;
+}
+
+const Clock::StandardUserSystemClockCore& TimeManager::GetStandardUserSystemClockCore() const {
+ return impl->standard_user_system_clock_core;
+}
+
+TimeZone::TimeZoneContentManager& TimeManager::GetTimeZoneContentManager() {
+ return impl->time_zone_content_manager;
+}
+
+const TimeZone::TimeZoneContentManager& TimeManager::GetTimeZoneContentManager() const {
+ return impl->time_zone_content_manager;
+}
+
+SharedMemory& TimeManager::GetSharedMemory() {
+ return impl->shared_memory;
+}
+
+const SharedMemory& TimeManager::GetSharedMemory() const {
+ return impl->shared_memory;
+}
+
+void TimeManager::UpdateLocalSystemClockTime(s64 posix_time) {
+ impl->UpdateLocalSystemClockTime(system, posix_time);
+}
+
+void TimeManager::SetupTimeZoneManager(std::string location_name,
+ Clock::SteadyClockTimePoint time_zone_updated_time_point,
+ std::size_t total_location_name_count,
+ u128 time_zone_rule_version,
+ FileSys::VirtualFile& vfs_file) {
+ impl->SetupTimeZoneManager(location_name, time_zone_updated_time_point,
+ total_location_name_count, time_zone_rule_version, vfs_file);
+}
+
+/*static*/ s64 TimeManager::GetExternalTimeZoneOffset() {
+ // With "auto" timezone setting, we use the external system's timezone offset
+ if (Settings::GetTimeZoneString() == "auto") {
+ return Common::TimeZone::GetCurrentOffsetSeconds().count();
+ }
+ return 0;
}
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_manager.h b/src/core/hle/service/time/time_manager.h
index 8e65f0d22..993c7c288 100644
--- a/src/core/hle/service/time/time_manager.h
+++ b/src/core/hle/service/time/time_manager.h
@@ -5,6 +5,7 @@
#pragma once
#include "common/common_types.h"
+#include "common/time_zone.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/service/time/clock_types.h"
#include "core/hle/service/time/ephemeral_network_system_clock_core.h"
@@ -32,86 +33,46 @@ public:
explicit TimeManager(Core::System& system);
~TimeManager();
- Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() {
- return standard_steady_clock_core;
- }
+ void Initialize();
- const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const {
- return standard_steady_clock_core;
- }
+ Clock::StandardSteadyClockCore& GetStandardSteadyClockCore();
- Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() {
- return standard_local_system_clock_core;
- }
+ const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const;
- const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const {
- return standard_local_system_clock_core;
- }
+ Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore();
- Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() {
- return standard_network_system_clock_core;
- }
+ const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const;
- const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const {
- return standard_network_system_clock_core;
- }
+ Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore();
- Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() {
- return standard_user_system_clock_core;
- }
+ const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const;
- const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const {
- return standard_user_system_clock_core;
- }
+ Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore();
- TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() {
- return time_zone_content_manager;
- }
+ const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const;
- const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const {
- return time_zone_content_manager;
- }
+ TimeZone::TimeZoneContentManager& GetTimeZoneContentManager();
- SharedMemory& GetSharedMemory() {
- return shared_memory;
- }
+ const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const;
- const SharedMemory& GetSharedMemory() const {
- return shared_memory;
- }
+ void UpdateLocalSystemClockTime(s64 posix_time);
+
+ SharedMemory& GetSharedMemory();
+
+ const SharedMemory& GetSharedMemory() const;
void SetupTimeZoneManager(std::string location_name,
Clock::SteadyClockTimePoint time_zone_updated_time_point,
std::size_t total_location_name_count, u128 time_zone_rule_version,
FileSys::VirtualFile& vfs_file);
+ static s64 GetExternalTimeZoneOffset();
+
private:
- void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id,
- Clock::TimeSpanType setup_value,
- Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected);
- void SetupStandardLocalSystemClock(Core::System& system,
- Clock::SystemClockContext clock_context, s64 posix_time);
- void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context,
- Clock::TimeSpanType sufficient_accuracy);
- void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled,
- Clock::SteadyClockTimePoint steady_clock_time_point);
- void SetupEphemeralNetworkSystemClock();
-
- SharedMemory shared_memory;
-
- Clock::StandardSteadyClockCore standard_steady_clock_core;
- Clock::TickBasedSteadyClockCore tick_based_steady_clock_core;
- Clock::StandardLocalSystemClockCore standard_local_system_clock_core;
- Clock::StandardNetworkSystemClockCore standard_network_system_clock_core;
- Clock::StandardUserSystemClockCore standard_user_system_clock_core;
- Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core;
-
- std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer;
- std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer;
- std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter>
- ephemeral_network_system_clock_context_writer;
-
- TimeZone::TimeZoneContentManager time_zone_content_manager;
+ Core::System& system;
+
+ struct Impl;
+ std::unique_ptr<Impl> impl;
};
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h
index 5976b2046..e0c3e63da 100644
--- a/src/core/hle/service/time/time_sharedmemory.h
+++ b/src/core/hle/service/time/time_sharedmemory.h
@@ -6,8 +6,8 @@
#include "common/common_types.h"
#include "common/uuid.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/shared_memory.h"
-#include "core/hle/kernel/thread.h"
#include "core/hle/service/time/clock_types.h"
namespace Service::Time {
diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp
index 320672add..4177d0a41 100644
--- a/src/core/hle/service/time/time_zone_content_manager.cpp
+++ b/src/core/hle/service/time/time_zone_content_manager.cpp
@@ -68,9 +68,10 @@ static std::vector<std::string> BuildLocationNameCache(Core::System& system) {
return location_name_cache;
}
-TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core::System& system)
- : system{system}, location_name_cache{BuildLocationNameCache(system)} {
+TimeZoneContentManager::TimeZoneContentManager(Core::System& system)
+ : system{system}, location_name_cache{BuildLocationNameCache(system)} {}
+void TimeZoneContentManager::Initialize(TimeManager& time_manager) {
std::string location_name;
const auto timezone_setting = Settings::GetTimeZoneString();
if (timezone_setting == "auto" || timezone_setting == "default") {
diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h
index 4f302c3b9..52dd1a020 100644
--- a/src/core/hle/service/time/time_zone_content_manager.h
+++ b/src/core/hle/service/time/time_zone_content_manager.h
@@ -21,7 +21,9 @@ namespace Service::Time::TimeZone {
class TimeZoneContentManager final {
public:
- TimeZoneContentManager(TimeManager& time_manager, Core::System& system);
+ explicit TimeZoneContentManager(Core::System& system);
+
+ void Initialize(TimeManager& time_manager);
TimeZoneManager& GetTimeZoneManager() {
return time_zone_manager;
diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp
index 69152d0ac..bdf0439f2 100644
--- a/src/core/hle/service/time/time_zone_manager.cpp
+++ b/src/core/hle/service/time/time_zone_manager.cpp
@@ -820,7 +820,10 @@ static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, Calend
const ResultCode result{
ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)};
calendar.time.year = static_cast<s16>(calendar_time.year);
- calendar.time.month = calendar_time.month + 1; // Internal impl. uses 0-indexed month
+
+ // Internal impl. uses 0-indexed month
+ calendar.time.month = static_cast<s8>(calendar_time.month + 1);
+
calendar.time.day = calendar_time.day;
calendar.time.hour = calendar_time.hour;
calendar.time.minute = calendar_time.minute;
@@ -872,13 +875,15 @@ ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
const CalendarTime& calendar_time, s64& posix_time) const {
posix_time = 0;
- CalendarTimeInternal internal_time{};
- internal_time.year = calendar_time.year;
- internal_time.month = calendar_time.month - 1; // Internal impl. uses 0-indexed month
- internal_time.day = calendar_time.day;
- internal_time.hour = calendar_time.hour;
- internal_time.minute = calendar_time.minute;
- internal_time.second = calendar_time.second;
+ CalendarTimeInternal internal_time{
+ .year = calendar_time.year,
+ // Internal impl. uses 0-indexed month
+ .month = static_cast<s8>(calendar_time.month - 1),
+ .day = calendar_time.day,
+ .hour = calendar_time.hour,
+ .minute = calendar_time.minute,
+ .second = calendar_time.second,
+ };
s32 hour{internal_time.hour};
s32 minute{internal_time.minute};
diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp
index ff3a10b3e..25cecbc83 100644
--- a/src/core/hle/service/time/time_zone_service.cpp
+++ b/src/core/hle/service/time/time_zone_service.cpp
@@ -10,8 +10,9 @@
namespace Service::Time {
-ITimeZoneService ::ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_content_manager)
- : ServiceFramework("ITimeZoneService"), time_zone_content_manager{time_zone_content_manager} {
+ITimeZoneService ::ITimeZoneService(Core::System& system_,
+ TimeZone::TimeZoneContentManager& time_zone_manager_)
+ : ServiceFramework{system_, "ITimeZoneService"}, time_zone_content_manager{time_zone_manager_} {
static const FunctionInfo functions[] = {
{0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"},
{1, nullptr, "SetDeviceLocationName"},
diff --git a/src/core/hle/service/time/time_zone_service.h b/src/core/hle/service/time/time_zone_service.h
index cb495748b..2c9b97603 100644
--- a/src/core/hle/service/time/time_zone_service.h
+++ b/src/core/hle/service/time/time_zone_service.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Service::Time {
namespace TimeZone {
@@ -14,7 +18,8 @@ class TimeZoneContentManager;
class ITimeZoneService final : public ServiceFramework<ITimeZoneService> {
public:
- explicit ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_manager);
+ explicit ITimeZoneService(Core::System& system_,
+ TimeZone::TimeZoneContentManager& time_zone_manager_);
private:
void GetDeviceLocationName(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/time/time_zone_types.h b/src/core/hle/service/time/time_zone_types.h
index 9be15b53e..4a57e036d 100644
--- a/src/core/hle/service/time/time_zone_types.h
+++ b/src/core/hle/service/time/time_zone_types.h
@@ -45,23 +45,23 @@ static_assert(sizeof(TimeZoneRule) == 0x4000, "TimeZoneRule is incorrect size");
/// https://switchbrew.org/wiki/Glue_services#CalendarAdditionalInfo
struct CalendarAdditionalInfo {
- u32 day_of_week{};
- u32 day_of_year{};
+ u32 day_of_week;
+ u32 day_of_year;
std::array<char, 8> timezone_name;
- u32 is_dst{};
- s32 gmt_offset{};
+ u32 is_dst;
+ s32 gmt_offset;
};
static_assert(sizeof(CalendarAdditionalInfo) == 0x18, "CalendarAdditionalInfo is incorrect size");
/// https://switchbrew.org/wiki/Glue_services#CalendarTime
struct CalendarTime {
- s16 year{};
- s8 month{};
- s8 day{};
- s8 hour{};
- s8 minute{};
- s8 second{};
- INSERT_PADDING_BYTES(1);
+ s16 year;
+ s8 month;
+ s8 day;
+ s8 hour;
+ s8 minute;
+ s8 second;
+ INSERT_PADDING_BYTES_NOINIT(1);
};
static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime is incorrect size");
diff --git a/src/core/hle/service/usb/usb.cpp b/src/core/hle/service/usb/usb.cpp
index d033f8603..579de83e4 100644
--- a/src/core/hle/service/usb/usb.cpp
+++ b/src/core/hle/service/usb/usb.cpp
@@ -15,7 +15,7 @@ namespace Service::USB {
class IDsInterface final : public ServiceFramework<IDsInterface> {
public:
- explicit IDsInterface() : ServiceFramework{"IDsInterface"} {
+ explicit IDsInterface(Core::System& system_) : ServiceFramework{system_, "IDsInterface"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetDsEndpoint"},
@@ -40,7 +40,7 @@ public:
class USB_DS final : public ServiceFramework<USB_DS> {
public:
- explicit USB_DS() : ServiceFramework{"usb:ds"} {
+ explicit USB_DS(Core::System& system_) : ServiceFramework{system_, "usb:ds"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "BindDevice"},
@@ -65,7 +65,8 @@ public:
class IClientEpSession final : public ServiceFramework<IClientEpSession> {
public:
- explicit IClientEpSession() : ServiceFramework{"IClientEpSession"} {
+ explicit IClientEpSession(Core::System& system_)
+ : ServiceFramework{system_, "IClientEpSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Open"},
@@ -86,7 +87,8 @@ public:
class IClientIfSession final : public ServiceFramework<IClientIfSession> {
public:
- explicit IClientIfSession() : ServiceFramework{"IClientIfSession"} {
+ explicit IClientIfSession(Core::System& system_)
+ : ServiceFramework{system_, "IClientIfSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown0"},
@@ -108,7 +110,7 @@ public:
class USB_HS final : public ServiceFramework<USB_HS> {
public:
- explicit USB_HS() : ServiceFramework{"usb:hs"} {
+ explicit USB_HS(Core::System& system_) : ServiceFramework{system_, "usb:hs"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "BindClientProcess"},
@@ -129,7 +131,7 @@ public:
class IPdSession final : public ServiceFramework<IPdSession> {
public:
- explicit IPdSession() : ServiceFramework{"IPdSession"} {
+ explicit IPdSession(Core::System& system_) : ServiceFramework{system_, "IPdSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "BindNoticeEvent"},
@@ -148,7 +150,7 @@ public:
class USB_PD final : public ServiceFramework<USB_PD> {
public:
- explicit USB_PD() : ServiceFramework{"usb:pd"} {
+ explicit USB_PD(Core::System& system_) : ServiceFramework{system_, "usb:pd"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &USB_PD::GetPdSession, "GetPdSession"},
@@ -164,13 +166,14 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IPdSession>();
+ rb.PushIpcInterface<IPdSession>(system);
}
};
class IPdCradleSession final : public ServiceFramework<IPdCradleSession> {
public:
- explicit IPdCradleSession() : ServiceFramework{"IPdCradleSession"} {
+ explicit IPdCradleSession(Core::System& system_)
+ : ServiceFramework{system_, "IPdCradleSession"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "VdmUserWrite"},
@@ -191,7 +194,7 @@ public:
class USB_PD_C final : public ServiceFramework<USB_PD_C> {
public:
- explicit USB_PD_C() : ServiceFramework{"usb:pd:c"} {
+ explicit USB_PD_C(Core::System& system_) : ServiceFramework{system_, "usb:pd:c"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &USB_PD_C::GetPdCradleSession, "GetPdCradleSession"},
@@ -205,7 +208,7 @@ private:
void GetPdCradleSession(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IPdCradleSession>();
+ rb.PushIpcInterface<IPdCradleSession>(system);
LOG_DEBUG(Service_USB, "called");
}
@@ -213,7 +216,7 @@ private:
class USB_PM final : public ServiceFramework<USB_PM> {
public:
- explicit USB_PM() : ServiceFramework{"usb:pm"} {
+ explicit USB_PM(Core::System& system_) : ServiceFramework{system_, "usb:pm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown0"},
@@ -229,12 +232,12 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<USB_DS>()->InstallAsService(sm);
- std::make_shared<USB_HS>()->InstallAsService(sm);
- std::make_shared<USB_PD>()->InstallAsService(sm);
- std::make_shared<USB_PD_C>()->InstallAsService(sm);
- std::make_shared<USB_PM>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<USB_DS>(system)->InstallAsService(sm);
+ std::make_shared<USB_HS>(system)->InstallAsService(sm);
+ std::make_shared<USB_PD>(system)->InstallAsService(sm);
+ std::make_shared<USB_PD_C>(system)->InstallAsService(sm);
+ std::make_shared<USB_PM>(system)->InstallAsService(sm);
}
} // namespace Service::USB
diff --git a/src/core/hle/service/usb/usb.h b/src/core/hle/service/usb/usb.h
index 970a11fe8..fc366df34 100644
--- a/src/core/hle/service/usb/usb.h
+++ b/src/core/hle/service/usb/usb.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::USB {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::USB
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index 5a202ac81..7f42aa4a0 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -9,7 +9,9 @@
#include "common/assert.h"
#include "core/core.h"
-#include "core/hle/kernel/readable_event.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
@@ -17,8 +19,8 @@ namespace Service::VI {
Display::Display(u64 id, std::string name, Core::System& system) : id{id}, name{std::move(name)} {
auto& kernel = system.Kernel();
- vsync_event =
- Kernel::WritableEvent::CreateEventPair(kernel, fmt::format("Display VSync Event {}", id));
+ vsync_event = Kernel::KEvent::Create(kernel, fmt::format("Display VSync Event {}", id));
+ vsync_event->Initialize();
}
Display::~Display() = default;
@@ -31,12 +33,12 @@ const Layer& Display::GetLayer(std::size_t index) const {
return *layers.at(index);
}
-std::shared_ptr<Kernel::ReadableEvent> Display::GetVSyncEvent() const {
- return vsync_event.readable;
+std::shared_ptr<Kernel::KReadableEvent> Display::GetVSyncEvent() const {
+ return vsync_event->GetReadableEvent();
}
void Display::SignalVSyncEvent() {
- vsync_event.writable->Signal();
+ vsync_event->GetWritableEvent()->Signal();
}
void Display::CreateLayer(u64 id, NVFlinger::BufferQueue& buffer_queue) {
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index a3855d8cd..931c898f6 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -9,7 +9,10 @@
#include <vector>
#include "common/common_types.h"
-#include "core/hle/kernel/writable_event.h"
+
+namespace Kernel {
+class KEvent;
+}
namespace Service::NVFlinger {
class BufferQueue;
@@ -58,7 +61,7 @@ public:
const Layer& GetLayer(std::size_t index) const;
/// Gets the readable vsync event.
- std::shared_ptr<Kernel::ReadableEvent> GetVSyncEvent() const;
+ std::shared_ptr<Kernel::KReadableEvent> GetVSyncEvent() const;
/// Signals the internal vsync event.
void SignalVSyncEvent();
@@ -99,7 +102,7 @@ private:
std::string name;
std::vector<std::shared_ptr<Layer>> layers;
- Kernel::EventPair vsync_event;
+ std::shared_ptr<Kernel::KEvent> vsync_event;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 480d34725..8661895ae 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -18,9 +18,9 @@
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/writable_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
@@ -159,7 +159,7 @@ public:
header.data_size = static_cast<u32_le>(write_index - sizeof(Header));
header.data_offset = sizeof(Header);
header.objects_size = 4;
- header.objects_offset = sizeof(Header) + header.data_size;
+ header.objects_offset = static_cast<u32>(sizeof(Header) + header.data_size);
std::memcpy(buffer.data(), &header, sizeof(Header));
return buffer;
@@ -215,10 +215,9 @@ public:
explicit IGBPConnectRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPConnectRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
@@ -279,23 +278,28 @@ public:
: Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPSetPreallocatedBufferRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
- buffer = Read<NVFlinger::IGBPBuffer>();
+ if (data.contains_object != 0) {
+ buffer_container = Read<BufferContainer>();
+ }
}
struct Data {
u32_le slot;
- INSERT_PADDING_WORDS(1);
+ u32_le contains_object;
+ };
+
+ struct BufferContainer {
u32_le graphic_buffer_length;
INSERT_PADDING_WORDS(1);
+ NVFlinger::IGBPBuffer buffer{};
};
- Data data;
- NVFlinger::IGBPBuffer buffer;
+ Data data{};
+ BufferContainer buffer_container{};
};
class IGBPSetPreallocatedBufferResponseParcel : public Parcel {
@@ -306,15 +310,40 @@ protected:
}
};
+class IGBPCancelBufferRequestParcel : public Parcel {
+public:
+ explicit IGBPCancelBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
+ Deserialize();
+ }
+
+ void DeserializeData() override {
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
+ data = Read<Data>();
+ }
+
+ struct Data {
+ u32_le slot;
+ Service::Nvidia::MultiFence multi_fence;
+ };
+
+ Data data;
+};
+
+class IGBPCancelBufferResponseParcel : public Parcel {
+protected:
+ void SerializeData() override {
+ Write<u32>(0); // Success
+ }
+};
+
class IGBPDequeueBufferRequestParcel : public Parcel {
public:
explicit IGBPDequeueBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPDequeueBufferRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
@@ -333,7 +362,6 @@ class IGBPDequeueBufferResponseParcel : public Parcel {
public:
explicit IGBPDequeueBufferResponseParcel(u32 slot, Service::Nvidia::MultiFence& multi_fence)
: slot(slot), multi_fence(multi_fence) {}
- ~IGBPDequeueBufferResponseParcel() override = default;
protected:
void SerializeData() override {
@@ -352,10 +380,9 @@ public:
explicit IGBPRequestBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPRequestBufferRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
slot = Read<u32_le>();
}
@@ -384,10 +411,9 @@ public:
explicit IGBPQueueBufferRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPQueueBufferRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
data = Read<Data>();
}
@@ -447,10 +473,9 @@ public:
explicit IGBPQueryRequestParcel(std::vector<u8> buffer) : Parcel(std::move(buffer)) {
Deserialize();
}
- ~IGBPQueryRequestParcel() override = default;
void DeserializeData() override {
- std::u16string token = ReadInterfaceToken();
+ [[maybe_unused]] const std::u16string token = ReadInterfaceToken();
type = Read<u32_le>();
}
@@ -473,8 +498,8 @@ private:
class IHOSBinderDriver final : public ServiceFramework<IHOSBinderDriver> {
public:
- explicit IHOSBinderDriver(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework("IHOSBinderDriver"), nv_flinger(std::move(nv_flinger)) {
+ explicit IHOSBinderDriver(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
+ : ServiceFramework{system_, "IHOSBinderDriver"}, nv_flinger(nv_flinger_) {
static const FunctionInfo functions[] = {
{0, &IHOSBinderDriver::TransactParcel, "TransactParcel"},
{1, &IHOSBinderDriver::AdjustRefcount, "AdjustRefcount"},
@@ -509,10 +534,9 @@ private:
const u32 flags = rp.Pop<u32>();
LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id,
- static_cast<u32>(transaction), flags);
+ transaction, flags);
- const auto guard = nv_flinger->Lock();
- auto& buffer_queue = nv_flinger->FindBufferQueue(id);
+ auto& buffer_queue = *nv_flinger.FindBufferQueue(id);
switch (transaction) {
case TransactionId::Connect: {
@@ -522,13 +546,16 @@ private:
Settings::values.resolution_factor.GetValue()),
static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) *
Settings::values.resolution_factor.GetValue())};
+
+ buffer_queue.Connect();
+
ctx.WriteBuffer(response.Serialize());
break;
}
case TransactionId::SetPreallocatedBuffer: {
IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()};
- buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer);
+ buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer_container.buffer);
IGBPSetPreallocatedBufferResponseParcel response{};
ctx.WriteBuffer(response.Serialize());
@@ -538,40 +565,25 @@ private:
IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
const u32 width{request.data.width};
const u32 height{request.data.height};
- auto result = buffer_queue.DequeueBuffer(width, height);
-
- if (result) {
- // Buffer is available
- IGBPDequeueBufferResponseParcel response{result->first, *result->second};
- ctx.WriteBuffer(response.Serialize());
- } else {
- // Wait the current thread until a buffer becomes available
- ctx.SleepClientThread(
- "IHOSBinderDriver::DequeueBuffer", UINT64_MAX,
- [=, this](std::shared_ptr<Kernel::Thread> thread,
- Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) {
- // Repeat TransactParcel DequeueBuffer when a buffer is available
- const auto guard = nv_flinger->Lock();
- auto& buffer_queue = nv_flinger->FindBufferQueue(id);
- auto result = buffer_queue.DequeueBuffer(width, height);
- ASSERT_MSG(result != std::nullopt, "Could not dequeue buffer.");
-
- IGBPDequeueBufferResponseParcel response{result->first, *result->second};
- ctx.WriteBuffer(response.Serialize());
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- },
- buffer_queue.GetWritableBufferWaitEvent());
- }
+
+ do {
+ if (auto result = buffer_queue.DequeueBuffer(width, height); result) {
+ // Buffer is available
+ IGBPDequeueBufferResponseParcel response{result->first, *result->second};
+ ctx.WriteBuffer(response.Serialize());
+ break;
+ }
+ } while (buffer_queue.IsConnected());
+
break;
}
case TransactionId::RequestBuffer: {
IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()};
auto& buffer = buffer_queue.RequestBuffer(request.slot);
-
IGBPRequestBufferResponseParcel response{buffer};
ctx.WriteBuffer(response.Serialize());
+
break;
}
case TransactionId::QueueBuffer: {
@@ -596,7 +608,12 @@ private:
break;
}
case TransactionId::CancelBuffer: {
- LOG_CRITICAL(Service_VI, "(STUBBED) called, transaction=CancelBuffer");
+ IGBPCancelBufferRequestParcel request{ctx.ReadBuffer()};
+
+ buffer_queue.CancelBuffer(request.data.slot, request.data.multi_fence);
+
+ IGBPCancelBufferResponseParcel response{};
+ ctx.WriteBuffer(response.Serialize());
break;
}
case TransactionId::Disconnect: {
@@ -652,7 +669,7 @@ private:
LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown);
- const auto& buffer_queue = nv_flinger->FindBufferQueue(id);
+ const auto& buffer_queue = *nv_flinger.FindBufferQueue(id);
// TODO(Subv): Find out what this actually is.
IPC::ResponseBuilder rb{ctx, 2, 1};
@@ -660,12 +677,13 @@ private:
rb.PushCopyObjects(buffer_queue.GetBufferWaitEvent());
}
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
-}; // namespace VI
+ NVFlinger::NVFlinger& nv_flinger;
+};
class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
public:
- explicit ISystemDisplayService() : ServiceFramework("ISystemDisplayService") {
+ explicit ISystemDisplayService(Core::System& system_)
+ : ServiceFramework{system_, "ISystemDisplayService"} {
static const FunctionInfo functions[] = {
{1200, nullptr, "GetZOrderCountMin"},
{1202, nullptr, "GetZOrderCountMax"},
@@ -747,7 +765,7 @@ private:
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
- if (Settings::values.use_docked_mode) {
+ if (Settings::values.use_docked_mode.GetValue()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *
@@ -766,8 +784,8 @@ private:
class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
public:
- explicit IManagerDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework("IManagerDisplayService"), nv_flinger(std::move(nv_flinger)) {
+ explicit IManagerDisplayService(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
+ : ServiceFramework{system_, "IManagerDisplayService"}, nv_flinger{nv_flinger_} {
// clang-format off
static const FunctionInfo functions[] = {
{200, nullptr, "AllocateProcessHeapBlock"},
@@ -869,7 +887,7 @@ private:
"(STUBBED) called. unknown=0x{:08X}, display=0x{:016X}, aruid=0x{:016X}",
unknown, display, aruid);
- const auto layer_id = nv_flinger->CreateLayer(display);
+ const auto layer_id = nv_flinger.CreateLayer(display);
if (!layer_id) {
LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
IPC::ResponseBuilder rb{ctx, 2};
@@ -906,12 +924,12 @@ private:
rb.Push(RESULT_SUCCESS);
}
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ NVFlinger::NVFlinger& nv_flinger;
};
class IApplicationDisplayService final : public ServiceFramework<IApplicationDisplayService> {
public:
- explicit IApplicationDisplayService(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit IApplicationDisplayService(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_);
private:
enum class ConvertedScaleMode : u64 {
@@ -935,7 +953,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
+ rb.PushIpcInterface<IHOSBinderDriver>(system, nv_flinger);
}
void GetSystemDisplayService(Kernel::HLERequestContext& ctx) {
@@ -943,7 +961,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemDisplayService>();
+ rb.PushIpcInterface<ISystemDisplayService>(system);
}
void GetManagerDisplayService(Kernel::HLERequestContext& ctx) {
@@ -951,7 +969,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IManagerDisplayService>(nv_flinger);
+ rb.PushIpcInterface<IManagerDisplayService>(system, nv_flinger);
}
void GetIndirectDisplayTransactionService(Kernel::HLERequestContext& ctx) {
@@ -959,7 +977,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IHOSBinderDriver>(nv_flinger);
+ rb.PushIpcInterface<IHOSBinderDriver>(system, nv_flinger);
}
void OpenDisplay(Kernel::HLERequestContext& ctx) {
@@ -986,7 +1004,7 @@ private:
ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet");
- const auto display_id = nv_flinger->OpenDisplay(name);
+ const auto display_id = nv_flinger.OpenDisplay(name);
if (!display_id) {
LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
IPC::ResponseBuilder rb{ctx, 2};
@@ -1041,8 +1059,8 @@ private:
const auto scaling_mode = rp.PopEnum<NintendoScaleMode>();
const u64 unknown = rp.Pop<u64>();
- LOG_DEBUG(Service_VI, "called. scaling_mode=0x{:08X}, unknown=0x{:016X}",
- static_cast<u32>(scaling_mode), unknown);
+ LOG_DEBUG(Service_VI, "called. scaling_mode=0x{:08X}, unknown=0x{:016X}", scaling_mode,
+ unknown);
IPC::ResponseBuilder rb{ctx, 2};
@@ -1086,7 +1104,7 @@ private:
LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}, aruid=0x{:016X}", layer_id, aruid);
- const auto display_id = nv_flinger->OpenDisplay(display_name);
+ const auto display_id = nv_flinger.OpenDisplay(display_name);
if (!display_id) {
LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
IPC::ResponseBuilder rb{ctx, 2};
@@ -1094,7 +1112,7 @@ private:
return;
}
- const auto buffer_queue_id = nv_flinger->FindBufferQueueId(*display_id, layer_id);
+ const auto buffer_queue_id = nv_flinger.FindBufferQueueId(*display_id, layer_id);
if (!buffer_queue_id) {
LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
IPC::ResponseBuilder rb{ctx, 2};
@@ -1114,7 +1132,7 @@ private:
LOG_DEBUG(Service_VI, "called. layer_id=0x{:016X}", layer_id);
- nv_flinger->CloseLayer(layer_id);
+ nv_flinger.CloseLayer(layer_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -1130,7 +1148,7 @@ private:
// TODO(Subv): What's the difference between a Stray and a Managed layer?
- const auto layer_id = nv_flinger->CreateLayer(display_id);
+ const auto layer_id = nv_flinger.CreateLayer(display_id);
if (!layer_id) {
LOG_ERROR(Service_VI, "Layer not found! layer_id={}", *layer_id);
IPC::ResponseBuilder rb{ctx, 2};
@@ -1138,7 +1156,7 @@ private:
return;
}
- const auto buffer_queue_id = nv_flinger->FindBufferQueueId(display_id, *layer_id);
+ const auto buffer_queue_id = nv_flinger.FindBufferQueueId(display_id, *layer_id);
if (!buffer_queue_id) {
LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
@@ -1169,7 +1187,7 @@ private:
LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id);
- const auto vsync_event = nv_flinger->FindVsyncEvent(display_id);
+ const auto vsync_event = nv_flinger.FindVsyncEvent(display_id);
if (!vsync_event) {
LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
@@ -1185,7 +1203,7 @@ private:
void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto mode = rp.PopEnum<NintendoScaleMode>();
- LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode));
+ LOG_DEBUG(Service_VI, "called mode={}", mode);
const auto converted_mode = ConvertScalingModeImpl(mode);
@@ -1205,8 +1223,8 @@ private:
const auto height = rp.Pop<u64>();
LOG_DEBUG(Service_VI, "called width={}, height={}", width, height);
- constexpr std::size_t base_size = 0x20000;
- constexpr std::size_t alignment = 0x1000;
+ constexpr u64 base_size = 0x20000;
+ constexpr u64 alignment = 0x1000;
const auto texture_size = width * height * 4;
const auto out_size = (texture_size + base_size - 1) / base_size * base_size;
@@ -1234,12 +1252,12 @@ private:
}
}
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ NVFlinger::NVFlinger& nv_flinger;
};
-IApplicationDisplayService::IApplicationDisplayService(
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework("IApplicationDisplayService"), nv_flinger(std::move(nv_flinger)) {
+IApplicationDisplayService::IApplicationDisplayService(Core::System& system_,
+ NVFlinger::NVFlinger& nv_flinger_)
+ : ServiceFramework{system_, "IApplicationDisplayService"}, nv_flinger{nv_flinger_} {
static const FunctionInfo functions[] = {
{100, &IApplicationDisplayService::GetRelayService, "GetRelayService"},
{101, &IApplicationDisplayService::GetSystemDisplayService, "GetSystemDisplayService"},
@@ -1280,14 +1298,13 @@ static bool IsValidServiceAccess(Permission permission, Policy policy) {
return false;
}
-void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger,
- Permission permission) {
+void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& system,
+ NVFlinger::NVFlinger& nv_flinger, Permission permission) {
IPC::RequestParser rp{ctx};
const auto policy = rp.PopEnum<Policy>();
if (!IsValidServiceAccess(permission, policy)) {
- LOG_ERROR(Service_VI, "Permission denied for policy {}", static_cast<u32>(policy));
+ LOG_ERROR(Service_VI, "Permission denied for policy {}", policy);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_PERMISSION_DENIED);
return;
@@ -1295,14 +1312,14 @@ void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationDisplayService>(std::move(nv_flinger));
+ rb.PushIpcInterface<IApplicationDisplayService>(system, nv_flinger);
}
-void InstallInterfaces(SM::ServiceManager& service_manager,
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) {
- std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system,
+ NVFlinger::NVFlinger& nv_flinger) {
+ std::make_shared<VI_M>(system, nv_flinger)->InstallAsService(service_manager);
+ std::make_shared<VI_S>(system, nv_flinger)->InstallAsService(service_manager);
+ std::make_shared<VI_U>(system, nv_flinger)->InstallAsService(service_manager);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index 6b66f8b81..eec531d54 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -7,6 +7,10 @@
#include <memory>
#include "common/common_types.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -43,12 +47,12 @@ enum class Policy {
};
namespace detail {
-void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, Permission permission);
+void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System& system,
+ NVFlinger::NVFlinger& nv_flinger, Permission permission);
} // namespace detail
/// Registers all VI services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& service_manager,
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system,
+ NVFlinger::NVFlinger& nv_flinger);
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp
index 06070087f..87db1c416 100644
--- a/src/core/hle/service/vi/vi_m.cpp
+++ b/src/core/hle/service/vi/vi_m.cpp
@@ -8,8 +8,8 @@
namespace Service::VI {
-VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework{"vi:m"}, nv_flinger{std::move(nv_flinger)} {
+VI_M::VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
+ : ServiceFramework{system_, "vi:m"}, nv_flinger{nv_flinger_} {
static const FunctionInfo functions[] = {
{2, &VI_M::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -22,7 +22,7 @@ VI_M::~VI_M() = default;
void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
- detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::Manager);
+ detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::Manager);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h
index 290e06689..d79c41beb 100644
--- a/src/core/hle/service/vi/vi_m.h
+++ b/src/core/hle/service/vi/vi_m.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -18,13 +22,13 @@ namespace Service::VI {
class VI_M final : public ServiceFramework<VI_M> {
public:
- explicit VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_M(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_);
~VI_M() override;
private:
void GetDisplayService(Kernel::HLERequestContext& ctx);
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ NVFlinger::NVFlinger& nv_flinger;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp
index 57c596cc4..5cd22f7df 100644
--- a/src/core/hle/service/vi/vi_s.cpp
+++ b/src/core/hle/service/vi/vi_s.cpp
@@ -8,8 +8,8 @@
namespace Service::VI {
-VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework{"vi:s"}, nv_flinger{std::move(nv_flinger)} {
+VI_S::VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
+ : ServiceFramework{system_, "vi:s"}, nv_flinger{nv_flinger_} {
static const FunctionInfo functions[] = {
{1, &VI_S::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -22,7 +22,7 @@ VI_S::~VI_S() = default;
void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
- detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::System);
+ detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::System);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h
index 47804dc0b..5f1f8f290 100644
--- a/src/core/hle/service/vi/vi_s.h
+++ b/src/core/hle/service/vi/vi_s.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -18,13 +22,13 @@ namespace Service::VI {
class VI_S final : public ServiceFramework<VI_S> {
public:
- explicit VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_S(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_);
~VI_S() override;
private:
void GetDisplayService(Kernel::HLERequestContext& ctx);
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ NVFlinger::NVFlinger& nv_flinger;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp
index 6b7329345..0079d51f0 100644
--- a/src/core/hle/service/vi/vi_u.cpp
+++ b/src/core/hle/service/vi/vi_u.cpp
@@ -8,8 +8,8 @@
namespace Service::VI {
-VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} {
+VI_U::VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_)
+ : ServiceFramework{system_, "vi:u"}, nv_flinger{nv_flinger_} {
static const FunctionInfo functions[] = {
{0, &VI_U::GetDisplayService, "GetDisplayService"},
{1, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -22,7 +22,7 @@ VI_U::~VI_U() = default;
void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_VI, "called");
- detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::User);
+ detail::GetDisplayServiceImpl(ctx, system, nv_flinger, Permission::User);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h
index 19bdb73b0..8e3885c73 100644
--- a/src/core/hle/service/vi/vi_u.h
+++ b/src/core/hle/service/vi/vi_u.h
@@ -6,6 +6,10 @@
#include "core/hle/service/service.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class HLERequestContext;
}
@@ -18,13 +22,13 @@ namespace Service::VI {
class VI_U final : public ServiceFramework<VI_U> {
public:
- explicit VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_U(Core::System& system_, NVFlinger::NVFlinger& nv_flinger_);
~VI_U() override;
private:
void GetDisplayService(Kernel::HLERequestContext& ctx);
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
+ NVFlinger::NVFlinger& nv_flinger;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/wlan/wlan.cpp b/src/core/hle/service/wlan/wlan.cpp
index 0260d7dcf..ddbf04069 100644
--- a/src/core/hle/service/wlan/wlan.cpp
+++ b/src/core/hle/service/wlan/wlan.cpp
@@ -12,7 +12,7 @@ namespace Service::WLAN {
class WLANInfra final : public ServiceFramework<WLANInfra> {
public:
- explicit WLANInfra() : ServiceFramework{"wlan:inf"} {
+ explicit WLANInfra(Core::System& system_) : ServiceFramework{system_, "wlan:inf"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "OpenMode"},
@@ -55,7 +55,7 @@ public:
class WLANLocal final : public ServiceFramework<WLANLocal> {
public:
- explicit WLANLocal() : ServiceFramework{"wlan:lcl"} {
+ explicit WLANLocal(Core::System& system_) : ServiceFramework{system_, "wlan:lcl"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown0"},
@@ -120,7 +120,7 @@ public:
class WLANLocalGetFrame final : public ServiceFramework<WLANLocalGetFrame> {
public:
- explicit WLANLocalGetFrame() : ServiceFramework{"wlan:lg"} {
+ explicit WLANLocalGetFrame(Core::System& system_) : ServiceFramework{system_, "wlan:lg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown"},
@@ -133,7 +133,7 @@ public:
class WLANSocketGetFrame final : public ServiceFramework<WLANSocketGetFrame> {
public:
- explicit WLANSocketGetFrame() : ServiceFramework{"wlan:sg"} {
+ explicit WLANSocketGetFrame(Core::System& system_) : ServiceFramework{system_, "wlan:sg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown"},
@@ -146,7 +146,7 @@ public:
class WLANSocketManager final : public ServiceFramework<WLANSocketManager> {
public:
- explicit WLANSocketManager() : ServiceFramework{"wlan:soc"} {
+ explicit WLANSocketManager(Core::System& system_) : ServiceFramework{system_, "wlan:soc"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown0"},
@@ -169,12 +169,12 @@ public:
}
};
-void InstallInterfaces(SM::ServiceManager& sm) {
- std::make_shared<WLANInfra>()->InstallAsService(sm);
- std::make_shared<WLANLocal>()->InstallAsService(sm);
- std::make_shared<WLANLocalGetFrame>()->InstallAsService(sm);
- std::make_shared<WLANSocketGetFrame>()->InstallAsService(sm);
- std::make_shared<WLANSocketManager>()->InstallAsService(sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
+ std::make_shared<WLANInfra>(system)->InstallAsService(sm);
+ std::make_shared<WLANLocal>(system)->InstallAsService(sm);
+ std::make_shared<WLANLocalGetFrame>(system)->InstallAsService(sm);
+ std::make_shared<WLANSocketGetFrame>(system)->InstallAsService(sm);
+ std::make_shared<WLANSocketManager>(system)->InstallAsService(sm);
}
} // namespace Service::WLAN
diff --git a/src/core/hle/service/wlan/wlan.h b/src/core/hle/service/wlan/wlan.h
index 054ea928a..3899eedbb 100644
--- a/src/core/hle/service/wlan/wlan.h
+++ b/src/core/hle/service/wlan/wlan.h
@@ -4,12 +4,16 @@
#pragma once
+namespace Core {
+class System;
+}
+
namespace Service::SM {
class ServiceManager;
}
namespace Service::WLAN {
-void InstallInterfaces(SM::ServiceManager& sm);
+void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
} // namespace Service::WLAN
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 134e83412..79ebf11de 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -12,7 +12,6 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/romfs_factory.h"
-#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
@@ -89,7 +88,7 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::Virtua
}
AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirectory::Load(
- Kernel::Process& process) {
+ Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -114,7 +113,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
if (override_update) {
- const FileSys::PatchManager patch_manager(metadata.GetTitleID());
+ const FileSys::PatchManager patch_manager(
+ metadata.GetTitleID(), system.GetFileSystemController(), system.GetContentProvider());
dir = patch_manager.PatchExeFS(dir);
}
@@ -141,9 +141,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
continue;
}
- const bool should_pass_arguments{std::strcmp(module, "rtld") == 0};
- const auto tentative_next_load_addr{AppLoader_NSO::LoadModule(
- process, *module_file, code_size, should_pass_arguments, false)};
+ const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
+ const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
+ process, system, *module_file, code_size, should_pass_arguments, false);
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -160,7 +160,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
modules.clear();
const VAddr base_address{process.PageTable().GetCodeRegionStart()};
VAddr next_load_addr{base_address};
- const FileSys::PatchManager pm{metadata.GetTitleID()};
+ const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
+ system.GetContentProvider()};
for (const auto& module : static_modules) {
const FileSys::VirtualFile module_file{dir->GetFile(module)};
if (!module_file) {
@@ -168,9 +169,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
const VAddr load_addr{next_load_addr};
- const bool should_pass_arguments{std::strcmp(module, "rtld") == 0};
- const auto tentative_next_load_addr{AppLoader_NSO::LoadModule(
- process, *module_file, load_addr, should_pass_arguments, true, pm)};
+ const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
+ const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
+ process, system, *module_file, load_addr, should_pass_arguments, true, pm);
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -178,8 +179,6 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
next_load_addr = *tentative_next_load_addr;
modules.insert_or_assign(load_addr, module);
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
- // Register module with GDBStub
- GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
}
// Find the RomFS by searching for a ".romfs" file in this directory
@@ -192,8 +191,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
// Register the RomFS if a ".romfs" file was found
if (romfs_iter != files.end() && *romfs_iter != nullptr) {
romfs = *romfs_iter;
- Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
- std::make_unique<FileSys::RomFSFactory>(*this));
+ system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
+ *this, system.GetContentProvider(), system.GetFileSystemController()));
}
is_loaded = true;
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index 1c0a354a4..3c968580f 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -9,6 +9,10 @@
#include "core/file_sys/program_metadata.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace Loader {
/**
@@ -28,7 +32,7 @@ public:
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
@@ -37,7 +41,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 8f7615115..dca1fcb18 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -383,7 +383,8 @@ FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process) {
+AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process,
+ [[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h
index 7ef7770a6..2067932c7 100644
--- a/src/core/loader/elf.h
+++ b/src/core/loader/elf.h
@@ -8,6 +8,10 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace Loader {
/// Loads an ELF/AXF file
@@ -17,7 +21,7 @@ public:
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
@@ -26,7 +30,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
};
} // namespace Loader
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index 40fa03ad1..e162c4ff0 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -5,7 +5,6 @@
#include <cstring>
#include "core/file_sys/kernel_executable.h"
#include "core/file_sys/program_metadata.h"
-#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
@@ -16,7 +15,7 @@ namespace Loader {
namespace {
constexpr u32 PageAlignSize(u32 size) {
- return (size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK;
+ return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
}
} // Anonymous namespace
@@ -43,7 +42,8 @@ FileType AppLoader_KIP::GetFileType() const {
: FileType::Error;
}
-AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) {
+AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process,
+ [[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -90,8 +90,6 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) {
program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
codeset.DataSegment().size += kip->GetBSSSize();
- GDBStub::RegisterModule(kip->GetName(), base_address, base_address + program_image.size());
-
codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), base_address);
diff --git a/src/core/loader/kip.h b/src/core/loader/kip.h
index 12ca40269..14a85e295 100644
--- a/src/core/loader/kip.h
+++ b/src/core/loader/kip.h
@@ -6,6 +6,10 @@
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class KIP;
}
@@ -19,14 +23,14 @@ public:
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override;
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
private:
std::unique_ptr<FileSys::KIP> kip;
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 9bc3a8840..e4f5fd40c 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -10,6 +10,7 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/elf.h"
@@ -184,6 +185,10 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
"The INI file contains more than the maximum allowable number of KIP files.",
};
+std::string GetResultStatusString(ResultStatus status) {
+ return RESULT_MESSAGES.at(static_cast<std::size_t>(status));
+}
+
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
os << RESULT_MESSAGES.at(static_cast<std::size_t>(status));
return os;
@@ -194,15 +199,15 @@ AppLoader::~AppLoader() = default;
/**
* Get a loader for a file with a specific type
- * @param file The file to load
- * @param type The type of the file
- * @param file the file to retrieve the loader for
- * @param type the file type
+ * @param system The system context to use.
+ * @param file The file to retrieve the loader for
+ * @param type The file type
+ * @param program_index Specifies the index within the container of the program to launch.
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
*/
-static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileType type) {
+static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::VirtualFile file,
+ FileType type, std::size_t program_index) {
switch (type) {
-
// Standard ELF file format.
case FileType::ELF:
return std::make_unique<AppLoader_ELF>(std::move(file));
@@ -221,7 +226,8 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
// NX XCI (nX Card Image) file format.
case FileType::XCI:
- return std::make_unique<AppLoader_XCI>(std::move(file));
+ return std::make_unique<AppLoader_XCI>(std::move(file), system.GetFileSystemController(),
+ system.GetContentProvider(), program_index);
// NX NAX (NintendoAesXts) file format.
case FileType::NAX:
@@ -229,7 +235,8 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
// NX NSP (Nintendo Submission Package) file format
case FileType::NSP:
- return std::make_unique<AppLoader_NSP>(std::move(file));
+ return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(),
+ system.GetContentProvider(), program_index);
// NX KIP (Kernel Internal Process) file format
case FileType::KIP:
@@ -244,20 +251,22 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
}
}
-std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) {
+std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
+ std::size_t program_index) {
FileType type = IdentifyFile(file);
- FileType filename_type = GuessFromFilename(file->GetName());
+ const FileType filename_type = GuessFromFilename(file->GetName());
// Special case: 00 is either a NCA or NAX.
if (type != filename_type && !(file->GetName() == "00" && type == FileType::NAX)) {
LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName());
- if (FileType::Unknown == type)
+ if (FileType::Unknown == type) {
type = filename_type;
+ }
}
LOG_DEBUG(Loader, "Loading file {} as {}...", file->GetName(), GetFileTypeString(type));
- return GetFileLoader(std::move(file), type);
+ return GetFileLoader(system, std::move(file), type, program_index);
}
} // namespace Loader
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 227ecc704..b2e5b13de 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -15,6 +15,10 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class NACP;
} // namespace FileSys
@@ -131,6 +135,7 @@ enum class ResultStatus : u16 {
ErrorINITooManyKIPs,
};
+std::string GetResultStatusString(ResultStatus status);
std::ostream& operator<<(std::ostream& os, ResultStatus status);
/// Interface for loading an application
@@ -154,9 +159,10 @@ public:
/**
* Load the application and return the created Process instance
* @param process The newly created process.
+ * @param system The system that this process is being loaded under.
* @return The status result of the operation.
*/
- virtual LoadResult Load(Kernel::Process& process) = 0;
+ virtual LoadResult Load(Kernel::Process& process, Core::System& system) = 0;
/**
* Get the code (typically .code section) of the application
@@ -285,9 +291,14 @@ protected:
/**
* Identifies a bootable file and return a suitable loader
- * @param file The bootable file
- * @return the best loader for this file
+ *
+ * @param system The system context.
+ * @param file The bootable file.
+ * @param program_index Specifies the index within the container of the program to launch.
+ *
+ * @return the best loader for this file.
*/
-std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file);
+std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
+ std::size_t program_index = 0);
} // namespace Loader
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index a152981a0..49028177b 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -41,7 +41,8 @@ FileType AppLoader_NAX::GetFileType() const {
return IdentifyTypeImpl(*nax);
}
-AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) {
+AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process,
+ [[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -65,7 +66,7 @@ AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) {
return {nca_status, {}};
}
- const auto result = nca_loader->Load(process);
+ const auto result = nca_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index eaec9bf58..a5b5e2ae1 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -8,10 +8,12 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
-namespace FileSys {
+namespace Core {
+class System;
+}
+namespace FileSys {
class NAX;
-
} // namespace FileSys
namespace Loader {
@@ -26,14 +28,14 @@ public:
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override;
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 5a0469978..fa694de37 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -31,7 +31,7 @@ FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
+AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -52,14 +52,14 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
- const auto load_result = directory_loader->Load(process);
+ const auto load_result = directory_loader->Load(process, system);
if (load_result.first != ResultStatus::Success) {
return load_result;
}
if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) {
- Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
- std::make_unique<FileSys::RomFSFactory>(*this));
+ system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
+ *this, system.GetContentProvider(), system.GetFileSystemController()));
}
is_loaded = true;
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index e47dc0e47..918792800 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -8,6 +8,10 @@
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class NCA;
}
@@ -24,7 +28,7 @@ public:
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
@@ -33,7 +37,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 906544bc9..f976d0a9c 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -14,8 +14,8 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/vfs_offset.h"
-#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -127,7 +127,7 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& file) {
}
static constexpr u32 PageAlignSize(u32 size) {
- return (size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK;
+ return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
}
static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
@@ -197,10 +197,6 @@ static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), process.PageTable().GetCodeRegionStart());
- // Register module with GDBStub
- GDBStub::RegisterModule(name, process.PageTable().GetCodeRegionStart(),
- process.PageTable().GetCodeRegionEnd());
-
return true;
}
@@ -208,7 +204,7 @@ bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& fi
return LoadNroImpl(process, file.ReadAllBytes(), file.GetName());
}
-AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
+AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -218,13 +214,13 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
}
if (romfs != nullptr) {
- Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
- std::make_unique<FileSys::RomFSFactory>(*this));
+ system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
+ *this, system.GetContentProvider(), system.GetFileSystemController()));
}
is_loaded = true;
- return {ResultStatus::Success,
- LoadParameters{Kernel::THREADPRIO_DEFAULT, Core::Memory::DEFAULT_STACK_SIZE}};
+ return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
+ Core::Memory::DEFAULT_STACK_SIZE}};
}
ResultStatus AppLoader_NRO::ReadIcon(std::vector<u8>& buffer) {
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 4593d48fb..a82b66221 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -10,6 +10,10 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class NACP;
}
@@ -28,7 +32,7 @@ public:
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
@@ -37,7 +41,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 575330a86..ea347ea83 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -14,8 +14,8 @@
#include "common/swap.h"
#include "core/core.h"
#include "core/file_sys/patch_manager.h"
-#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/loader/nso.h"
@@ -47,7 +47,7 @@ std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
}
constexpr u32 PageAlignSize(u32 size) {
- return (size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK;
+ return static_cast<u32>((size + Core::Memory::PAGE_MASK) & ~Core::Memory::PAGE_MASK);
}
} // Anonymous namespace
@@ -71,21 +71,21 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::NSO;
}
-std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
+std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, Core::System& system,
const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm) {
if (file.GetSize() < sizeof(NSOHeader)) {
- return {};
+ return std::nullopt;
}
NSOHeader nso_header{};
if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) {
- return {};
+ return std::nullopt;
}
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
- return {};
+ return std::nullopt;
}
// Build program image
@@ -148,9 +148,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
// Apply cheats if they exist and the program has a valid title ID
if (pm) {
- auto& system = Core::System::GetInstance();
system.SetCurrentProcessBuildID(nso_header.build_id);
- const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
+ const auto cheats = pm->CreateCheatList(nso_header.build_id);
if (!cheats.empty()) {
system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
}
@@ -160,13 +159,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), load_base);
- // Register module with GDBStub
- GDBStub::RegisterModule(file.GetName(), load_base, load_base);
-
return load_base + image_size;
}
-AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
+AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -175,7 +171,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
// Load module
const VAddr base_address = process.PageTable().GetCodeRegionStart();
- if (!LoadModule(process, *file, base_address, true, true)) {
+ if (!LoadModule(process, system, *file, base_address, true, true)) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -183,8 +179,8 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
is_loaded = true;
- return {ResultStatus::Success,
- LoadParameters{Kernel::THREADPRIO_DEFAULT, Core::Memory::DEFAULT_STACK_SIZE}};
+ return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority,
+ Core::Memory::DEFAULT_STACK_SIZE}};
}
ResultStatus AppLoader_NSO::ReadNSOModules(Modules& modules) {
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index b210830f0..3af461b5f 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -12,6 +12,10 @@
#include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h"
+namespace Core {
+class System;
+}
+
namespace Kernel {
class Process;
}
@@ -55,7 +59,7 @@ struct NSOHeader {
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
-constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
+constexpr u32 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
struct NSOArgumentHeader {
u32_le allocated_size;
@@ -71,7 +75,7 @@ public:
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
@@ -80,12 +84,12 @@ public:
return IdentifyType(file);
}
- static std::optional<VAddr> LoadModule(Kernel::Process& process, const FileSys::VfsFile& file,
- VAddr load_base, bool should_pass_arguments,
- bool load_into_process,
+ static std::optional<VAddr> LoadModule(Kernel::Process& process, Core::System& system,
+ const FileSys::VfsFile& file, VAddr load_base,
+ bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm = {});
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadNSOModules(Modules& modules) override;
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 13950fc08..928f64c8c 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -21,26 +21,34 @@
namespace Loader {
-AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
- : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)),
+AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider,
+ std::size_t program_index)
+ : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file, program_index)),
title_id(nsp->GetProgramTitleID()) {
- if (nsp->GetStatus() != ResultStatus::Success)
+ if (nsp->GetStatus() != ResultStatus::Success) {
return;
+ }
if (nsp->IsExtractedType()) {
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
} else {
const auto control_nca =
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
- if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
+ if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) {
return;
+ }
- std::tie(nacp_file, icon_file) =
- FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
+ std::tie(nacp_file, icon_file) = [this, &content_provider, &control_nca, &fsc] {
+ const FileSys::PatchManager pm{nsp->GetProgramTitleID(), fsc, content_provider};
+ return pm.ParseControlNCA(*control_nca);
+ }();
- if (title_id == 0)
+ if (title_id == 0) {
return;
+ }
secondary_loader = std::make_unique<AppLoader_NCA>(
nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
@@ -71,7 +79,7 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
+AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -99,15 +107,14 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
return {ResultStatus::ErrorNSPMissingProgramNCA, {}};
}
- const auto result = secondary_loader->Load(process);
+ const auto result = secondary_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
- Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
- std::move(update_raw));
+ system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
}
is_loaded = true;
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 868b028d3..d48d87f2c 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -10,10 +10,15 @@
#include "core/loader/loader.h"
namespace FileSys {
+class ContentProvider;
class NACP;
class NSP;
} // namespace FileSys
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace Loader {
class AppLoader_NCA;
@@ -21,12 +26,15 @@ class AppLoader_NCA;
/// Loads an XCI file
class AppLoader_NSP final : public AppLoader {
public:
- explicit AppLoader_NSP(FileSys::VirtualFile file);
+ explicit AppLoader_NSP(FileSys::VirtualFile file,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider,
+ std::size_t program_index);
~AppLoader_NSP() override;
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
@@ -35,7 +43,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 7186ad1ff..aaa250cea 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -20,18 +20,25 @@
namespace Loader {
-AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
- : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
+AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider,
+ std::size_t program_index)
+ : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file, program_index)),
nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) {
- if (xci->GetStatus() != ResultStatus::Success)
+ if (xci->GetStatus() != ResultStatus::Success) {
return;
+ }
const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control);
- if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
+ if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) {
return;
+ }
- std::tie(nacp_file, icon_file) =
- FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(*control_nca);
+ std::tie(nacp_file, icon_file) = [this, &content_provider, &control_nca, &fsc] {
+ const FileSys::PatchManager pm{xci->GetProgramTitleID(), fsc, content_provider};
+ return pm.ParseControlNCA(*control_nca);
+ }();
}
AppLoader_XCI::~AppLoader_XCI() = default;
@@ -49,7 +56,7 @@ FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
-AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
+AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -66,15 +73,14 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
return {ResultStatus::ErrorMissingProductionKeyFile, {}};
}
- const auto result = nca_loader->Load(process);
+ const auto result = nca_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
- Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
- std::move(update_raw));
+ system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
}
is_loaded = true;
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 618ae2f47..9f0ceb5ef 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -10,10 +10,15 @@
#include "core/loader/loader.h"
namespace FileSys {
+class ContentProvider;
class NACP;
class XCI;
} // namespace FileSys
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace Loader {
class AppLoader_NCA;
@@ -21,12 +26,15 @@ class AppLoader_NCA;
/// Loads an XCI file
class AppLoader_XCI final : public AppLoader {
public:
- explicit AppLoader_XCI(FileSys::VirtualFile file);
+ explicit AppLoader_XCI(FileSys::VirtualFile file,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider,
+ std::size_t program_index);
~AppLoader_XCI() override;
/**
* Returns the type of the file
- * @param file std::shared_ptr<VfsFile> open file
+ * @param file open file
* @return FileType found, or FileType::Error if this loader doesn't know it
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
@@ -35,7 +43,7 @@ public:
return IdentifyType(file);
}
- LoadResult Load(Kernel::Process& process) override;
+ LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 86d17c6cb..11609682a 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -44,44 +44,16 @@ struct Memory::Impl {
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
}
- void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer mmio_handler) {
- UNIMPLEMENTED();
- }
-
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped);
}
- void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer hook) {
- UNIMPLEMENTED();
- }
-
- void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer hook) {
- UNIMPLEMENTED();
- }
-
bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) const {
const auto& page_table = process.PageTable().PageTableImpl();
-
- const u8* const page_pointer = page_table.pointers[vaddr >> PAGE_BITS];
- if (page_pointer != nullptr) {
- return true;
- }
-
- if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory) {
- return true;
- }
-
- if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special) {
- return false;
- }
-
- return false;
+ const auto [pointer, type] = page_table.pointers[vaddr >> PAGE_BITS].PointerType();
+ return pointer != nullptr || type == Common::PageType::RasterizerCachedMemory;
}
bool IsValidVirtualAddress(VAddr vaddr) const {
@@ -99,17 +71,15 @@ struct Memory::Impl {
}
u8* GetPointer(const VAddr vaddr) const {
- u8* const page_pointer{current_page_table->pointers[vaddr >> PAGE_BITS]};
- if (page_pointer) {
- return page_pointer + vaddr;
+ const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
+ if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
+ return pointer + vaddr;
}
-
- if (current_page_table->attributes[vaddr >> PAGE_BITS] ==
- Common::PageType::RasterizerCachedMemory) {
+ const auto type = Common::PageTable::PageInfo::ExtractType(raw_pointer);
+ if (type == Common::PageType::RasterizerCachedMemory) {
return GetPointerFromRasterizerCachedMemory(vaddr);
}
-
- return {};
+ return nullptr;
}
u8 Read8(const VAddr addr) {
@@ -120,9 +90,9 @@ struct Memory::Impl {
if ((addr & 1) == 0) {
return Read<u16_le>(addr);
} else {
- const u8 a{Read<u8>(addr)};
- const u8 b{Read<u8>(addr + sizeof(u8))};
- return (static_cast<u16>(b) << 8) | a;
+ const u32 a{Read<u8>(addr)};
+ const u32 b{Read<u8>(addr + sizeof(u8))};
+ return static_cast<u16>((b << 8) | a);
}
}
@@ -130,9 +100,9 @@ struct Memory::Impl {
if ((addr & 3) == 0) {
return Read<u32_le>(addr);
} else {
- const u16 a{Read16(addr)};
- const u16 b{Read16(addr + sizeof(u16))};
- return (static_cast<u32>(b) << 16) | a;
+ const u32 a{Read16(addr)};
+ const u32 b{Read16(addr + sizeof(u16))};
+ return (b << 16) | a;
}
}
@@ -221,7 +191,8 @@ struct Memory::Impl {
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (page_table.attributes[page_index]) {
+ const auto [pointer, type] = page_table.pointers[page_index].PointerType();
+ switch (type) {
case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
@@ -230,10 +201,8 @@ struct Memory::Impl {
break;
}
case Common::PageType::Memory: {
- DEBUG_ASSERT(page_table.pointers[page_index]);
-
- const u8* const src_ptr =
- page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS);
+ DEBUG_ASSERT(pointer);
+ const u8* const src_ptr = pointer + page_offset + (page_index << PAGE_BITS);
std::memcpy(dest_buffer, src_ptr, copy_amount);
break;
}
@@ -267,7 +236,8 @@ struct Memory::Impl {
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (page_table.attributes[page_index]) {
+ const auto [pointer, type] = page_table.pointers[page_index].PointerType();
+ switch (type) {
case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
@@ -276,10 +246,8 @@ struct Memory::Impl {
break;
}
case Common::PageType::Memory: {
- DEBUG_ASSERT(page_table.pointers[page_index]);
-
- const u8* const src_ptr =
- page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS);
+ DEBUG_ASSERT(pointer);
+ const u8* const src_ptr = pointer + page_offset + (page_index << PAGE_BITS);
std::memcpy(dest_buffer, src_ptr, copy_amount);
break;
}
@@ -319,7 +287,8 @@ struct Memory::Impl {
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (page_table.attributes[page_index]) {
+ const auto [pointer, type] = page_table.pointers[page_index].PointerType();
+ switch (type) {
case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
@@ -327,10 +296,8 @@ struct Memory::Impl {
break;
}
case Common::PageType::Memory: {
- DEBUG_ASSERT(page_table.pointers[page_index]);
-
- u8* const dest_ptr =
- page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS);
+ DEBUG_ASSERT(pointer);
+ u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS);
std::memcpy(dest_ptr, src_buffer, copy_amount);
break;
}
@@ -363,7 +330,8 @@ struct Memory::Impl {
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (page_table.attributes[page_index]) {
+ const auto [pointer, type] = page_table.pointers[page_index].PointerType();
+ switch (type) {
case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
@@ -371,10 +339,8 @@ struct Memory::Impl {
break;
}
case Common::PageType::Memory: {
- DEBUG_ASSERT(page_table.pointers[page_index]);
-
- u8* const dest_ptr =
- page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS);
+ DEBUG_ASSERT(pointer);
+ u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS);
std::memcpy(dest_ptr, src_buffer, copy_amount);
break;
}
@@ -413,7 +379,8 @@ struct Memory::Impl {
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (page_table.attributes[page_index]) {
+ const auto [pointer, type] = page_table.pointers[page_index].PointerType();
+ switch (type) {
case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
@@ -421,10 +388,8 @@ struct Memory::Impl {
break;
}
case Common::PageType::Memory: {
- DEBUG_ASSERT(page_table.pointers[page_index]);
-
- u8* dest_ptr =
- page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS);
+ DEBUG_ASSERT(pointer);
+ u8* const dest_ptr = pointer + page_offset + (page_index << PAGE_BITS);
std::memset(dest_ptr, 0, copy_amount);
break;
}
@@ -460,7 +425,8 @@ struct Memory::Impl {
std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size);
const auto current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
- switch (page_table.attributes[page_index]) {
+ const auto [pointer, type] = page_table.pointers[page_index].PointerType();
+ switch (type) {
case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
@@ -469,9 +435,8 @@ struct Memory::Impl {
break;
}
case Common::PageType::Memory: {
- DEBUG_ASSERT(page_table.pointers[page_index]);
- const u8* src_ptr =
- page_table.pointers[page_index] + page_offset + (page_index << PAGE_BITS);
+ DEBUG_ASSERT(pointer);
+ const u8* src_ptr = pointer + page_offset + (page_index << PAGE_BITS);
WriteBlock(process, dest_addr, src_ptr, copy_amount);
break;
}
@@ -501,16 +466,15 @@ struct Memory::Impl {
if (vaddr == 0) {
return;
}
-
// Iterate over a contiguous CPU address space, which corresponds to the specified GPU
// address space, marking the region as un/cached. The region is marked un/cached at a
// granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size
// is different). This assumes the specified GPU address region is contiguous as well.
- u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
- for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
- Common::PageType& page_type{current_page_table->attributes[vaddr >> PAGE_BITS]};
-
+ const u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
+ for (u64 i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
+ const Common::PageType page_type{
+ current_page_table->pointers[vaddr >> PAGE_BITS].Type()};
if (cached) {
// Switch page type to cached if now cached
switch (page_type) {
@@ -519,8 +483,8 @@ struct Memory::Impl {
// space, for example, a system module need not have a VRAM mapping.
break;
case Common::PageType::Memory:
- page_type = Common::PageType::RasterizerCachedMemory;
- current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
+ current_page_table->pointers[vaddr >> PAGE_BITS].Store(
+ nullptr, Common::PageType::RasterizerCachedMemory);
break;
case Common::PageType::RasterizerCachedMemory:
// There can be more than one GPU region mapped per CPU region, so it's common
@@ -541,16 +505,16 @@ struct Memory::Impl {
// that this area is already unmarked as cached.
break;
case Common::PageType::RasterizerCachedMemory: {
- u8* pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~PAGE_MASK)};
+ u8* const pointer{GetPointerFromRasterizerCachedMemory(vaddr & ~PAGE_MASK)};
if (pointer == nullptr) {
// It's possible that this function has been called while updating the
// pagetable after unmapping a VMA. In that case the underlying VMA will no
// longer exist, and we should just leave the pagetable entry blank.
- page_type = Common::PageType::Unmapped;
+ current_page_table->pointers[vaddr >> PAGE_BITS].Store(
+ nullptr, Common::PageType::Unmapped);
} else {
- current_page_table->pointers[vaddr >> PAGE_BITS] =
- pointer - (vaddr & ~PAGE_MASK);
- page_type = Common::PageType::Memory;
+ current_page_table->pointers[vaddr >> PAGE_BITS].Store(
+ pointer - (vaddr & ~PAGE_MASK), Common::PageType::Memory);
}
break;
}
@@ -567,7 +531,7 @@ struct Memory::Impl {
* @param page_table The page table to use to perform the mapping.
* @param base The base address to begin mapping at.
* @param size The total size of the range in bytes.
- * @param memory The memory to map.
+ * @param target The target address to begin mapping from.
* @param type The page type to map the memory as.
*/
void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target,
@@ -580,7 +544,7 @@ struct Memory::Impl {
auto& gpu = system.GPU();
for (u64 i = 0; i < size; i++) {
const auto page = base + i;
- if (page_table.attributes[page] == Common::PageType::RasterizerCachedMemory) {
+ if (page_table.pointers[page].Type() == Common::PageType::RasterizerCachedMemory) {
gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE);
}
}
@@ -595,20 +559,18 @@ struct Memory::Impl {
"Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE);
while (base != end) {
- page_table.attributes[base] = type;
- page_table.pointers[base] = nullptr;
+ page_table.pointers[base].Store(nullptr, type);
page_table.backing_addr[base] = 0;
base += 1;
}
} else {
while (base != end) {
- page_table.pointers[base] =
- system.DeviceMemory().GetPointer(target) - (base << PAGE_BITS);
- page_table.attributes[base] = type;
+ page_table.pointers[base].Store(
+ system.DeviceMemory().GetPointer(target) - (base << PAGE_BITS), type);
page_table.backing_addr[base] = target - (base << PAGE_BITS);
- ASSERT_MSG(page_table.pointers[base],
+ ASSERT_MSG(page_table.pointers[base].Pointer(),
"memory mapping base yield a nullptr within the table");
base += 1;
@@ -630,16 +592,14 @@ struct Memory::Impl {
*/
template <typename T>
T Read(const VAddr vaddr) {
- const u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
- if (page_pointer != nullptr) {
- // NOTE: Avoid adding any extra logic to this fast-path block
+ // Avoid adding any extra logic to this fast-path block
+ const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
+ if (const u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
T value;
- std::memcpy(&value, &page_pointer[vaddr], sizeof(T));
+ std::memcpy(&value, &pointer[vaddr], sizeof(T));
return value;
}
-
- const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
- switch (type) {
+ switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
case Common::PageType::Unmapped:
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
return 0;
@@ -667,20 +627,16 @@ struct Memory::Impl {
* @tparam T The data type to write to memory. This type *must* be
* trivially copyable, otherwise the behavior of this function
* is undefined.
- *
- * @returns The instance of T write to the specified virtual address.
*/
template <typename T>
void Write(const VAddr vaddr, const T data) {
- u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
- if (page_pointer != nullptr) {
- // NOTE: Avoid adding any extra logic to this fast-path block
- std::memcpy(&page_pointer[vaddr], &data, sizeof(T));
+ // Avoid adding any extra logic to this fast-path block
+ const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
+ if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
+ std::memcpy(&pointer[vaddr], &data, sizeof(T));
return;
}
-
- const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
- switch (type) {
+ switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
case Common::PageType::Unmapped:
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
static_cast<u32>(data), vaddr);
@@ -701,15 +657,13 @@ struct Memory::Impl {
template <typename T>
bool WriteExclusive(const VAddr vaddr, const T data, const T expected) {
- u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
- if (page_pointer != nullptr) {
+ const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
+ if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
// NOTE: Avoid adding any extra logic to this fast-path block
- auto* pointer = reinterpret_cast<volatile T*>(&page_pointer[vaddr]);
- return Common::AtomicCompareAndSwap(pointer, data, expected);
+ const auto volatile_pointer = reinterpret_cast<volatile T*>(&pointer[vaddr]);
+ return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
}
-
- const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
- switch (type) {
+ switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
case Common::PageType::Unmapped:
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
static_cast<u32>(data), vaddr);
@@ -730,15 +684,13 @@ struct Memory::Impl {
}
bool WriteExclusive128(const VAddr vaddr, const u128 data, const u128 expected) {
- u8* const page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
- if (page_pointer != nullptr) {
+ const uintptr_t raw_pointer = current_page_table->pointers[vaddr >> PAGE_BITS].Raw();
+ if (u8* const pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) {
// NOTE: Avoid adding any extra logic to this fast-path block
- auto* pointer = reinterpret_cast<volatile u64*>(&page_pointer[vaddr]);
- return Common::AtomicCompareAndSwap(pointer, data, expected);
+ const auto volatile_pointer = reinterpret_cast<volatile u64*>(&pointer[vaddr]);
+ return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
}
-
- const Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
- switch (type) {
+ switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) {
case Common::PageType::Unmapped:
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}{:016X}", sizeof(data) * 8,
static_cast<u64>(data[1]), static_cast<u64>(data[0]), vaddr);
@@ -773,25 +725,10 @@ void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size
impl->MapMemoryRegion(page_table, base, size, target);
}
-void Memory::MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer mmio_handler) {
- impl->MapIoRegion(page_table, base, size, std::move(mmio_handler));
-}
-
void Memory::UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
impl->UnmapRegion(page_table, base, size);
}
-void Memory::AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer hook) {
- impl->AddDebugHook(page_table, base, size, std::move(hook));
-}
-
-void Memory::RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer hook) {
- impl->RemoveDebugHook(page_table, base, size, std::move(hook));
-}
-
bool Memory::IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) const {
return impl->IsValidVirtualAddress(process, vaddr);
}
diff --git a/src/core/memory.h b/src/core/memory.h
index 4a1cc63f4..705ebb23d 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -8,7 +8,6 @@
#include <memory>
#include <string>
#include "common/common_types.h"
-#include "common/memory_hook.h"
namespace Common {
struct PageTable;
@@ -78,17 +77,6 @@ public:
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target);
/**
- * Maps a region of the emulated process address space as a IO region.
- *
- * @param page_table The page table of the emulated process.
- * @param base The address to start mapping at. Must be page-aligned.
- * @param size The amount of bytes to map. Must be page-aligned.
- * @param mmio_handler The handler that backs the mapping.
- */
- void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer mmio_handler);
-
- /**
* Unmaps a region of the emulated process address space.
*
* @param page_table The page table of the emulated process.
@@ -98,28 +86,6 @@ public:
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size);
/**
- * Adds a memory hook to intercept reads and writes to given region of memory.
- *
- * @param page_table The page table of the emulated process
- * @param base The starting address to apply the hook to.
- * @param size The size of the memory region to apply the hook to, in bytes.
- * @param hook The hook to apply to the region of memory.
- */
- void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer hook);
-
- /**
- * Removes a memory hook from a given range of memory.
- *
- * @param page_table The page table of the emulated process.
- * @param base The starting address to remove the hook from.
- * @param size The size of the memory region to remove the hook from, in bytes.
- * @param hook The hook to remove from the specified region of memory.
- */
- void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
- Common::MemoryHookPointer hook);
-
- /**
* Checks whether or not the supplied address is a valid virtual
* address for the given process.
*
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index e503118dd..2dd0eb0f8 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -19,10 +19,24 @@
#include "core/memory/cheat_engine.h"
namespace Core::Memory {
-
+namespace {
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
+std::string_view ExtractName(std::string_view data, std::size_t start_index, char match) {
+ auto end_index = start_index;
+ while (data[end_index] != match) {
+ ++end_index;
+ if (end_index > data.size() ||
+ (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
+ return {};
+ }
+ }
+
+ return data.substr(start_index, end_index - start_index);
+}
+} // Anonymous namespace
+
StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata)
: metadata(metadata), system(system) {}
@@ -82,26 +96,9 @@ CheatParser::~CheatParser() = default;
TextCheatParser::~TextCheatParser() = default;
-namespace {
-template <char match>
-std::string_view ExtractName(std::string_view data, std::size_t start_index) {
- auto end_index = start_index;
- while (data[end_index] != match) {
- ++end_index;
- if (end_index > data.size() ||
- (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
- return {};
- }
- }
-
- return data.substr(start_index, end_index - start_index);
-}
-} // Anonymous namespace
-
-std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
- std::string_view data) const {
+std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
std::vector<CheatEntry> out(1);
- std::optional<u64> current_entry = std::nullopt;
+ std::optional<u64> current_entry;
for (std::size_t i = 0; i < data.size(); ++i) {
if (::isspace(data[i])) {
@@ -115,7 +112,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
return {};
}
- const auto name = ExtractName<'}'>(data, i + 1);
+ const auto name = ExtractName(data, i + 1, '}');
if (name.empty()) {
return {};
}
@@ -132,7 +129,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
current_entry = out.size();
out.emplace_back();
- const auto name = ExtractName<']'>(data, i + 1);
+ const auto name = ExtractName(data, i + 1, ']');
if (name.empty()) {
return {};
}
@@ -156,8 +153,9 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
return {};
}
+ const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10));
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
- std::stoul(hex, nullptr, 0x10);
+ value;
i += 8;
} else {
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
index fa039a831..a31002346 100644
--- a/src/core/memory/cheat_engine.h
+++ b/src/core/memory/cheat_engine.h
@@ -47,8 +47,7 @@ class CheatParser {
public:
virtual ~CheatParser();
- virtual std::vector<CheatEntry> Parse(const Core::System& system,
- std::string_view data) const = 0;
+ [[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0;
};
// CheatParser implementation that parses text files
@@ -56,7 +55,7 @@ class TextCheatParser final : public CheatParser {
public:
~TextCheatParser() override;
- std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override;
+ [[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override;
};
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp
index 56d173b5e..681e93468 100644
--- a/src/core/network/network.cpp
+++ b/src/core/network/network.cpp
@@ -11,7 +11,7 @@
#ifdef _WIN32
#define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname
#include <winsock2.h>
-#elif __unix__
+#elif YUZU_UNIX
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
@@ -54,7 +54,7 @@ constexpr IPv4Address TranslateIPv4(in_addr addr) {
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result;
-#ifdef __unix__
+#if YUZU_UNIX
result.sin_len = sizeof(result);
#endif
@@ -63,7 +63,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
result.sin_family = AF_INET;
break;
default:
- UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", static_cast<int>(input.family));
+ UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family);
result.sin_family = AF_INET;
break;
}
@@ -99,7 +99,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) {
return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR;
}
-#elif __unix__ // ^ _WIN32 v __unix__
+#elif YUZU_UNIX // ^ _WIN32 v YUZU_UNIX
using SOCKET = int;
using WSAPOLLFD = pollfd;
@@ -133,7 +133,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
result.sin_family = AF_INET;
break;
default:
- UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", static_cast<int>(input.family));
+ UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.family);
result.sin_family = AF_INET;
break;
}
@@ -148,7 +148,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
}
int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
- return poll(fds, nfds, timeout);
+ return poll(fds, static_cast<nfds_t>(nfds), timeout);
}
int closesocket(SOCKET fd) {
@@ -186,7 +186,7 @@ int TranslateDomain(Domain domain) {
case Domain::INET:
return AF_INET;
default:
- UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain));
+ UNIMPLEMENTED_MSG("Unimplemented domain={}", domain);
return 0;
}
}
@@ -198,7 +198,7 @@ int TranslateType(Type type) {
case Type::DGRAM:
return SOCK_DGRAM;
default:
- UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type));
+ UNIMPLEMENTED_MSG("Unimplemented type={}", type);
return 0;
}
}
@@ -210,7 +210,7 @@ int TranslateProtocol(Protocol protocol) {
case Protocol::UDP:
return IPPROTO_UDP;
default:
- UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast<int>(protocol));
+ UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
return 0;
}
}
@@ -238,45 +238,45 @@ SockAddrIn TranslateToSockAddrIn(sockaddr input_) {
return result;
}
-u16 TranslatePollEvents(u16 events) {
- u16 result = 0;
+short TranslatePollEvents(PollEvents events) {
+ short result = 0;
- if (events & POLL_IN) {
- events &= ~POLL_IN;
+ if (True(events & PollEvents::In)) {
+ events &= ~PollEvents::In;
result |= POLLIN;
}
- if (events & POLL_PRI) {
- events &= ~POLL_PRI;
+ if (True(events & PollEvents::Pri)) {
+ events &= ~PollEvents::Pri;
#ifdef _WIN32
LOG_WARNING(Service, "Winsock doesn't support POLLPRI");
#else
- result |= POLL_PRI;
+ result |= POLLPRI;
#endif
}
- if (events & POLL_OUT) {
- events &= ~POLL_OUT;
+ if (True(events & PollEvents::Out)) {
+ events &= ~PollEvents::Out;
result |= POLLOUT;
}
- UNIMPLEMENTED_IF_MSG(events != 0, "Unhandled guest events=0x{:x}", events);
+ UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events);
return result;
}
-u16 TranslatePollRevents(u16 revents) {
- u16 result = 0;
- const auto translate = [&result, &revents](int host, unsigned guest) {
- if (revents & host) {
- revents &= ~host;
+PollEvents TranslatePollRevents(short revents) {
+ PollEvents result{};
+ const auto translate = [&result, &revents](short host, PollEvents guest) {
+ if ((revents & host) != 0) {
+ revents &= static_cast<short>(~host);
result |= guest;
}
};
- translate(POLLIN, POLL_IN);
- translate(POLLPRI, POLL_PRI);
- translate(POLLOUT, POLL_OUT);
- translate(POLLERR, POLL_ERR);
- translate(POLLHUP, POLL_HUP);
+ translate(POLLIN, PollEvents::In);
+ translate(POLLPRI, PollEvents::Pri);
+ translate(POLLOUT, PollEvents::Out);
+ translate(POLLERR, PollEvents::Err);
+ translate(POLLHUP, PollEvents::Hup);
UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents);
@@ -408,7 +408,7 @@ std::pair<Socket::AcceptResult, Errno> Socket::Accept() {
Errno Socket::Connect(SockAddrIn addr_in) {
const sockaddr host_addr_in = TranslateFromSockAddrIn(addr_in);
- if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != INVALID_SOCKET) {
+ if (connect(fd, &host_addr_in, sizeof(host_addr_in)) != SOCKET_ERROR) {
return Errno::SUCCESS;
}
@@ -482,7 +482,7 @@ Errno Socket::Shutdown(ShutdownHow how) {
host_how = SD_BOTH;
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented flag how={}", static_cast<int>(how));
+ UNIMPLEMENTED_MSG("Unimplemented flag how={}", how);
return Errno::SUCCESS;
}
if (shutdown(fd, host_how) != SOCKET_ERROR) {
@@ -503,10 +503,10 @@ std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) {
ASSERT(flags == 0);
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
- const int result =
+ const auto result =
recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0);
if (result != SOCKET_ERROR) {
- return {result, Errno::SUCCESS};
+ return {static_cast<s32>(result), Errno::SUCCESS};
}
switch (const int ec = LastError()) {
@@ -531,14 +531,14 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, Sock
socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
sockaddr* const p_addr_in = addr ? &addr_in : nullptr;
- const int result = recvfrom(fd, reinterpret_cast<char*>(message.data()),
- static_cast<int>(message.size()), 0, p_addr_in, p_addrlen);
+ const auto result = recvfrom(fd, reinterpret_cast<char*>(message.data()),
+ static_cast<int>(message.size()), 0, p_addr_in, p_addrlen);
if (result != SOCKET_ERROR) {
if (addr) {
ASSERT(addrlen == sizeof(addr_in));
*addr = TranslateToSockAddrIn(addr_in);
}
- return {result, Errno::SUCCESS};
+ return {static_cast<s32>(result), Errno::SUCCESS};
}
switch (const int ec = LastError()) {
@@ -558,10 +558,10 @@ std::pair<s32, Errno> Socket::Send(const std::vector<u8>& message, int flags) {
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
ASSERT(flags == 0);
- const int result = send(fd, reinterpret_cast<const char*>(message.data()),
- static_cast<int>(message.size()), 0);
+ const auto result = send(fd, reinterpret_cast<const char*>(message.data()),
+ static_cast<int>(message.size()), 0);
if (result != SOCKET_ERROR) {
- return {result, Errno::SUCCESS};
+ return {static_cast<s32>(result), Errno::SUCCESS};
}
const int ec = LastError();
@@ -591,10 +591,10 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message,
to = &host_addr_in;
}
- const int result = sendto(fd, reinterpret_cast<const char*>(message.data()),
- static_cast<int>(message.size()), 0, to, tolen);
+ const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()),
+ static_cast<int>(message.size()), 0, to, tolen);
if (result != SOCKET_ERROR) {
- return {result, Errno::SUCCESS};
+ return {static_cast<s32>(result), Errno::SUCCESS};
}
const int ec = LastError();
diff --git a/src/core/network/network.h b/src/core/network/network.h
index 0622e4593..76b2821f2 100644
--- a/src/core/network/network.h
+++ b/src/core/network/network.h
@@ -61,19 +61,25 @@ struct SockAddrIn {
};
/// Cross-platform poll fd structure
+
+enum class PollEvents : u16 {
+ // Using Pascal case because IN is a macro on Windows.
+ In = 1 << 0,
+ Pri = 1 << 1,
+ Out = 1 << 2,
+ Err = 1 << 3,
+ Hup = 1 << 4,
+ Nval = 1 << 5,
+};
+
+DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
+
struct PollFD {
Socket* socket;
- u16 events;
- u16 revents;
+ PollEvents events;
+ PollEvents revents;
};
-constexpr u16 POLL_IN = 1 << 0;
-constexpr u16 POLL_PRI = 1 << 1;
-constexpr u16 POLL_OUT = 1 << 2;
-constexpr u16 POLL_ERR = 1 << 3;
-constexpr u16 POLL_HUP = 1 << 4;
-constexpr u16 POLL_NVAL = 1 << 5;
-
class NetworkInstance {
public:
explicit NetworkInstance();
diff --git a/src/core/network/sockets.h b/src/core/network/sockets.h
index 7bdff0fe4..a44393325 100644
--- a/src/core/network/sockets.h
+++ b/src/core/network/sockets.h
@@ -9,7 +9,7 @@
#if defined(_WIN32)
#include <winsock.h>
-#elif !defined(__unix__)
+#elif !YUZU_UNIX
#error "Platform not implemented"
#endif
@@ -84,7 +84,7 @@ public:
#if defined(_WIN32)
SOCKET fd = INVALID_SOCKET;
-#elif defined(__unix__)
+#elif YUZU_UNIX
int fd = -1;
#endif
};
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index 0becdf642..f199c3362 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -20,7 +20,6 @@
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
#include "core/hle/result.h"
-#include "core/hle/service/lm/manager.h"
#include "core/memory.h"
#include "core/reporter.h"
#include "core/settings.h"
@@ -360,55 +359,6 @@ void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp));
}
-void Reporter::SaveLogReport(u32 destination, std::vector<Service::LM::LogMessage> messages) const {
- if (!IsReportingEnabled()) {
- return;
- }
-
- const auto timestamp = GetTimestamp();
- json out;
-
- out["yuzu_version"] = GetYuzuVersionData();
- out["report_common"] =
- GetReportCommonData(system.CurrentProcess()->GetTitleID(), RESULT_SUCCESS, timestamp);
-
- out["log_destination"] =
- fmt::format("{}", static_cast<Service::LM::DestinationFlag>(destination));
-
- auto json_messages = json::array();
- std::transform(messages.begin(), messages.end(), std::back_inserter(json_messages),
- [](const Service::LM::LogMessage& message) {
- json out;
- out["is_head"] = fmt::format("{}", message.header.IsHeadLog());
- out["is_tail"] = fmt::format("{}", message.header.IsTailLog());
- out["pid"] = fmt::format("{:016X}", message.header.pid);
- out["thread_context"] =
- fmt::format("{:016X}", message.header.thread_context);
- out["payload_size"] = fmt::format("{:016X}", message.header.payload_size);
- out["flags"] = fmt::format("{:04X}", message.header.flags.Value());
- out["severity"] = fmt::format("{}", message.header.severity.Value());
- out["verbosity"] = fmt::format("{:02X}", message.header.verbosity);
-
- auto fields = json::array();
- std::transform(message.fields.begin(), message.fields.end(),
- std::back_inserter(fields), [](const auto& kv) {
- json out;
- out["type"] = fmt::format("{}", kv.first);
- out["data"] =
- Service::LM::FormatField(kv.first, kv.second);
- return out;
- });
-
- out["fields"] = std::move(fields);
- return out;
- });
-
- out["log_messages"] = std::move(json_messages);
-
- SaveToFile(std::move(out),
- GetPath("log_report", system.CurrentProcess()->GetTitleID(), timestamp));
-}
-
void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode,
std::string log_message) const {
if (!IsReportingEnabled())
diff --git a/src/core/reporter.h b/src/core/reporter.h
index 86d760cf0..b2c2d9a2e 100644
--- a/src/core/reporter.h
+++ b/src/core/reporter.h
@@ -72,9 +72,6 @@ public:
void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode,
std::string log_message) const;
- // Used by lm services
- void SaveLogReport(u32 destination, std::vector<Service::LM::LogMessage> messages) const;
-
// Can be used anywhere to generate a backtrace and general info report at any point during
// execution. Not intended to be used for anything other than debugging or testing.
void SaveUserReport() const;
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 28d3f9099..2ae5196e0 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -4,9 +4,10 @@
#include <string_view>
+#include "common/assert.h"
#include "common/file_util.h"
+#include "common/logging/log.h"
#include "core/core.h"
-#include "core/gdbstub/gdbstub.h"
#include "core/hle/service/hid/hid.h"
#include "core/settings.h"
#include "video_core/renderer_base.h"
@@ -14,7 +15,7 @@
namespace Settings {
Values values = {};
-bool configuring_global = true;
+static bool configuring_global = true;
std::string GetTimeZoneString() {
static constexpr std::array timezones{
@@ -31,13 +32,9 @@ std::string GetTimeZoneString() {
return timezones[time_zone_index];
}
-void Apply() {
- GDBStub::SetServerPort(values.gdbstub_port);
- GDBStub::ToggleServer(values.use_gdbstub);
-
- auto& system_instance = Core::System::GetInstance();
- if (system_instance.IsPoweredOn()) {
- system_instance.Renderer().RefreshBaseSettings();
+void Apply(Core::System& system) {
+ if (system.IsPoweredOn()) {
+ system.Renderer().RefreshBaseSettings();
}
Service::HID::ReloadInputDevices();
@@ -49,13 +46,14 @@ void LogSettings() {
};
LOG_INFO(Config, "yuzu Configuration:");
- log_setting("Controls_UseDockedMode", values.use_docked_mode);
+ log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
log_setting("System_CurrentUser", values.current_user);
log_setting("System_LanguageIndex", values.language_index.GetValue());
log_setting("System_RegionIndex", values.region_index.GetValue());
log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue());
log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());
+ log_setting("CPU_Accuracy", values.cpu_accuracy);
log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue());
log_setting("Renderer_UseFrameLimit", values.use_frame_limit.GetValue());
log_setting("Renderer_FrameLimit", values.frame_limit.GetValue());
@@ -63,6 +61,7 @@ void LogSettings() {
log_setting("Renderer_GPUAccuracyLevel", values.gpu_accuracy.GetValue());
log_setting("Renderer_UseAsynchronousGpuEmulation",
values.use_asynchronous_gpu_emulation.GetValue());
+ log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue());
log_setting("Renderer_UseVsync", values.use_vsync.GetValue());
log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue());
log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
@@ -71,20 +70,22 @@ void LogSettings() {
log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
log_setting("Audio_OutputDevice", values.audio_device_id);
log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd);
+ log_setting("DataStorage_CacheDir", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir));
+ log_setting("DataStorage_ConfigDir", Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir));
+ log_setting("DataStorage_LoadDir", Common::FS::GetUserPath(Common::FS::UserPath::LoadDir));
log_setting("DataStorage_NandDir", Common::FS::GetUserPath(Common::FS::UserPath::NANDDir));
log_setting("DataStorage_SdmcDir", Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir));
- log_setting("Debugging_UseGdbstub", values.use_gdbstub);
- log_setting("Debugging_GdbstubPort", values.gdbstub_port);
log_setting("Debugging_ProgramArgs", values.program_args);
log_setting("Services_BCATBackend", values.bcat_backend);
log_setting("Services_BCATBoxcatLocal", values.bcat_boxcat_local);
}
-float Volume() {
- if (values.audio_muted) {
- return 0.0f;
- }
- return values.volume.GetValue();
+bool IsConfiguringGlobal() {
+ return configuring_global;
+}
+
+void SetConfiguringGlobal(bool is_global) {
+ configuring_global = is_global;
}
bool IsGPULevelExtreme() {
@@ -96,9 +97,16 @@ bool IsGPULevelHigh() {
values.gpu_accuracy.GetValue() == GPUAccuracy::High;
}
-void RestoreGlobalState() {
+float Volume() {
+ if (values.audio_muted) {
+ return 0.0f;
+ }
+ return values.volume.GetValue();
+}
+
+void RestoreGlobalState(bool is_powered_on) {
// If a game is running, DO NOT restore the global settings state
- if (Core::System::GetInstance().IsPoweredOn()) {
+ if (is_powered_on) {
return;
}
@@ -119,6 +127,7 @@ void RestoreGlobalState() {
values.use_disk_shader_cache.SetGlobal(true);
values.gpu_accuracy.SetGlobal(true);
values.use_asynchronous_gpu_emulation.SetGlobal(true);
+ values.use_nvdec_emulation.SetGlobal(true);
values.use_vsync.SetGlobal(true);
values.use_assembly_shaders.SetGlobal(true);
values.use_asynchronous_shaders.SetGlobal(true);
@@ -134,11 +143,12 @@ void RestoreGlobalState() {
values.rng_seed.SetGlobal(true);
values.custom_rtc.SetGlobal(true);
values.sound_index.SetGlobal(true);
-}
-void Sanitize() {
- values.use_asynchronous_gpu_emulation.SetValue(
- values.use_asynchronous_gpu_emulation.GetValue() || values.use_multi_core.GetValue());
+ // Controls
+ values.players.SetGlobal(true);
+ values.use_docked_mode.SetGlobal(true);
+ values.vibration_enabled.SetGlobal(true);
+ values.motion_enabled.SetGlobal(true);
}
} // namespace Settings
diff --git a/src/core/settings.h b/src/core/settings.h
index 732c6a894..d849dded3 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -14,6 +14,10 @@
#include "common/common_types.h"
#include "input_common/settings.h"
+namespace Core {
+class System;
+}
+
namespace Settings {
enum class RendererBackend {
@@ -33,8 +37,6 @@ enum class CPUAccuracy {
DebugMode = 2,
};
-extern bool configuring_global;
-
template <typename Type>
class Setting final {
public:
@@ -67,6 +69,43 @@ private:
Type local{};
};
+/**
+ * The InputSetting class allows for getting a reference to either the global or local members.
+ * This is required as we cannot easily modify the values of user-defined types within containers
+ * using the SetValue() member function found in the Setting class. The primary purpose of this
+ * class is to store an array of 10 PlayerInput structs for both the global and local (per-game)
+ * setting and allows for easily accessing and modifying both settings.
+ */
+template <typename Type>
+class InputSetting final {
+public:
+ InputSetting() = default;
+ explicit InputSetting(Type val) : global{val} {}
+ ~InputSetting() = default;
+ void SetGlobal(bool to_global) {
+ use_global = to_global;
+ }
+ bool UsingGlobal() const {
+ return use_global;
+ }
+ Type& GetValue(bool need_global = false) {
+ if (use_global || need_global) {
+ return global;
+ }
+ return local;
+ }
+
+private:
+ bool use_global = true;
+ Type global{};
+ Type local{};
+};
+
+struct TouchFromButtonMap {
+ std::string name;
+ std::vector<std::string> buttons;
+};
+
struct Values {
// Audio
std::string audio_device_id;
@@ -92,13 +131,14 @@ struct Values {
bool cpuopt_unsafe_unfuse_fma;
bool cpuopt_unsafe_reduce_fp_error;
+ bool cpuopt_unsafe_inaccurate_nan;
// Renderer
Setting<RendererBackend> renderer_backend;
bool renderer_debug;
Setting<int> vulkan_device;
- Setting<u16> resolution_factor = Setting(static_cast<u16>(1));
+ Setting<u16> resolution_factor{1};
Setting<int> aspect_ratio;
Setting<int> max_anisotropy;
Setting<bool> use_frame_limit;
@@ -106,6 +146,7 @@ struct Values {
Setting<bool> use_disk_shader_cache;
Setting<GPUAccuracy> gpu_accuracy;
Setting<bool> use_asynchronous_gpu_emulation;
+ Setting<bool> use_nvdec_emulation;
Setting<bool> use_vsync;
Setting<bool> use_assembly_shaders;
Setting<bool> use_asynchronous_shaders;
@@ -129,14 +170,24 @@ struct Values {
Setting<s32> sound_index;
// Controls
- std::array<PlayerInput, 10> players;
+ InputSetting<std::array<PlayerInput, 10>> players;
+
+ Setting<bool> use_docked_mode;
- bool use_docked_mode;
+ Setting<bool> vibration_enabled;
+ Setting<bool> enable_accurate_vibrations;
+
+ Setting<bool> motion_enabled;
+ std::string motion_device;
+ std::string udp_input_servers;
+ bool mouse_panning;
+ float mouse_panning_sensitivity;
bool mouse_enabled;
std::string mouse_device;
MouseButtonsRaw mouse_buttons;
+ bool emulate_analog_keyboard;
bool keyboard_enabled;
KeyboardKeysRaw keyboard_keys;
KeyboardModsRaw keyboard_mods;
@@ -145,15 +196,14 @@ struct Values {
ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs;
- std::string motion_device;
+ TouchscreenInput touchscreen;
- bool vibration_enabled;
+ bool use_touch_from_button;
+ std::string touch_device;
+ int touch_from_button_map_index;
+ std::vector<TouchFromButtonMap> touch_from_button_maps;
- TouchscreenInput touchscreen;
std::atomic_bool is_device_reload_pending{true};
- std::string udp_input_address;
- u16 udp_input_port;
- u8 udp_pad_index;
// Data Storage
bool use_virtual_sd;
@@ -171,8 +221,9 @@ struct Values {
bool reporting_services;
bool quest_flag;
bool disable_macro_jit;
+ bool extended_logging;
- // Misceallaneous
+ // Miscellaneous
std::string log_filter;
bool use_dev_keys;
@@ -188,22 +239,24 @@ struct Values {
// Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons;
-} extern values;
+};
-float Volume();
+extern Values values;
+
+bool IsConfiguringGlobal();
+void SetConfiguringGlobal(bool is_global);
bool IsGPULevelExtreme();
bool IsGPULevelHigh();
+float Volume();
+
std::string GetTimeZoneString();
-void Apply();
+void Apply(Core::System& system);
void LogSettings();
// Restore the global state of all applicable settings in the Values struct
-void RestoreGlobalState();
-
-// Fixes settings that are known to cause issues with the emulator
-void Sanitize();
+void RestoreGlobalState(bool is_powered_on);
} // namespace Settings
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index da09c0dbc..d11b15f38 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -147,7 +147,9 @@ TelemetrySession::~TelemetrySession() {
}
}
-void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
+void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider) {
// Log one-time top-level information
AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
@@ -167,7 +169,10 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
app_loader.ReadTitle(name);
if (name.empty()) {
- const auto metadata = FileSys::PatchManager(program_id).GetControlMetadata();
+ const auto metadata = [&content_provider, &fsc, program_id] {
+ const FileSys::PatchManager pm{program_id, fsc, content_provider};
+ return pm.GetControlMetadata();
+ }();
if (metadata.first != nullptr) {
name = metadata.first->GetApplicationName();
}
@@ -206,12 +211,14 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue()));
AddField(field_type, "Renderer_UseAsynchronousGpuEmulation",
Settings::values.use_asynchronous_gpu_emulation.GetValue());
+ AddField(field_type, "Renderer_UseNvdecEmulation",
+ Settings::values.use_nvdec_emulation.GetValue());
AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue());
AddField(field_type, "Renderer_UseAssemblyShaders",
Settings::values.use_assembly_shaders.GetValue());
AddField(field_type, "Renderer_UseAsynchronousShaders",
Settings::values.use_asynchronous_shaders.GetValue());
- AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode);
+ AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue());
}
bool TelemetrySession::SubmitTestcase() {
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 66789d4bd..6f3d45bea 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -7,10 +7,18 @@
#include <string>
#include "common/telemetry.h"
+namespace FileSys {
+class ContentProvider;
+}
+
namespace Loader {
class AppLoader;
}
+namespace Service::FileSystem {
+class FileSystemController;
+}
+
namespace Core {
/**
@@ -40,10 +48,14 @@ public:
* - Title file format
* - Miscellaneous settings values.
*
- * @param app_loader The application loader to use to retrieve
- * title-specific information.
+ * @param app_loader The application loader to use to retrieve
+ * title-specific information.
+ * @param fsc Filesystem controller to use to retrieve info.
+ * @param content_provider Content provider to use to retrieve info.
*/
- void AddInitialInfo(Loader::AppLoader& app_loader);
+ void AddInitialInfo(Loader::AppLoader& app_loader,
+ const Service::FileSystem::FileSystemController& fsc,
+ const FileSys::ContentProvider& content_provider);
/**
* Wrapper around the Telemetry::FieldCollection::AddField method.
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 56267c8a8..38ab31898 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -5,14 +5,22 @@ add_library(input_common STATIC
keyboard.h
main.cpp
main.h
- motion_emu.cpp
- motion_emu.h
+ motion_from_button.cpp
+ motion_from_button.h
+ motion_input.cpp
+ motion_input.h
settings.cpp
settings.h
+ touch_from_button.cpp
+ touch_from_button.h
gcadapter/gc_adapter.cpp
gcadapter/gc_adapter.h
gcadapter/gc_poller.cpp
gcadapter/gc_poller.h
+ mouse/mouse_input.cpp
+ mouse/mouse_input.h
+ mouse/mouse_poller.cpp
+ mouse/mouse_poller.h
sdl/sdl.cpp
sdl/sdl.h
udp/client.cpp
@@ -23,6 +31,39 @@ add_library(input_common STATIC
udp/udp.h
)
+if (MSVC)
+ target_compile_options(input_common PRIVATE
+ /W4
+ /WX
+
+ # 'expression' : signed/unsigned mismatch
+ /we4018
+ # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point)
+ /we4244
+ # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch
+ /we4245
+ # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
+ /we4254
+ # 'var' : conversion from 'size_t' to 'type', possible loss of data
+ /we4267
+ # 'context' : truncation from 'type1' to 'type2'
+ /we4305
+ )
+else()
+ target_compile_options(input_common PRIVATE
+ -Werror
+ -Werror=conversion
+ -Werror=ignored-qualifiers
+ -Werror=implicit-fallthrough
+ -Werror=reorder
+ -Werror=shadow
+ -Werror=sign-compare
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
+ -Werror=unused-variable
+ )
+endif()
+
if(SDL2_FOUND)
target_sources(input_common PRIVATE
sdl/sdl_impl.cpp
diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp
index 6cabdaa3c..770893687 100755
--- a/src/input_common/analog_from_button.cpp
+++ b/src/input_common/analog_from_button.cpp
@@ -2,6 +2,12 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <atomic>
+#include <chrono>
+#include <cmath>
+#include <thread>
+#include "common/math_util.h"
+#include "core/settings.h"
#include "input_common/analog_from_button.h"
namespace InputCommon {
@@ -11,27 +17,130 @@ public:
using Button = std::unique_ptr<Input::ButtonDevice>;
Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_,
- float modifier_scale_)
+ float modifier_scale_, float modifier_angle_)
: up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
- right(std::move(right_)), modifier(std::move(modifier_)),
- modifier_scale(modifier_scale_) {}
+ right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
+ modifier_angle(modifier_angle_) {
+ update_thread_running.store(true);
+ update_thread = std::thread(&Analog::UpdateStatus, this);
+ }
+
+ ~Analog() override {
+ if (update_thread_running.load()) {
+ update_thread_running.store(false);
+ if (update_thread.joinable()) {
+ update_thread.join();
+ }
+ }
+ }
+
+ void MoveToDirection(bool enable, float to_angle) {
+ if (!enable) {
+ return;
+ }
+ constexpr float TAU = Common::PI * 2.0f;
+ // Use wider angle to ease the transition.
+ constexpr float aperture = TAU * 0.15f;
+ const float top_limit = to_angle + aperture;
+ const float bottom_limit = to_angle - aperture;
+
+ if ((angle > to_angle && angle <= top_limit) ||
+ (angle + TAU > to_angle && angle + TAU <= top_limit)) {
+ angle -= modifier_angle;
+ if (angle < 0) {
+ angle += TAU;
+ }
+ } else if ((angle >= bottom_limit && angle < to_angle) ||
+ (angle - TAU >= bottom_limit && angle - TAU < to_angle)) {
+ angle += modifier_angle;
+ if (angle >= TAU) {
+ angle -= TAU;
+ }
+ } else {
+ angle = to_angle;
+ }
+ }
+
+ void UpdateStatus() {
+ while (update_thread_running.load()) {
+ const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
+
+ bool r = right->GetStatus();
+ bool l = left->GetStatus();
+ bool u = up->GetStatus();
+ bool d = down->GetStatus();
+
+ // Eliminate contradictory movements
+ if (r && l) {
+ r = false;
+ l = false;
+ }
+ if (u && d) {
+ u = false;
+ d = false;
+ }
+
+ // Move to the right
+ MoveToDirection(r && !u && !d, 0.0f);
+
+ // Move to the upper right
+ MoveToDirection(r && u && !d, Common::PI * 0.25f);
+
+ // Move up
+ MoveToDirection(u && !l && !r, Common::PI * 0.5f);
+
+ // Move to the upper left
+ MoveToDirection(l && u && !d, Common::PI * 0.75f);
+
+ // Move to the left
+ MoveToDirection(l && !u && !d, Common::PI);
+
+ // Move to the bottom left
+ MoveToDirection(l && !u && d, Common::PI * 1.25f);
+
+ // Move down
+ MoveToDirection(d && !l && !r, Common::PI * 1.5f);
+
+ // Move to the bottom right
+ MoveToDirection(r && !u && d, Common::PI * 1.75f);
+
+ // Move if a key is pressed
+ if (r || l || u || d) {
+ amplitude = coef;
+ } else {
+ amplitude = 0;
+ }
+
+ // Delay the update rate to 100hz
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+ }
std::tuple<float, float> GetStatus() const override {
+ if (Settings::values.emulate_analog_keyboard) {
+ return std::make_tuple(std::cos(angle) * amplitude, std::sin(angle) * amplitude);
+ }
constexpr float SQRT_HALF = 0.707106781f;
int x = 0, y = 0;
-
- if (right->GetStatus())
+ if (right->GetStatus()) {
++x;
- if (left->GetStatus())
+ }
+ if (left->GetStatus()) {
--x;
- if (up->GetStatus())
+ }
+ if (up->GetStatus()) {
++y;
- if (down->GetStatus())
+ }
+ if (down->GetStatus()) {
--y;
+ }
+ const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
+ return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF),
+ static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF));
+ }
- float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
- return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF),
- y * coef * (x == 0 ? 1.0f : SQRT_HALF));
+ Input::AnalogProperties GetAnalogProperties() const override {
+ return {modifier_scale, 1.0f, 0.5f};
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
@@ -55,6 +164,11 @@ private:
Button right;
Button modifier;
float modifier_scale;
+ float modifier_angle;
+ float angle{};
+ float amplitude{};
+ std::thread update_thread;
+ std::atomic<bool> update_thread_running{};
};
std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
@@ -65,8 +179,10 @@ std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::Para
auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine));
auto modifier_scale = params.Get("modifier_scale", 0.5f);
+ auto modifier_angle = params.Get("modifier_angle", 0.035f);
return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
- std::move(right), std::move(modifier), modifier_scale);
+ std::move(right), std::move(modifier), modifier_scale,
+ modifier_angle);
}
} // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
index 74759ea7d..d80195c82 100644
--- a/src/input_common/gcadapter/gc_adapter.cpp
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -4,20 +4,23 @@
#include <chrono>
#include <thread>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
+#endif
#include <libusb.h>
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
#include "common/logging/log.h"
+#include "common/param_package.h"
#include "input_common/gcadapter/gc_adapter.h"
+#include "input_common/settings.h"
namespace GCAdapter {
-/// Used to loop through and assign button in poller
-constexpr std::array<PadButton, 12> PadButtonArray{
- PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN,
- PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R,
- PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B,
- PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START,
-};
-
Adapter::Adapter() {
if (usb_adapter_handle != nullptr) {
return;
@@ -26,179 +29,261 @@ Adapter::Adapter() {
const int init_res = libusb_init(&libusb_ctx);
if (init_res == LIBUSB_SUCCESS) {
- Setup();
+ adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
} else {
LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
}
}
-GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) {
- GCPadStatus pad = {};
- const std::size_t offset = 1 + (9 * port);
+Adapter::~Adapter() {
+ Reset();
+}
- adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
+void Adapter::AdapterInputThread() {
+ LOG_DEBUG(Input, "GC Adapter input thread started");
+ s32 payload_size{};
+ AdapterPayload adapter_payload{};
- static constexpr std::array<PadButton, 8> b1_buttons{
- PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X,
- PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT,
- PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP,
- };
+ if (adapter_scan_thread.joinable()) {
+ adapter_scan_thread.join();
+ }
- static constexpr std::array<PadButton, 4> b2_buttons{
- PadButton::PAD_BUTTON_START,
- PadButton::PAD_TRIGGER_Z,
- PadButton::PAD_TRIGGER_R,
- PadButton::PAD_TRIGGER_L,
- };
+ while (adapter_input_thread_running) {
+ libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
+ static_cast<s32>(adapter_payload.size()), &payload_size, 16);
+ if (IsPayloadCorrect(adapter_payload, payload_size)) {
+ UpdateControllers(adapter_payload);
+ UpdateVibrations();
+ }
+ std::this_thread::yield();
+ }
- static constexpr std::array<PadAxes, 6> axes{
- PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
- PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
- };
+ if (restart_scan_thread) {
+ adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
+ restart_scan_thread = false;
+ }
+}
- if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) {
- // Controller may have been disconnected, recalibrate if reconnected.
- get_origin[port] = true;
+bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
+ if (payload_size != static_cast<s32>(adapter_payload.size()) ||
+ adapter_payload[0] != LIBUSB_DT_HID) {
+ LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
+ adapter_payload[0]);
+ if (input_error_counter++ > 20) {
+ LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?");
+ adapter_input_thread_running = false;
+ restart_scan_thread = true;
+ }
+ return false;
}
- if (adapter_controllers_status[port] != ControllerTypes::None) {
- const u8 b1 = adapter_payload[offset + 1];
- const u8 b2 = adapter_payload[offset + 2];
+ input_error_counter = 0;
+ return true;
+}
- for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
- if ((b1 & (1U << i)) != 0) {
- pad.button |= static_cast<u16>(b1_buttons[i]);
+void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
+ for (std::size_t port = 0; port < pads.size(); ++port) {
+ const std::size_t offset = 1 + (9 * port);
+ const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
+ UpdatePadType(port, type);
+ if (DeviceConnected(port)) {
+ const u8 b1 = adapter_payload[offset + 1];
+ const u8 b2 = adapter_payload[offset + 2];
+ UpdateStateButtons(port, b1, b2);
+ UpdateStateAxes(port, adapter_payload);
+ if (configuring) {
+ UpdateYuzuSettings(port);
}
}
+ }
+}
- for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
- if ((b2 & (1U << j)) != 0) {
- pad.button |= static_cast<u16>(b2_buttons[j]);
- }
- }
- for (PadAxes axis : axes) {
- const std::size_t index = static_cast<std::size_t>(axis);
- pad.axis_values[index] = adapter_payload[offset + 3 + index];
+void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
+ if (pads[port].type == pad_type) {
+ return;
+ }
+ // Device changed reset device and set new type
+ ResetDevice(port);
+ pads[port].type = pad_type;
+}
+
+void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
+ if (port >= pads.size()) {
+ return;
+ }
+
+ static constexpr std::array<PadButton, 8> b1_buttons{
+ PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY,
+ PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp,
+ };
+
+ static constexpr std::array<PadButton, 4> b2_buttons{
+ PadButton::ButtonStart,
+ PadButton::TriggerZ,
+ PadButton::TriggerR,
+ PadButton::TriggerL,
+ };
+ pads[port].buttons = 0;
+ for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
+ if ((b1 & (1U << i)) != 0) {
+ pads[port].buttons =
+ static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i]));
+ pads[port].last_button = b1_buttons[i];
}
+ }
- if (get_origin[port]) {
- origin_status[port].axis_values = pad.axis_values;
- get_origin[port] = false;
+ for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
+ if ((b2 & (1U << j)) != 0) {
+ pads[port].buttons =
+ static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j]));
+ pads[port].last_button = b2_buttons[j];
}
}
- return pad;
}
-void Adapter::PadToState(const GCPadStatus& pad, GCState& state) {
- for (const auto& button : PadButtonArray) {
- const u16 button_value = static_cast<u16>(button);
- state.buttons.insert_or_assign(button_value, pad.button & button_value);
+void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
+ if (port >= pads.size()) {
+ return;
}
- for (size_t i = 0; i < pad.axis_values.size(); ++i) {
- state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]);
+ const std::size_t offset = 1 + (9 * port);
+ static constexpr std::array<PadAxes, 6> axes{
+ PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
+ PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
+ };
+
+ for (const PadAxes axis : axes) {
+ const auto index = static_cast<std::size_t>(axis);
+ const u8 axis_value = adapter_payload[offset + 3 + index];
+ if (pads[port].axis_origin[index] == 255) {
+ pads[port].axis_origin[index] = axis_value;
+ }
+ pads[port].axis_values[index] =
+ static_cast<s16>(axis_value - pads[port].axis_origin[index]);
}
}
-void Adapter::Read() {
- LOG_DEBUG(Input, "GC Adapter Read() thread started");
+void Adapter::UpdateYuzuSettings(std::size_t port) {
+ if (port >= pads.size()) {
+ return;
+ }
- int payload_size;
- std::array<u8, 37> adapter_payload;
- std::array<GCPadStatus, 4> pads;
+ constexpr u8 axis_threshold = 50;
+ GCPadStatus pad_status = {.port = port};
- while (adapter_thread_running) {
- libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
- sizeof(adapter_payload), &payload_size, 16);
-
- if (payload_size != sizeof(adapter_payload) || adapter_payload[0] != LIBUSB_DT_HID) {
- LOG_ERROR(Input,
- "Error reading payload (size: {}, type: {:02x}) Is the adapter connected?",
- payload_size, adapter_payload[0]);
- adapter_thread_running = false; // error reading from adapter, stop reading.
- break;
- }
- for (std::size_t port = 0; port < pads.size(); ++port) {
- pads[port] = GetPadStatus(port, adapter_payload);
- if (DeviceConnected(port) && configuring) {
- if (pads[port].button != 0) {
- pad_queue[port].Push(pads[port]);
- }
+ if (pads[port].buttons != 0) {
+ pad_status.button = pads[port].last_button;
+ pad_queue.Push(pad_status);
+ }
- // Accounting for a threshold here to ensure an intentional press
- for (size_t i = 0; i < pads[port].axis_values.size(); ++i) {
- const u8 value = pads[port].axis_values[i];
- const u8 origin = origin_status[port].axis_values[i];
-
- if (value > origin + pads[port].THRESHOLD ||
- value < origin - pads[port].THRESHOLD) {
- pads[port].axis = static_cast<PadAxes>(i);
- pads[port].axis_value = pads[port].axis_values[i];
- pad_queue[port].Push(pads[port]);
- }
- }
- }
- PadToState(pads[port], state[port]);
+ // Accounting for a threshold here to ensure an intentional press
+ for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) {
+ const s16 value = pads[port].axis_values[i];
+
+ if (value > axis_threshold || value < -axis_threshold) {
+ pad_status.axis = static_cast<PadAxes>(i);
+ pad_status.axis_value = value;
+ pad_status.axis_threshold = axis_threshold;
+ pad_queue.Push(pad_status);
}
- std::this_thread::yield();
}
}
-void Adapter::Setup() {
- // Initialize all controllers as unplugged
- adapter_controllers_status.fill(ControllerTypes::None);
- // Initialize all ports to store axis origin values
- get_origin.fill(true);
-
- // pointer to list of connected usb devices
- libusb_device** devices{};
-
- // populate the list of devices, get the count
- const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices);
- if (device_count < 0) {
- LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count);
- return;
+void Adapter::UpdateVibrations() {
+ // Use 8 states to keep the switching between on/off fast enough for
+ // a human to not notice the difference between switching from on/off
+ // More states = more rumble strengths = slower update time
+ constexpr u8 vibration_states = 8;
+
+ vibration_counter = (vibration_counter + 1) % vibration_states;
+
+ for (GCController& pad : pads) {
+ const bool vibrate = pad.rumble_amplitude > vibration_counter;
+ vibration_changed |= vibrate != pad.enable_vibration;
+ pad.enable_vibration = vibrate;
}
+ SendVibrations();
+}
- if (devices != nullptr) {
- for (std::size_t index = 0; index < static_cast<std::size_t>(device_count); ++index) {
- if (CheckDeviceAccess(devices[index])) {
- // GC Adapter found and accessible, registering it
- GetGCEndpoint(devices[index]);
- break;
- }
+void Adapter::SendVibrations() {
+ if (!rumble_enabled || !vibration_changed) {
+ return;
+ }
+ s32 size{};
+ constexpr u8 rumble_command = 0x11;
+ const u8 p1 = pads[0].enable_vibration;
+ const u8 p2 = pads[1].enable_vibration;
+ const u8 p3 = pads[2].enable_vibration;
+ const u8 p4 = pads[3].enable_vibration;
+ std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
+ const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(),
+ static_cast<s32>(payload.size()), &size, 16);
+ if (err) {
+ LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
+ if (output_error_counter++ > 5) {
+ LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
+ rumble_enabled = false;
}
- libusb_free_device_list(devices, 1);
+ return;
}
+ output_error_counter = 0;
+ vibration_changed = false;
}
-bool Adapter::CheckDeviceAccess(libusb_device* device) {
- libusb_device_descriptor desc;
- const int get_descriptor_error = libusb_get_device_descriptor(device, &desc);
- if (get_descriptor_error) {
- // could not acquire the descriptor, no point in trying to use it.
- LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}",
- get_descriptor_error);
- return false;
+bool Adapter::RumblePlay(std::size_t port, u8 amplitude) {
+ pads[port].rumble_amplitude = amplitude;
+
+ return rumble_enabled;
+}
+
+void Adapter::AdapterScanThread() {
+ adapter_scan_thread_running = true;
+ adapter_input_thread_running = false;
+ if (adapter_input_thread.joinable()) {
+ adapter_input_thread.join();
+ }
+ ClearLibusbHandle();
+ ResetDevices();
+ while (adapter_scan_thread_running && !adapter_input_thread_running) {
+ Setup();
+ std::this_thread::sleep_for(std::chrono::seconds(1));
}
+}
- if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) {
- // This isn't the device we are looking for.
- return false;
+void Adapter::Setup() {
+ usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337);
+
+ if (usb_adapter_handle == NULL) {
+ return;
+ }
+ if (!CheckDeviceAccess()) {
+ ClearLibusbHandle();
+ return;
}
- const int open_error = libusb_open(device, &usb_adapter_handle);
- if (open_error == LIBUSB_ERROR_ACCESS) {
- LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.",
- desc.idVendor, desc.idProduct);
- return false;
+ libusb_device* device = libusb_get_device(usb_adapter_handle);
+
+ LOG_INFO(Input, "GC adapter is now connected");
+ // GC Adapter found and accessible, registering it
+ if (GetGCEndpoint(device)) {
+ adapter_scan_thread_running = false;
+ adapter_input_thread_running = true;
+ rumble_enabled = true;
+ input_error_counter = 0;
+ output_error_counter = 0;
+ adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this);
}
- if (open_error) {
- LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error);
- return false;
+}
+
+bool Adapter::CheckDeviceAccess() {
+ // This fixes payload problems from offbrand GCAdapters
+ const s32 control_transfer_error =
+ libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
+ if (control_transfer_error < 0) {
+ LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
}
- int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
+ s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
if (kernel_driver_error == 1) {
kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);
if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
@@ -224,13 +309,13 @@ bool Adapter::CheckDeviceAccess(libusb_device* device) {
return true;
}
-void Adapter::GetGCEndpoint(libusb_device* device) {
+bool Adapter::GetGCEndpoint(libusb_device* device) {
libusb_config_descriptor* config = nullptr;
const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
if (config_descriptor_return != LIBUSB_SUCCESS) {
LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
config_descriptor_return);
- return;
+ return false;
}
for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
@@ -239,7 +324,7 @@ void Adapter::GetGCEndpoint(libusb_device* device) {
const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];
for (u8 e = 0; e < interface->bNumEndpoints; e++) {
const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e];
- if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
+ if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) {
input_endpoint = endpoint->bEndpointAddress;
} else {
output_endpoint = endpoint->bEndpointAddress;
@@ -252,78 +337,173 @@ void Adapter::GetGCEndpoint(libusb_device* device) {
unsigned char clear_payload = 0x13;
libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,
sizeof(clear_payload), nullptr, 16);
-
- adapter_thread_running = true;
- adapter_input_thread = std::thread(&Adapter::Read, this);
+ return true;
}
-Adapter::~Adapter() {
- Reset();
-}
+void Adapter::JoinThreads() {
+ restart_scan_thread = false;
+ adapter_input_thread_running = false;
+ adapter_scan_thread_running = false;
-void Adapter::Reset() {
- if (adapter_thread_running) {
- adapter_thread_running = false;
+ if (adapter_scan_thread.joinable()) {
+ adapter_scan_thread.join();
}
+
if (adapter_input_thread.joinable()) {
adapter_input_thread.join();
}
+}
- adapter_controllers_status.fill(ControllerTypes::None);
- get_origin.fill(true);
-
+void Adapter::ClearLibusbHandle() {
if (usb_adapter_handle) {
libusb_release_interface(usb_adapter_handle, 1);
libusb_close(usb_adapter_handle);
usb_adapter_handle = nullptr;
}
+}
+
+void Adapter::ResetDevices() {
+ for (std::size_t i = 0; i < pads.size(); ++i) {
+ ResetDevice(i);
+ }
+}
+
+void Adapter::ResetDevice(std::size_t port) {
+ pads[port].type = ControllerTypes::None;
+ pads[port].enable_vibration = false;
+ pads[port].rumble_amplitude = 0;
+ pads[port].buttons = 0;
+ pads[port].last_button = PadButton::Undefined;
+ pads[port].axis_values.fill(0);
+ pads[port].axis_origin.fill(255);
+}
+
+void Adapter::Reset() {
+ JoinThreads();
+ ClearLibusbHandle();
+ ResetDevices();
if (libusb_ctx) {
libusb_exit(libusb_ctx);
}
}
-bool Adapter::DeviceConnected(std::size_t port) {
- return adapter_controllers_status[port] != ControllerTypes::None;
+std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ for (std::size_t port = 0; port < pads.size(); ++port) {
+ if (!DeviceConnected(port)) {
+ continue;
+ }
+ std::string name = fmt::format("Gamecube Controller {}", port + 1);
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "gcpad"},
+ {"display", std::move(name)},
+ {"port", std::to_string(port)},
+ });
+ }
+ return devices;
}
-void Adapter::ResetDeviceType(std::size_t port) {
- adapter_controllers_status[port] = ControllerTypes::None;
+InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
+ const Common::ParamPackage& params) const {
+ // This list is missing ZL/ZR since those are not considered buttons.
+ // We will add those afterwards
+ // This list also excludes any button that can't be really mapped
+ static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
+ switch_to_gcadapter_button = {
+ std::pair{Settings::NativeButton::A, PadButton::ButtonA},
+ {Settings::NativeButton::B, PadButton::ButtonB},
+ {Settings::NativeButton::X, PadButton::ButtonX},
+ {Settings::NativeButton::Y, PadButton::ButtonY},
+ {Settings::NativeButton::Plus, PadButton::ButtonStart},
+ {Settings::NativeButton::DLeft, PadButton::ButtonLeft},
+ {Settings::NativeButton::DUp, PadButton::ButtonUp},
+ {Settings::NativeButton::DRight, PadButton::ButtonRight},
+ {Settings::NativeButton::DDown, PadButton::ButtonDown},
+ {Settings::NativeButton::SL, PadButton::TriggerL},
+ {Settings::NativeButton::SR, PadButton::TriggerR},
+ {Settings::NativeButton::R, PadButton::TriggerZ},
+ };
+ if (!params.Has("port")) {
+ return {};
+ }
+
+ InputCommon::ButtonMapping mapping{};
+ for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
+ Common::ParamPackage button_params({{"engine", "gcpad"}});
+ button_params.Set("port", params.Get("port", 0));
+ button_params.Set("button", static_cast<int>(gcadapter_button));
+ mapping.insert_or_assign(switch_button, std::move(button_params));
+ }
+
+ // Add the missing bindings for ZL/ZR
+ static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
+ switch_to_gcadapter_axis = {
+ std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
+ {Settings::NativeButton::ZR, PadAxes::TriggerRight},
+ };
+ for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
+ Common::ParamPackage button_params({{"engine", "gcpad"}});
+ button_params.Set("port", params.Get("port", 0));
+ button_params.Set("button", static_cast<s32>(PadButton::Stick));
+ button_params.Set("axis", static_cast<s32>(gcadapter_axis));
+ button_params.Set("threshold", 0.5f);
+ button_params.Set("direction", "+");
+ mapping.insert_or_assign(switch_button, std::move(button_params));
+ }
+ return mapping;
}
-void Adapter::BeginConfiguration() {
- get_origin.fill(true);
- for (auto& pq : pad_queue) {
- pq.Clear();
+InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("port")) {
+ return {};
}
+
+ InputCommon::AnalogMapping mapping = {};
+ Common::ParamPackage left_analog_params;
+ left_analog_params.Set("engine", "gcpad");
+ left_analog_params.Set("port", params.Get("port", 0));
+ left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
+ left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
+ mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
+ Common::ParamPackage right_analog_params;
+ right_analog_params.Set("engine", "gcpad");
+ right_analog_params.Set("port", params.Get("port", 0));
+ right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
+ right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
+ return mapping;
+}
+
+bool Adapter::DeviceConnected(std::size_t port) const {
+ return pads[port].type != ControllerTypes::None;
+}
+
+void Adapter::BeginConfiguration() {
+ pad_queue.Clear();
configuring = true;
}
void Adapter::EndConfiguration() {
- for (auto& pq : pad_queue) {
- pq.Clear();
- }
+ pad_queue.Clear();
configuring = false;
}
-std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() {
+Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() {
return pad_queue;
}
-const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const {
+const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const {
return pad_queue;
}
-std::array<GCState, 4>& Adapter::GetPadState() {
- return state;
-}
-
-const std::array<GCState, 4>& Adapter::GetPadState() const {
- return state;
+GCController& Adapter::GetPadState(std::size_t port) {
+ return pads.at(port);
}
-int Adapter::GetOriginValue(int port, int axis) const {
- return origin_status[port].axis_values[axis];
+const GCController& Adapter::GetPadState(std::size_t port) const {
+ return pads.at(port);
}
} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
index bed81915c..7a6c545bd 100644
--- a/src/input_common/gcadapter/gc_adapter.h
+++ b/src/input_common/gcadapter/gc_adapter.h
@@ -10,6 +10,7 @@
#include <unordered_map>
#include "common/common_types.h"
#include "common/threadsafe_queue.h"
+#include "input_common/main.h"
struct libusb_context;
struct libusb_device;
@@ -18,24 +19,23 @@ struct libusb_device_handle;
namespace GCAdapter {
enum class PadButton {
- PAD_BUTTON_LEFT = 0x0001,
- PAD_BUTTON_RIGHT = 0x0002,
- PAD_BUTTON_DOWN = 0x0004,
- PAD_BUTTON_UP = 0x0008,
- PAD_TRIGGER_Z = 0x0010,
- PAD_TRIGGER_R = 0x0020,
- PAD_TRIGGER_L = 0x0040,
- PAD_BUTTON_A = 0x0100,
- PAD_BUTTON_B = 0x0200,
- PAD_BUTTON_X = 0x0400,
- PAD_BUTTON_Y = 0x0800,
- PAD_BUTTON_START = 0x1000,
+ Undefined = 0x0000,
+ ButtonLeft = 0x0001,
+ ButtonRight = 0x0002,
+ ButtonDown = 0x0004,
+ ButtonUp = 0x0008,
+ TriggerZ = 0x0010,
+ TriggerR = 0x0020,
+ TriggerL = 0x0040,
+ ButtonA = 0x0100,
+ ButtonB = 0x0200,
+ ButtonX = 0x0400,
+ ButtonY = 0x0800,
+ ButtonStart = 0x1000,
// Below is for compatibility with "AxisButton" type
- PAD_STICK = 0x2000,
+ Stick = 0x2000,
};
-extern const std::array<PadButton, 12> PadButtonArray;
-
enum class PadAxes : u8 {
StickX,
StickY,
@@ -46,85 +46,122 @@ enum class PadAxes : u8 {
Undefined,
};
+enum class ControllerTypes {
+ None,
+ Wired,
+ Wireless,
+};
+
struct GCPadStatus {
- u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
+ std::size_t port{};
- std::array<u8, 6> axis_values{}; // Triggers and sticks, following indices defined in PadAxes
- static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling
+ PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
- u8 port{};
PadAxes axis{PadAxes::Undefined};
- u8 axis_value{255};
+ s16 axis_value{};
+ u8 axis_threshold{50};
};
-struct GCState {
- std::unordered_map<int, bool> buttons;
- std::unordered_map<int, u16> axes;
+struct GCController {
+ ControllerTypes type{};
+ bool enable_vibration{};
+ u8 rumble_amplitude{};
+ u16 buttons{};
+ PadButton last_button{};
+ std::array<s16, 6> axis_values{};
+ std::array<u8, 6> axis_origin{};
};
-enum class ControllerTypes { None, Wired, Wireless };
-
class Adapter {
public:
- /// Initialize the GC Adapter capture and read sequence
Adapter();
-
- /// Close the adapter read thread and release the adapter
~Adapter();
+
+ /// Request a vibration for a controller
+ bool RumblePlay(std::size_t port, u8 amplitude);
+
/// Used for polling
void BeginConfiguration();
void EndConfiguration();
- /// Returns true if there is a device connected to port
- bool DeviceConnected(std::size_t port);
+ Common::SPSCQueue<GCPadStatus>& GetPadQueue();
+ const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const;
- std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();
- const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const;
+ GCController& GetPadState(std::size_t port);
+ const GCController& GetPadState(std::size_t port) const;
- std::array<GCState, 4>& GetPadState();
- const std::array<GCState, 4>& GetPadState() const;
+ /// Returns true if there is a device connected to port
+ bool DeviceConnected(std::size_t port) const;
- int GetOriginValue(int port, int axis) const;
+ /// Used for automapping features
+ std::vector<Common::ParamPackage> GetInputDevices() const;
+ InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
+ InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
private:
- GCPadStatus GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload);
+ using AdapterPayload = std::array<u8, 37>;
+
+ void UpdatePadType(std::size_t port, ControllerTypes pad_type);
+ void UpdateControllers(const AdapterPayload& adapter_payload);
+ void UpdateYuzuSettings(std::size_t port);
+ void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
+ void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
+ void UpdateVibrations();
+
+ void AdapterInputThread();
- void PadToState(const GCPadStatus& pad, GCState& state);
+ void AdapterScanThread();
- void Read();
+ bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
- /// Resets status of device connected to port
- void ResetDeviceType(std::size_t port);
+ // Updates vibration state of all controllers
+ void SendVibrations();
+
+ /// For use in initialization, querying devices to find the adapter
+ void Setup();
+
+ /// Resets status of all GC controller devices to a disconnected state
+ void ResetDevices();
+
+ /// Resets status of device connected to a disconnected state
+ void ResetDevice(std::size_t port);
/// Returns true if we successfully gain access to GC Adapter
- bool CheckDeviceAccess(libusb_device* device);
+ bool CheckDeviceAccess();
- /// Captures GC Adapter endpoint address,
- void GetGCEndpoint(libusb_device* device);
+ /// Captures GC Adapter endpoint address
+ /// Returns true if the endpoint was set correctly
+ bool GetGCEndpoint(libusb_device* device);
/// For shutting down, clear all data, join all threads, release usb
void Reset();
- /// For use in initialization, querying devices to find the adapter
- void Setup();
+ // Join all threads
+ void JoinThreads();
+
+ // Release usb handles
+ void ClearLibusbHandle();
libusb_device_handle* usb_adapter_handle = nullptr;
+ std::array<GCController, 4> pads;
+ Common::SPSCQueue<GCPadStatus> pad_queue;
std::thread adapter_input_thread;
- bool adapter_thread_running;
+ std::thread adapter_scan_thread;
+ bool adapter_input_thread_running;
+ bool adapter_scan_thread_running;
+ bool restart_scan_thread;
libusb_context* libusb_ctx;
- u8 input_endpoint = 0;
- u8 output_endpoint = 0;
+ u8 input_endpoint{0};
+ u8 output_endpoint{0};
+ u8 input_error_counter{0};
+ u8 output_error_counter{0};
+ int vibration_counter{0};
- bool configuring = false;
-
- std::array<GCState, 4> state;
- std::array<bool, 4> get_origin;
- std::array<GCPadStatus, 4> origin_status;
- std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue;
- std::array<ControllerTypes, 4> adapter_controllers_status{};
+ bool configuring{false};
+ bool rumble_enabled{true};
+ bool vibration_changed{true};
};
-
} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
index 71cd85eeb..1b6ded8d6 100644
--- a/src/input_common/gcadapter/gc_poller.cpp
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -15,35 +15,35 @@ namespace InputCommon {
class GCButton final : public Input::ButtonDevice {
public:
- explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
+ explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
: port(port_), button(button_), gcadapter(adapter) {}
~GCButton() override;
bool GetStatus() const override {
if (gcadapter->DeviceConnected(port)) {
- return gcadapter->GetPadState()[port].buttons.at(button);
+ return (gcadapter->GetPadState(port).buttons & button) != 0;
}
return false;
}
private:
- const int port;
- const int button;
- GCAdapter::Adapter* gcadapter;
+ const u32 port;
+ const s32 button;
+ const GCAdapter::Adapter* gcadapter;
};
class GCAxisButton final : public Input::ButtonDevice {
public:
- explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
- GCAdapter::Adapter* adapter)
+ explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_,
+ const GCAdapter::Adapter* adapter)
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
- gcadapter(adapter), origin_value(adapter->GetOriginValue(port_, axis_)) {}
+ gcadapter(adapter) {}
bool GetStatus() const override {
if (gcadapter->DeviceConnected(port)) {
- const float current_axis_value = gcadapter->GetPadState()[port].axes.at(axis);
- const float axis_value = (current_axis_value - origin_value) / 128.0f;
+ const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis);
+ const float axis_value = current_axis_value / 128.0f;
if (trigger_if_greater) {
// TODO: Might be worthwile to set a slider for the trigger threshold. It is
// currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
@@ -55,12 +55,11 @@ public:
}
private:
- const int port;
- const int axis;
+ const u32 port;
+ const u32 axis;
float threshold;
bool trigger_if_greater;
- GCAdapter::Adapter* gcadapter;
- const float origin_value;
+ const GCAdapter::Adapter* gcadapter;
};
GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
@@ -69,10 +68,10 @@ GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
GCButton::~GCButton() = default;
std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
- const int button_id = params.Get("button", 0);
- const int port = params.Get("port", 0);
+ const auto button_id = params.Get("button", 0);
+ const auto port = static_cast<u32>(params.Get("port", 0));
- constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK);
+ constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);
// button is not an axis/stick button
if (button_id != PAD_STICK_ID) {
@@ -97,7 +96,6 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param
adapter.get());
}
- UNREACHABLE();
return nullptr;
}
@@ -105,32 +103,25 @@ Common::ParamPackage GCButtonFactory::GetNextInput() const {
Common::ParamPackage params;
GCAdapter::GCPadStatus pad;
auto& queue = adapter->GetPadQueue();
- for (std::size_t port = 0; port < queue.size(); ++port) {
- while (queue[port].Pop(pad)) {
- // This while loop will break on the earliest detected button
- params.Set("engine", "gcpad");
- params.Set("port", static_cast<int>(port));
- for (const auto& button : GCAdapter::PadButtonArray) {
- const u16 button_value = static_cast<u16>(button);
- if (pad.button & button_value) {
- params.Set("button", button_value);
- break;
- }
- }
+ while (queue.Pop(pad)) {
+ // This while loop will break on the earliest detected button
+ params.Set("engine", "gcpad");
+ params.Set("port", static_cast<s32>(pad.port));
+ if (pad.button != GCAdapter::PadButton::Undefined) {
+ params.Set("button", static_cast<u16>(pad.button));
+ }
- // For Axis button implementation
- if (pad.axis != GCAdapter::PadAxes::Undefined) {
- params.Set("axis", static_cast<u8>(pad.axis));
- params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK));
- if (pad.axis_value > 128) {
- params.Set("direction", "+");
- params.Set("threshold", "0.25");
- } else {
- params.Set("direction", "-");
- params.Set("threshold", "-0.25");
- }
- break;
+ // For Axis button implementation
+ if (pad.axis != GCAdapter::PadAxes::Undefined) {
+ params.Set("axis", static_cast<u8>(pad.axis));
+ params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick));
+ params.Set("threshold", "0.25");
+ if (pad.axis_value > 0) {
+ params.Set("direction", "+");
+ } else {
+ params.Set("direction", "-");
}
+ break;
}
}
return params;
@@ -148,25 +139,30 @@ void GCButtonFactory::EndConfiguration() {
class GCAnalog final : public Input::AnalogDevice {
public:
- GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter,
- float range_)
- : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
- origin_value_x(adapter->GetOriginValue(port_, axis_x_)),
- origin_value_y(adapter->GetOriginValue(port_, axis_y_)), range(range_) {}
+ explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
+ float deadzone_, float range_, const GCAdapter::Adapter* adapter)
+ : port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
+ deadzone(deadzone_), range(range_), gcadapter(adapter) {}
- float GetAxis(int axis) const {
+ float GetAxis(u32 axis) const {
if (gcadapter->DeviceConnected(port)) {
std::lock_guard lock{mutex};
- const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y;
- return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range);
+ const auto axis_value =
+ static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis));
+ return (axis_value) / (100.0f * range);
}
return 0.0f;
}
- std::pair<float, float> GetAnalog(int axis_x, int axis_y) const {
- float x = GetAxis(axis_x);
- float y = GetAxis(axis_y);
-
+ std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
+ float x = GetAxis(analog_axis_x);
+ float y = GetAxis(analog_axis_y);
+ if (invert_x) {
+ x = -x;
+ }
+ if (invert_y) {
+ y = -y;
+ }
// Make sure the coordinates are in the unit circle,
// otherwise normalize it.
float r = x * x + y * y;
@@ -189,6 +185,16 @@ public:
return {0.0f, 0.0f};
}
+ std::tuple<float, float> GetRawStatus() const override {
+ const float x = GetAxis(axis_x);
+ const float y = GetAxis(axis_y);
+ return {x, y};
+ }
+
+ Input::AnalogProperties GetAnalogProperties() const override {
+ return {deadzone, range, 0.5f};
+ }
+
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
const auto [x, y] = GetStatus();
const float directional_deadzone = 0.5f;
@@ -206,14 +212,14 @@ public:
}
private:
- const int port;
- const int axis_x;
- const int axis_y;
+ const u32 port;
+ const u32 axis_x;
+ const u32 axis_y;
+ const bool invert_x;
+ const bool invert_y;
const float deadzone;
- GCAdapter::Adapter* gcadapter;
- const float origin_value_x;
- const float origin_value_y;
const float range;
+ const GCAdapter::Adapter* gcadapter;
mutable std::mutex mutex;
};
@@ -229,13 +235,18 @@ GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
* - "axis_y": the index of the axis to be bind as y-axis
*/
std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
- const int port = params.Get("port", 0);
- const int axis_x = params.Get("axis_x", 0);
- const int axis_y = params.Get("axis_y", 1);
- const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
- const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
-
- return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range);
+ const auto port = static_cast<u32>(params.Get("port", 0));
+ const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
+ const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
+ const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
+ const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
+ const std::string invert_x_value = params.Get("invert_x", "+");
+ const std::string invert_y_value = params.Get("invert_y", "+");
+ const bool invert_x = invert_x_value == "-";
+ const bool invert_y = invert_y_value == "-";
+
+ return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
+ adapter.get());
}
void GCAnalogFactory::BeginConfiguration() {
@@ -250,31 +261,51 @@ void GCAnalogFactory::EndConfiguration() {
Common::ParamPackage GCAnalogFactory::GetNextInput() {
GCAdapter::GCPadStatus pad;
+ Common::ParamPackage params;
auto& queue = adapter->GetPadQueue();
- for (std::size_t port = 0; port < queue.size(); ++port) {
- while (queue[port].Pop(pad)) {
- if (pad.axis == GCAdapter::PadAxes::Undefined ||
- std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) {
- continue;
- }
- // An analog device needs two axes, so we need to store the axis for later and wait for
- // a second input event. The axes also must be from the same joystick.
- const u8 axis = static_cast<u8>(pad.axis);
- if (analog_x_axis == -1) {
- analog_x_axis = axis;
- controller_number = static_cast<int>(port);
- } else if (analog_y_axis == -1 && analog_x_axis != axis &&
- controller_number == static_cast<int>(port)) {
- analog_y_axis = axis;
- }
+ while (queue.Pop(pad)) {
+ if (pad.button != GCAdapter::PadButton::Undefined) {
+ params.Set("engine", "gcpad");
+ params.Set("port", static_cast<s32>(pad.port));
+ params.Set("button", static_cast<u16>(pad.button));
+ return params;
+ }
+ if (pad.axis == GCAdapter::PadAxes::Undefined ||
+ std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) {
+ continue;
+ }
+ // An analog device needs two axes, so we need to store the axis for later and wait for
+ // a second input event. The axes also must be from the same joystick.
+ const u8 axis = static_cast<u8>(pad.axis);
+ if (axis == 0 || axis == 1) {
+ analog_x_axis = 0;
+ analog_y_axis = 1;
+ controller_number = static_cast<s32>(pad.port);
+ break;
+ }
+ if (axis == 2 || axis == 3) {
+ analog_x_axis = 2;
+ analog_y_axis = 3;
+ controller_number = static_cast<s32>(pad.port);
+ break;
+ }
+
+ if (analog_x_axis == -1) {
+ analog_x_axis = axis;
+ controller_number = static_cast<s32>(pad.port);
+ } else if (analog_y_axis == -1 && analog_x_axis != axis &&
+ controller_number == static_cast<s32>(pad.port)) {
+ analog_y_axis = axis;
+ break;
}
}
- Common::ParamPackage params;
if (analog_x_axis != -1 && analog_y_axis != -1) {
params.Set("engine", "gcpad");
params.Set("port", controller_number);
params.Set("axis_x", analog_x_axis);
params.Set("axis_y", analog_y_axis);
+ params.Set("invert_x", "+");
+ params.Set("invert_y", "+");
analog_x_axis = -1;
analog_y_axis = -1;
controller_number = -1;
@@ -283,4 +314,43 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {
return params;
}
+class GCVibration final : public Input::VibrationDevice {
+public:
+ explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
+ : port(port_), gcadapter(adapter) {}
+
+ u8 GetStatus() const override {
+ return gcadapter->RumblePlay(port, 0);
+ }
+
+ bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
+ [[maybe_unused]] f32 freq_high) const override {
+ const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
+ const auto processed_amplitude =
+ static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
+
+ return gcadapter->RumblePlay(port, processed_amplitude);
+ }
+
+private:
+ const u32 port;
+ GCAdapter::Adapter* gcadapter;
+};
+
+/// An vibration device factory that creates vibration devices from GC Adapter
+GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
+ : adapter(std::move(adapter_)) {}
+
+/**
+ * Creates a vibration device from a joystick
+ * @param params contains parameters for creating the device:
+ * - "port": the nth gcpad on the adapter
+ */
+std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
+ const Common::ParamPackage& params) {
+ const auto port = static_cast<u32>(params.Get("port", 0));
+
+ return std::make_unique<GCVibration>(port, adapter.get());
+}
+
} // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h
index 0527f328f..d1271e3ea 100644
--- a/src/input_common/gcadapter/gc_poller.h
+++ b/src/input_common/gcadapter/gc_poller.h
@@ -64,4 +64,15 @@ private:
bool polling = false;
};
+/// A vibration device factory creates vibration devices from GC Adapter
+class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
+public:
+ explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
+
+ std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
+
+private:
+ std::shared_ptr<GCAdapter::Adapter> adapter;
+};
+
} // namespace InputCommon
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp
index afb8e6612..24a6f7a33 100644
--- a/src/input_common/keyboard.cpp
+++ b/src/input_common/keyboard.cpp
@@ -49,8 +49,9 @@ public:
void ChangeKeyStatus(int key_code, bool pressed) {
std::lock_guard guard{mutex};
for (const KeyButtonPair& pair : list) {
- if (pair.key_code == key_code)
+ if (pair.key_code == key_code) {
pair.key_button->status.store(pressed);
+ }
}
}
@@ -73,7 +74,7 @@ KeyButton::~KeyButton() {
}
std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
- int key_code = params.Get("code", 0);
+ const int key_code = params.Get("code", 0);
std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list);
key_button_list->AddKeyButton(key_code, button.get());
return button;
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 57e7a25fe..7c4e7dd3b 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -10,7 +10,11 @@
#include "input_common/gcadapter/gc_poller.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
-#include "input_common/motion_emu.h"
+#include "input_common/motion_from_button.h"
+#include "input_common/mouse/mouse_input.h"
+#include "input_common/mouse/mouse_poller.h"
+#include "input_common/touch_from_button.h"
+#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
#ifdef HAVE_SDL2
#include "input_common/sdl/sdl.h"
@@ -20,47 +24,82 @@ namespace InputCommon {
struct InputSubsystem::Impl {
void Initialize() {
- auto gcadapter = std::make_shared<GCAdapter::Adapter>();
+ gcadapter = std::make_shared<GCAdapter::Adapter>();
gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
+ gcvibration = std::make_shared<GCVibrationFactory>(gcadapter);
+ Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration);
keyboard = std::make_shared<Keyboard>();
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
std::make_shared<AnalogFromButton>());
- motion_emu = std::make_shared<MotionEmu>();
- Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
+ Input::RegisterFactory<Input::MotionDevice>("keyboard",
+ std::make_shared<MotionFromButton>());
+ Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
+ std::make_shared<TouchFromButtonFactory>());
#ifdef HAVE_SDL2
sdl = SDL::Init();
#endif
- udp = CemuhookUDP::Init();
+ udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
+ udpmotion = std::make_shared<UDPMotionFactory>(udp);
+ Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
+ udptouch = std::make_shared<UDPTouchFactory>(udp);
+ Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
+
+ mouse = std::make_shared<MouseInput::Mouse>();
+ mousebuttons = std::make_shared<MouseButtonFactory>(mouse);
+ Input::RegisterFactory<Input::ButtonDevice>("mouse", mousebuttons);
+ mouseanalog = std::make_shared<MouseAnalogFactory>(mouse);
+ Input::RegisterFactory<Input::AnalogDevice>("mouse", mouseanalog);
+ mousemotion = std::make_shared<MouseMotionFactory>(mouse);
+ Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
+ mousetouch = std::make_shared<MouseTouchFactory>(mouse);
+ Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
}
void Shutdown() {
Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
+ Input::UnregisterFactory<Input::MotionDevice>("keyboard");
keyboard.reset();
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
- Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
- motion_emu.reset();
+ Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
#ifdef HAVE_SDL2
sdl.reset();
#endif
- udp.reset();
Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
+ Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
gcbuttons.reset();
gcanalog.reset();
+ gcvibration.reset();
+
+ Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
+ Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
+
+ udpmotion.reset();
+ udptouch.reset();
+
+ Input::UnregisterFactory<Input::ButtonDevice>("mouse");
+ Input::UnregisterFactory<Input::AnalogDevice>("mouse");
+ Input::UnregisterFactory<Input::MotionDevice>("mouse");
+ Input::UnregisterFactory<Input::TouchDevice>("mouse");
+
+ mousebuttons.reset();
+ mouseanalog.reset();
+ mousemotion.reset();
+ mousetouch.reset();
}
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
std::vector<Common::ParamPackage> devices = {
Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
- Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}},
+ Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
};
#ifdef HAVE_SDL2
auto sdl_devices = sdl->GetInputDevices();
@@ -68,6 +107,8 @@ struct InputSubsystem::Impl {
#endif
auto udp_devices = udp->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
+ auto gcpad_devices = gcadapter->GetInputDevices();
+ devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
return devices;
}
@@ -76,9 +117,8 @@ struct InputSubsystem::Impl {
if (!params.Has("class") || params.Get("class", "") == "any") {
return {};
}
- if (params.Get("class", "") == "key") {
- // TODO consider returning the SDL key codes for the default keybindings
- return {};
+ if (params.Get("class", "") == "gcpad") {
+ return gcadapter->GetAnalogMappingForDevice(params);
}
#ifdef HAVE_SDL2
if (params.Get("class", "") == "sdl") {
@@ -93,9 +133,8 @@ struct InputSubsystem::Impl {
if (!params.Has("class") || params.Get("class", "") == "any") {
return {};
}
- if (params.Get("class", "") == "key") {
- // TODO consider returning the SDL key codes for the default keybindings
- return {};
+ if (params.Get("class", "") == "gcpad") {
+ return gcadapter->GetButtonMappingForDevice(params);
}
#ifdef HAVE_SDL2
if (params.Get("class", "") == "sdl") {
@@ -105,14 +144,34 @@ struct InputSubsystem::Impl {
return {};
}
+ [[nodiscard]] MotionMapping GetMotionMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "cemuhookudp") {
+ // TODO return the correct motion device
+ return {};
+ }
+ return {};
+ }
+
std::shared_ptr<Keyboard> keyboard;
- std::shared_ptr<MotionEmu> motion_emu;
#ifdef HAVE_SDL2
std::unique_ptr<SDL::State> sdl;
#endif
- std::unique_ptr<CemuhookUDP::State> udp;
std::shared_ptr<GCButtonFactory> gcbuttons;
std::shared_ptr<GCAnalogFactory> gcanalog;
+ std::shared_ptr<GCVibrationFactory> gcvibration;
+ std::shared_ptr<UDPMotionFactory> udpmotion;
+ std::shared_ptr<UDPTouchFactory> udptouch;
+ std::shared_ptr<MouseButtonFactory> mousebuttons;
+ std::shared_ptr<MouseAnalogFactory> mouseanalog;
+ std::shared_ptr<MouseMotionFactory> mousemotion;
+ std::shared_ptr<MouseTouchFactory> mousetouch;
+ std::shared_ptr<CemuhookUDP::Client> udp;
+ std::shared_ptr<GCAdapter::Adapter> gcadapter;
+ std::shared_ptr<MouseInput::Mouse> mouse;
};
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -135,12 +194,12 @@ const Keyboard* InputSubsystem::GetKeyboard() const {
return impl->keyboard.get();
}
-MotionEmu* InputSubsystem::GetMotionEmu() {
- return impl->motion_emu.get();
+MouseInput::Mouse* InputSubsystem::GetMouse() {
+ return impl->mouse.get();
}
-const MotionEmu* InputSubsystem::GetMotionEmu() const {
- return impl->motion_emu.get();
+const MouseInput::Mouse* InputSubsystem::GetMouse() const {
+ return impl->mouse.get();
}
std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
@@ -155,6 +214,10 @@ ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPacka
return impl->GetButtonMappingForDevice(device);
}
+MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPackage& device) const {
+ return impl->GetMotionMappingForDevice(device);
+}
+
GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
return impl->gcanalog.get();
}
@@ -171,6 +234,61 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
return impl->gcbuttons.get();
}
+UDPMotionFactory* InputSubsystem::GetUDPMotions() {
+ return impl->udpmotion.get();
+}
+
+const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
+ return impl->udpmotion.get();
+}
+
+UDPTouchFactory* InputSubsystem::GetUDPTouch() {
+ return impl->udptouch.get();
+}
+
+const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
+ return impl->udptouch.get();
+}
+
+MouseButtonFactory* InputSubsystem::GetMouseButtons() {
+ return impl->mousebuttons.get();
+}
+
+const MouseButtonFactory* InputSubsystem::GetMouseButtons() const {
+ return impl->mousebuttons.get();
+}
+
+MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() {
+ return impl->mouseanalog.get();
+}
+
+const MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() const {
+ return impl->mouseanalog.get();
+}
+
+MouseMotionFactory* InputSubsystem::GetMouseMotions() {
+ return impl->mousemotion.get();
+}
+
+const MouseMotionFactory* InputSubsystem::GetMouseMotions() const {
+ return impl->mousemotion.get();
+}
+
+MouseTouchFactory* InputSubsystem::GetMouseTouch() {
+ return impl->mousetouch.get();
+}
+
+const MouseTouchFactory* InputSubsystem::GetMouseTouch() const {
+ return impl->mousetouch.get();
+}
+
+void InputSubsystem::ReloadInputDevices() {
+ if (!impl->udp) {
+ return;
+ }
+ impl->udp->ReloadSockets();
+}
+
std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
Polling::DeviceType type) const {
#ifdef HAVE_SDL2
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 58e5dc250..5d6f26385 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -21,10 +21,18 @@ namespace Settings::NativeButton {
enum Values : int;
}
+namespace Settings::NativeMotion {
+enum Values : int;
+}
+
+namespace MouseInput {
+class Mouse;
+}
+
namespace InputCommon {
namespace Polling {
-enum class DeviceType { Button, AnalogPreferred };
+enum class DeviceType { Button, AnalogPreferred, Motion };
/**
* A class that can be used to get inputs from an input device like controllers without having to
@@ -50,8 +58,13 @@ public:
class GCAnalogFactory;
class GCButtonFactory;
+class UDPMotionFactory;
+class UDPTouchFactory;
+class MouseButtonFactory;
+class MouseAnalogFactory;
+class MouseMotionFactory;
+class MouseTouchFactory;
class Keyboard;
-class MotionEmu;
/**
* Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
@@ -59,6 +72,7 @@ class MotionEmu;
*/
using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
+using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>;
class InputSubsystem {
public:
@@ -83,11 +97,11 @@ public:
/// Retrieves the underlying keyboard device.
[[nodiscard]] const Keyboard* GetKeyboard() const;
- /// Retrieves the underlying motion emulation factory.
- [[nodiscard]] MotionEmu* GetMotionEmu();
+ /// Retrieves the underlying mouse device.
+ [[nodiscard]] MouseInput::Mouse* GetMouse();
- /// Retrieves the underlying motion emulation factory.
- [[nodiscard]] const MotionEmu* GetMotionEmu() const;
+ /// Retrieves the underlying mouse device.
+ [[nodiscard]] const MouseInput::Mouse* GetMouse() const;
/**
* Returns all available input devices that this Factory can create a new device with.
@@ -103,6 +117,9 @@ public:
/// Retrieves the button mappings for the given device.
[[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
+ /// Retrieves the motion mappings for the given device.
+ [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
+
/// Retrieves the underlying GameCube analog handler.
[[nodiscard]] GCAnalogFactory* GetGCAnalogs();
@@ -115,6 +132,45 @@ public:
/// Retrieves the underlying GameCube button handler.
[[nodiscard]] const GCButtonFactory* GetGCButtons() const;
+ /// Retrieves the underlying udp motion handler.
+ [[nodiscard]] UDPMotionFactory* GetUDPMotions();
+
+ /// Retrieves the underlying udp motion handler.
+ [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] UDPTouchFactory* GetUDPTouch();
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
+
+ /// Retrieves the underlying GameCube button handler.
+ [[nodiscard]] MouseButtonFactory* GetMouseButtons();
+
+ /// Retrieves the underlying GameCube button handler.
+ [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const;
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs();
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const;
+
+ /// Retrieves the underlying udp motion handler.
+ [[nodiscard]] MouseMotionFactory* GetMouseMotions();
+
+ /// Retrieves the underlying udp motion handler.
+ [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const;
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] MouseTouchFactory* GetMouseTouch();
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
+
+ /// Reloads the input devices
+ void ReloadInputDevices();
+
/// Get all DevicePoller from all backends for a specific device type
[[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
Polling::DeviceType type) const;
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
deleted file mode 100644
index d4cdf76a3..000000000
--- a/src/input_common/motion_emu.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <chrono>
-#include <mutex>
-#include <thread>
-#include <tuple>
-#include "common/math_util.h"
-#include "common/quaternion.h"
-#include "common/thread.h"
-#include "common/vector_math.h"
-#include "input_common/motion_emu.h"
-
-namespace InputCommon {
-
-// Implementation class of the motion emulation device
-class MotionEmuDevice {
-public:
- MotionEmuDevice(int update_millisecond, float sensitivity)
- : update_millisecond(update_millisecond),
- update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
- std::chrono::milliseconds(update_millisecond))),
- sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
-
- ~MotionEmuDevice() {
- if (motion_emu_thread.joinable()) {
- shutdown_event.Set();
- motion_emu_thread.join();
- }
- }
-
- void BeginTilt(int x, int y) {
- mouse_origin = Common::MakeVec(x, y);
- is_tilting = true;
- }
-
- void Tilt(int x, int y) {
- auto mouse_move = Common::MakeVec(x, y) - mouse_origin;
- if (is_tilting) {
- std::lock_guard guard{tilt_mutex};
- if (mouse_move.x == 0 && mouse_move.y == 0) {
- tilt_angle = 0;
- } else {
- tilt_direction = mouse_move.Cast<float>();
- tilt_angle =
- std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f);
- }
- }
- }
-
- void EndTilt() {
- std::lock_guard guard{tilt_mutex};
- tilt_angle = 0;
- is_tilting = false;
- }
-
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() {
- std::lock_guard guard{status_mutex};
- return status;
- }
-
-private:
- const int update_millisecond;
- const std::chrono::steady_clock::duration update_duration;
- const float sensitivity;
-
- Common::Vec2<int> mouse_origin;
-
- std::mutex tilt_mutex;
- Common::Vec2<float> tilt_direction;
- float tilt_angle = 0;
-
- bool is_tilting = false;
-
- Common::Event shutdown_event;
-
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> status;
- std::mutex status_mutex;
-
- // Note: always keep the thread declaration at the end so that other objects are initialized
- // before this!
- std::thread motion_emu_thread;
-
- void MotionEmuThread() {
- auto update_time = std::chrono::steady_clock::now();
- Common::Quaternion<float> q = Common::MakeQuaternion(Common::Vec3<float>(), 0);
- Common::Quaternion<float> old_q;
-
- while (!shutdown_event.WaitUntil(update_time)) {
- update_time += update_duration;
- old_q = q;
-
- {
- std::lock_guard guard{tilt_mutex};
-
- // Find the quaternion describing current 3DS tilting
- q = Common::MakeQuaternion(
- Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle);
- }
-
- auto inv_q = q.Inverse();
-
- // Set the gravity vector in world space
- auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f);
-
- // Find the angular rate vector in world space
- auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
- angular_rate *= 1000 / update_millisecond / Common::PI * 180;
-
- // Transform the two vectors from world space to 3DS space
- gravity = QuaternionRotate(inv_q, gravity);
- angular_rate = QuaternionRotate(inv_q, angular_rate);
-
- // Update the sensor state
- {
- std::lock_guard guard{status_mutex};
- status = std::make_tuple(gravity, angular_rate);
- }
- }
- }
-};
-
-// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as
-// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory
-// can forward all the inputs to the implementation only when it is valid.
-class MotionEmuDeviceWrapper : public Input::MotionDevice {
-public:
- MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
- device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
- }
-
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
- return device->GetStatus();
- }
-
- std::shared_ptr<MotionEmuDevice> device;
-};
-
-std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
- int update_period = params.Get("update_period", 100);
- float sensitivity = params.Get("sensitivity", 0.01f);
- auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
- // Previously created device is disconnected here. Having two motion devices for 3DS is not
- // expected.
- current_device = device_wrapper->device;
- return device_wrapper;
-}
-
-void MotionEmu::BeginTilt(int x, int y) {
- if (auto ptr = current_device.lock()) {
- ptr->BeginTilt(x, y);
- }
-}
-
-void MotionEmu::Tilt(int x, int y) {
- if (auto ptr = current_device.lock()) {
- ptr->Tilt(x, y);
- }
-}
-
-void MotionEmu::EndTilt() {
- if (auto ptr = current_device.lock()) {
- ptr->EndTilt();
- }
-}
-
-} // namespace InputCommon
diff --git a/src/input_common/motion_emu.h b/src/input_common/motion_emu.h
deleted file mode 100644
index 7a7e22467..000000000
--- a/src/input_common/motion_emu.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "core/frontend/input.h"
-
-namespace InputCommon {
-
-class MotionEmuDevice;
-
-class MotionEmu : public Input::Factory<Input::MotionDevice> {
-public:
- /**
- * Creates a motion device emulated from mouse input
- * @param params contains parameters for creating the device:
- * - "update_period": update period in milliseconds
- * - "sensitivity": the coefficient converting mouse movement to tilting angle
- */
- std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
-
- /**
- * Signals that a motion sensor tilt has begun.
- * @param x the x-coordinate of the cursor
- * @param y the y-coordinate of the cursor
- */
- void BeginTilt(int x, int y);
-
- /**
- * Signals that a motion sensor tilt is occurring.
- * @param x the x-coordinate of the cursor
- * @param y the y-coordinate of the cursor
- */
- void Tilt(int x, int y);
-
- /**
- * Signals that a motion sensor tilt has ended.
- */
- void EndTilt();
-
-private:
- std::weak_ptr<MotionEmuDevice> current_device;
-};
-
-} // namespace InputCommon
diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp
new file mode 100644
index 000000000..29045a673
--- /dev/null
+++ b/src/input_common/motion_from_button.cpp
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "input_common/motion_from_button.h"
+#include "input_common/motion_input.h"
+
+namespace InputCommon {
+
+class MotionKey final : public Input::MotionDevice {
+public:
+ using Button = std::unique_ptr<Input::ButtonDevice>;
+
+ explicit MotionKey(Button key_) : key(std::move(key_)) {}
+
+ Input::MotionStatus GetStatus() const override {
+
+ if (key->GetStatus()) {
+ return motion.GetRandomMotion(2, 6);
+ }
+ return motion.GetRandomMotion(0, 0);
+ }
+
+private:
+ Button key;
+ InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
+};
+
+std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) {
+ auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize());
+ return std::make_unique<MotionKey>(std::move(key));
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/motion_from_button.h b/src/input_common/motion_from_button.h
new file mode 100644
index 000000000..a959046fb
--- /dev/null
+++ b/src/input_common/motion_from_button.h
@@ -0,0 +1,25 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+/**
+ * An motion device factory that takes a keyboard button and uses it as a random
+ * motion device.
+ */
+class MotionFromButton final : public Input::Factory<Input::MotionDevice> {
+public:
+ /**
+ * Creates an motion device from button devices
+ * @param params contains parameters for creating the device:
+ * - "key": a serialized ParamPackage for creating a button device
+ */
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp
new file mode 100644
index 000000000..6a65f175e
--- /dev/null
+++ b/src/input_common/motion_input.cpp
@@ -0,0 +1,301 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <random>
+#include "common/math_util.h"
+#include "input_common/motion_input.h"
+
+namespace InputCommon {
+
+MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {}
+
+void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
+ accel = acceleration;
+}
+
+void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
+ gyro = gyroscope - gyro_drift;
+
+ // Auto adjust drift to minimize drift
+ if (!IsMoving(0.1f)) {
+ gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
+ }
+
+ if (gyro.Length2() < gyro_threshold) {
+ gyro = {};
+ } else {
+ only_accelerometer = false;
+ }
+}
+
+void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
+ quat = quaternion;
+}
+
+void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
+ gyro_drift = drift;
+}
+
+void MotionInput::SetGyroThreshold(f32 threshold) {
+ gyro_threshold = threshold;
+}
+
+void MotionInput::EnableReset(bool reset) {
+ reset_enabled = reset;
+}
+
+void MotionInput::ResetRotations() {
+ rotations = {};
+}
+
+bool MotionInput::IsMoving(f32 sensitivity) const {
+ return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
+}
+
+bool MotionInput::IsCalibrated(f32 sensitivity) const {
+ return real_error.Length() < sensitivity;
+}
+
+void MotionInput::UpdateRotation(u64 elapsed_time) {
+ const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
+ if (sample_period > 0.1f) {
+ return;
+ }
+ rotations += gyro * sample_period;
+}
+
+void MotionInput::UpdateOrientation(u64 elapsed_time) {
+ if (!IsCalibrated(0.1f)) {
+ ResetOrientation();
+ }
+ // Short name local variable for readability
+ f32 q1 = quat.w;
+ f32 q2 = quat.xyz[0];
+ f32 q3 = quat.xyz[1];
+ f32 q4 = quat.xyz[2];
+ const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
+
+ // Ignore invalid elapsed time
+ if (sample_period > 0.1f) {
+ return;
+ }
+
+ const auto normal_accel = accel.Normalized();
+ auto rad_gyro = gyro * Common::PI * 2;
+ const f32 swap = rad_gyro.x;
+ rad_gyro.x = rad_gyro.y;
+ rad_gyro.y = -swap;
+ rad_gyro.z = -rad_gyro.z;
+
+ // Clear gyro values if there is no gyro present
+ if (only_accelerometer) {
+ rad_gyro.x = 0;
+ rad_gyro.y = 0;
+ rad_gyro.z = 0;
+ }
+
+ // Ignore drift correction if acceleration is not reliable
+ if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
+ const f32 ax = -normal_accel.x;
+ const f32 ay = normal_accel.y;
+ const f32 az = -normal_accel.z;
+
+ // Estimated direction of gravity
+ const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
+ const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
+ const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
+
+ // Error is cross product between estimated direction and measured direction of gravity
+ const Common::Vec3f new_real_error = {
+ az * vx - ax * vz,
+ ay * vz - az * vy,
+ ax * vy - ay * vx,
+ };
+
+ derivative_error = new_real_error - real_error;
+ real_error = new_real_error;
+
+ // Prevent integral windup
+ if (ki != 0.0f && !IsCalibrated(0.05f)) {
+ integral_error += real_error;
+ } else {
+ integral_error = {};
+ }
+
+ // Apply feedback terms
+ if (!only_accelerometer) {
+ rad_gyro += kp * real_error;
+ rad_gyro += ki * integral_error;
+ rad_gyro += kd * derivative_error;
+ } else {
+ // Give more weight to accelerometer values to compensate for the lack of gyro
+ rad_gyro += 35.0f * kp * real_error;
+ rad_gyro += 10.0f * ki * integral_error;
+ rad_gyro += 10.0f * kd * derivative_error;
+
+ // Emulate gyro values for games that need them
+ gyro.x = -rad_gyro.y;
+ gyro.y = rad_gyro.x;
+ gyro.z = -rad_gyro.z;
+ UpdateRotation(elapsed_time);
+ }
+ }
+
+ const f32 gx = rad_gyro.y;
+ const f32 gy = rad_gyro.x;
+ const f32 gz = rad_gyro.z;
+
+ // Integrate rate of change of quaternion
+ const f32 pa = q2;
+ const f32 pb = q3;
+ const f32 pc = q4;
+ q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
+ q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
+ q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
+ q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
+
+ quat.w = q1;
+ quat.xyz[0] = q2;
+ quat.xyz[1] = q3;
+ quat.xyz[2] = q4;
+ quat = quat.Normalized();
+}
+
+std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
+ const Common::Quaternion<float> quad{
+ .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
+ .w = -quat.xyz[2],
+ };
+ const std::array<float, 16> matrix4x4 = quad.ToMatrix();
+
+ return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
+ Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
+ Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
+}
+
+Common::Vec3f MotionInput::GetAcceleration() const {
+ return accel;
+}
+
+Common::Vec3f MotionInput::GetGyroscope() const {
+ return gyro;
+}
+
+Common::Quaternion<f32> MotionInput::GetQuaternion() const {
+ return quat;
+}
+
+Common::Vec3f MotionInput::GetRotations() const {
+ return rotations;
+}
+
+Input::MotionStatus MotionInput::GetMotion() const {
+ const Common::Vec3f gyroscope = GetGyroscope();
+ const Common::Vec3f accelerometer = GetAcceleration();
+ const Common::Vec3f rotation = GetRotations();
+ const std::array<Common::Vec3f, 3> orientation = GetOrientation();
+ return {accelerometer, gyroscope, rotation, orientation};
+}
+
+Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<s16> distribution(-1000, 1000);
+ const Common::Vec3f gyroscope{
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ };
+ const Common::Vec3f accelerometer{
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ static_cast<f32>(distribution(gen)) * 0.001f,
+ };
+ constexpr Common::Vec3f rotation;
+ constexpr std::array orientation{
+ Common::Vec3f{1.0f, 0.0f, 0.0f},
+ Common::Vec3f{0.0f, 1.0f, 0.0f},
+ Common::Vec3f{0.0f, 0.0f, 1.0f},
+ };
+ return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation};
+}
+
+void MotionInput::ResetOrientation() {
+ if (!reset_enabled || only_accelerometer) {
+ return;
+ }
+ if (!IsMoving(0.5f) && accel.z <= -0.9f) {
+ ++reset_counter;
+ if (reset_counter > 900) {
+ quat.w = 0;
+ quat.xyz[0] = 0;
+ quat.xyz[1] = 0;
+ quat.xyz[2] = -1;
+ SetOrientationFromAccelerometer();
+ integral_error = {};
+ reset_counter = 0;
+ }
+ } else {
+ reset_counter = 0;
+ }
+}
+
+void MotionInput::SetOrientationFromAccelerometer() {
+ int iterations = 0;
+ const f32 sample_period = 0.015f;
+
+ const auto normal_accel = accel.Normalized();
+
+ while (!IsCalibrated(0.01f) && ++iterations < 100) {
+ // Short name local variable for readability
+ f32 q1 = quat.w;
+ f32 q2 = quat.xyz[0];
+ f32 q3 = quat.xyz[1];
+ f32 q4 = quat.xyz[2];
+
+ Common::Vec3f rad_gyro;
+ const f32 ax = -normal_accel.x;
+ const f32 ay = normal_accel.y;
+ const f32 az = -normal_accel.z;
+
+ // Estimated direction of gravity
+ const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
+ const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
+ const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
+
+ // Error is cross product between estimated direction and measured direction of gravity
+ const Common::Vec3f new_real_error = {
+ az * vx - ax * vz,
+ ay * vz - az * vy,
+ ax * vy - ay * vx,
+ };
+
+ derivative_error = new_real_error - real_error;
+ real_error = new_real_error;
+
+ rad_gyro += 10.0f * kp * real_error;
+ rad_gyro += 5.0f * ki * integral_error;
+ rad_gyro += 10.0f * kd * derivative_error;
+
+ const f32 gx = rad_gyro.y;
+ const f32 gy = rad_gyro.x;
+ const f32 gz = rad_gyro.z;
+
+ // Integrate rate of change of quaternion
+ const f32 pa = q2;
+ const f32 pb = q3;
+ const f32 pc = q4;
+ q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
+ q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
+ q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
+ q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
+
+ quat.w = q1;
+ quat.xyz[0] = q2;
+ quat.xyz[1] = q3;
+ quat.xyz[2] = q4;
+ quat = quat.Normalized();
+ }
+}
+} // namespace InputCommon
diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h
new file mode 100644
index 000000000..efe74cf19
--- /dev/null
+++ b/src/input_common/motion_input.h
@@ -0,0 +1,74 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/quaternion.h"
+#include "common/vector_math.h"
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+class MotionInput {
+public:
+ explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
+
+ MotionInput(const MotionInput&) = default;
+ MotionInput& operator=(const MotionInput&) = default;
+
+ MotionInput(MotionInput&&) = default;
+ MotionInput& operator=(MotionInput&&) = default;
+
+ void SetAcceleration(const Common::Vec3f& acceleration);
+ void SetGyroscope(const Common::Vec3f& gyroscope);
+ void SetQuaternion(const Common::Quaternion<f32>& quaternion);
+ void SetGyroDrift(const Common::Vec3f& drift);
+ void SetGyroThreshold(f32 threshold);
+
+ void EnableReset(bool reset);
+ void ResetRotations();
+
+ void UpdateRotation(u64 elapsed_time);
+ void UpdateOrientation(u64 elapsed_time);
+
+ [[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
+ [[nodiscard]] Common::Vec3f GetAcceleration() const;
+ [[nodiscard]] Common::Vec3f GetGyroscope() const;
+ [[nodiscard]] Common::Vec3f GetRotations() const;
+ [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
+ [[nodiscard]] Input::MotionStatus GetMotion() const;
+ [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
+ int gyro_magnitude) const;
+
+ [[nodiscard]] bool IsMoving(f32 sensitivity) const;
+ [[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
+
+private:
+ void ResetOrientation();
+ void SetOrientationFromAccelerometer();
+
+ // PID constants
+ f32 kp;
+ f32 ki;
+ f32 kd;
+
+ // PID errors
+ Common::Vec3f real_error;
+ Common::Vec3f integral_error;
+ Common::Vec3f derivative_error;
+
+ Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
+ Common::Vec3f rotations;
+ Common::Vec3f accel;
+ Common::Vec3f gyro;
+ Common::Vec3f gyro_drift;
+
+ f32 gyro_threshold = 0.0f;
+ u32 reset_counter = 0;
+ bool reset_enabled = true;
+ bool only_accelerometer = true;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp
new file mode 100644
index 000000000..67a584d53
--- /dev/null
+++ b/src/input_common/mouse/mouse_input.cpp
@@ -0,0 +1,159 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "core/settings.h"
+#include "input_common/mouse/mouse_input.h"
+
+namespace MouseInput {
+
+Mouse::Mouse() {
+ update_thread = std::thread(&Mouse::UpdateThread, this);
+}
+
+Mouse::~Mouse() {
+ update_thread_running = false;
+ if (update_thread.joinable()) {
+ update_thread.join();
+ }
+}
+
+void Mouse::UpdateThread() {
+ constexpr int update_time = 10;
+ while (update_thread_running) {
+ for (MouseInfo& info : mouse_info) {
+ const Common::Vec3f angular_direction{
+ -info.tilt_direction.y,
+ 0.0f,
+ -info.tilt_direction.x,
+ };
+
+ info.motion.SetGyroscope(angular_direction * info.tilt_speed);
+ info.motion.UpdateRotation(update_time * 1000);
+ info.motion.UpdateOrientation(update_time * 1000);
+ info.tilt_speed = 0;
+ info.data.motion = info.motion.GetMotion();
+ }
+ if (configuring) {
+ UpdateYuzuSettings();
+ }
+ if (mouse_panning_timout++ > 8) {
+ StopPanning();
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
+ }
+}
+
+void Mouse::UpdateYuzuSettings() {
+ if (buttons == 0) {
+ return;
+ }
+
+ mouse_queue.Push(MouseStatus{
+ .button = last_button,
+ });
+}
+
+void Mouse::PressButton(int x, int y, int button_) {
+ const auto button_index = static_cast<std::size_t>(button_);
+ if (button_index >= mouse_info.size()) {
+ return;
+ }
+
+ const auto button = 1U << button_index;
+ buttons |= static_cast<u16>(button);
+ last_button = static_cast<MouseButton>(button_index);
+
+ mouse_info[button_index].mouse_origin = Common::MakeVec(x, y);
+ mouse_info[button_index].last_mouse_position = Common::MakeVec(x, y);
+ mouse_info[button_index].data.pressed = true;
+}
+
+void Mouse::StopPanning() {
+ for (MouseInfo& info : mouse_info) {
+ if (Settings::values.mouse_panning) {
+ info.data.axis = {};
+ info.tilt_speed = 0;
+ info.last_mouse_change = {};
+ }
+ }
+}
+
+void Mouse::MouseMove(int x, int y, int center_x, int center_y) {
+ for (MouseInfo& info : mouse_info) {
+ if (Settings::values.mouse_panning) {
+ const auto mouse_change = Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y);
+ mouse_panning_timout = 0;
+
+ if (mouse_change.y == 0 && mouse_change.x == 0) {
+ continue;
+ }
+
+ info.last_mouse_change = (info.last_mouse_change * 0.8f) + (mouse_change * 0.2f);
+ info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x),
+ static_cast<int>(16 * -info.last_mouse_change.y)};
+ info.tilt_direction = info.last_mouse_change;
+ info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
+ continue;
+ }
+
+ if (info.data.pressed) {
+ const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin;
+ const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position;
+ info.last_mouse_position = Common::MakeVec(x, y);
+ info.data.axis = {mouse_move.x, -mouse_move.y};
+
+ if (mouse_change.x == 0 && mouse_change.y == 0) {
+ info.tilt_speed = 0;
+ } else {
+ info.tilt_direction = mouse_change.Cast<float>();
+ info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
+ }
+ }
+ }
+}
+
+void Mouse::ReleaseButton(int button_) {
+ const auto button_index = static_cast<std::size_t>(button_);
+ if (button_index >= mouse_info.size()) {
+ return;
+ }
+
+ const auto button = 1U << button_index;
+ buttons &= static_cast<u16>(0xFF - button);
+
+ mouse_info[button_index].tilt_speed = 0;
+ mouse_info[button_index].data.pressed = false;
+ mouse_info[button_index].data.axis = {0, 0};
+}
+
+void Mouse::BeginConfiguration() {
+ buttons = 0;
+ last_button = MouseButton::Undefined;
+ mouse_queue.Clear();
+ configuring = true;
+}
+
+void Mouse::EndConfiguration() {
+ buttons = 0;
+ last_button = MouseButton::Undefined;
+ mouse_queue.Clear();
+ configuring = false;
+}
+
+Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() {
+ return mouse_queue;
+}
+
+const Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() const {
+ return mouse_queue;
+}
+
+MouseData& Mouse::GetMouseState(std::size_t button) {
+ return mouse_info[button].data;
+}
+
+const MouseData& Mouse::GetMouseState(std::size_t button) const {
+ return mouse_info[button].data;
+}
+} // namespace MouseInput
diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h
new file mode 100644
index 000000000..46aa676c1
--- /dev/null
+++ b/src/input_common/mouse/mouse_input.h
@@ -0,0 +1,103 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <mutex>
+#include <thread>
+
+#include "common/common_types.h"
+#include "common/threadsafe_queue.h"
+#include "common/vector_math.h"
+#include "core/frontend/input.h"
+#include "input_common/motion_input.h"
+
+namespace MouseInput {
+
+enum class MouseButton {
+ Left,
+ Wheel,
+ Right,
+ Forward,
+ Backward,
+ Undefined,
+};
+
+struct MouseStatus {
+ MouseButton button{MouseButton::Undefined};
+};
+
+struct MouseData {
+ bool pressed{};
+ std::array<int, 2> axis{};
+ Input::MotionStatus motion{};
+ Input::TouchStatus touch{};
+};
+
+class Mouse {
+public:
+ Mouse();
+ ~Mouse();
+
+ /// Used for polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ /**
+ * Signals that a button is pressed.
+ * @param x the x-coordinate of the cursor
+ * @param y the y-coordinate of the cursor
+ * @param button_ the button pressed
+ */
+ void PressButton(int x, int y, int button_);
+
+ /**
+ * Signals that mouse has moved.
+ * @param x the x-coordinate of the cursor
+ * @param y the y-coordinate of the cursor
+ * @param center_x the x-coordinate of the middle of the screen
+ * @param center_y the y-coordinate of the middle of the screen
+ */
+ void MouseMove(int x, int y, int center_x, int center_y);
+
+ /**
+ * Signals that a motion sensor tilt has ended.
+ */
+ void ReleaseButton(int button_);
+
+ [[nodiscard]] Common::SPSCQueue<MouseStatus>& GetMouseQueue();
+ [[nodiscard]] const Common::SPSCQueue<MouseStatus>& GetMouseQueue() const;
+
+ [[nodiscard]] MouseData& GetMouseState(std::size_t button);
+ [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const;
+
+private:
+ void UpdateThread();
+ void UpdateYuzuSettings();
+ void StopPanning();
+
+ struct MouseInfo {
+ InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
+ Common::Vec2<int> mouse_origin;
+ Common::Vec2<int> last_mouse_position;
+ Common::Vec2<float> last_mouse_change;
+ bool is_tilting = false;
+ float sensitivity{0.120f};
+
+ float tilt_speed = 0;
+ Common::Vec2<float> tilt_direction;
+ MouseData data;
+ };
+
+ u16 buttons{};
+ std::thread update_thread;
+ MouseButton last_button{MouseButton::Undefined};
+ std::array<MouseInfo, 5> mouse_info;
+ Common::SPSCQueue<MouseStatus> mouse_queue;
+ bool configuring{false};
+ bool update_thread_running{true};
+ int mouse_panning_timout{};
+};
+} // namespace MouseInput
diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp
new file mode 100644
index 000000000..bb56787ee
--- /dev/null
+++ b/src/input_common/mouse/mouse_poller.cpp
@@ -0,0 +1,285 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <mutex>
+#include <utility>
+
+#include "common/threadsafe_queue.h"
+#include "core/settings.h"
+#include "input_common/mouse/mouse_input.h"
+#include "input_common/mouse/mouse_poller.h"
+
+namespace InputCommon {
+
+class MouseButton final : public Input::ButtonDevice {
+public:
+ explicit MouseButton(u32 button_, const MouseInput::Mouse* mouse_input_)
+ : button(button_), mouse_input(mouse_input_) {}
+
+ bool GetStatus() const override {
+ return mouse_input->GetMouseState(button).pressed;
+ }
+
+private:
+ const u32 button;
+ const MouseInput::Mouse* mouse_input;
+};
+
+MouseButtonFactory::MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
+ : mouse_input(std::move(mouse_input_)) {}
+
+std::unique_ptr<Input::ButtonDevice> MouseButtonFactory::Create(
+ const Common::ParamPackage& params) {
+ const auto button_id = params.Get("button", 0);
+
+ return std::make_unique<MouseButton>(button_id, mouse_input.get());
+}
+
+Common::ParamPackage MouseButtonFactory::GetNextInput() const {
+ MouseInput::MouseStatus pad;
+ Common::ParamPackage params;
+ auto& queue = mouse_input->GetMouseQueue();
+ while (queue.Pop(pad)) {
+ // This while loop will break on the earliest detected button
+ if (pad.button != MouseInput::MouseButton::Undefined) {
+ params.Set("engine", "mouse");
+ params.Set("button", static_cast<u16>(pad.button));
+ return params;
+ }
+ }
+ return params;
+}
+
+void MouseButtonFactory::BeginConfiguration() {
+ polling = true;
+ mouse_input->BeginConfiguration();
+}
+
+void MouseButtonFactory::EndConfiguration() {
+ polling = false;
+ mouse_input->EndConfiguration();
+}
+
+class MouseAnalog final : public Input::AnalogDevice {
+public:
+ explicit MouseAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
+ float deadzone_, float range_, const MouseInput::Mouse* mouse_input_)
+ : button(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
+ deadzone(deadzone_), range(range_), mouse_input(mouse_input_) {}
+
+ float GetAxis(u32 axis) const {
+ std::lock_guard lock{mutex};
+ const auto axis_value =
+ static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis));
+ return axis_value * Settings::values.mouse_panning_sensitivity / (100.0f * range);
+ }
+
+ std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
+ float x = GetAxis(analog_axis_x);
+ float y = GetAxis(analog_axis_y);
+ if (invert_x) {
+ x = -x;
+ }
+ if (invert_y) {
+ y = -y;
+ }
+
+ // Make sure the coordinates are in the unit circle,
+ // otherwise normalize it.
+ float r = x * x + y * y;
+ if (r > 1.0f) {
+ r = std::sqrt(r);
+ x /= r;
+ y /= r;
+ }
+
+ return {x, y};
+ }
+
+ std::tuple<float, float> GetStatus() const override {
+ const auto [x, y] = GetAnalog(axis_x, axis_y);
+ const float r = std::sqrt((x * x) + (y * y));
+ if (r > deadzone) {
+ return {x / r * (r - deadzone) / (1 - deadzone),
+ y / r * (r - deadzone) / (1 - deadzone)};
+ }
+ return {0.0f, 0.0f};
+ }
+
+ std::tuple<float, float> GetRawStatus() const override {
+ const float x = GetAxis(axis_x);
+ const float y = GetAxis(axis_y);
+ return {x, y};
+ }
+
+ Input::AnalogProperties GetAnalogProperties() const override {
+ return {deadzone, range, 0.5f};
+ }
+
+private:
+ const u32 button;
+ const u32 axis_x;
+ const u32 axis_y;
+ const bool invert_x;
+ const bool invert_y;
+ const float deadzone;
+ const float range;
+ const MouseInput::Mouse* mouse_input;
+ mutable std::mutex mutex;
+};
+
+/// An analog device factory that creates analog devices from GC Adapter
+MouseAnalogFactory::MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
+ : mouse_input(std::move(mouse_input_)) {}
+
+/**
+ * Creates analog device from joystick axes
+ * @param params contains parameters for creating the device:
+ * - "port": the nth gcpad on the adapter
+ * - "axis_x": the index of the axis to be bind as x-axis
+ * - "axis_y": the index of the axis to be bind as y-axis
+ */
+std::unique_ptr<Input::AnalogDevice> MouseAnalogFactory::Create(
+ const Common::ParamPackage& params) {
+ const auto port = static_cast<u32>(params.Get("port", 0));
+ const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
+ const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
+ const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
+ const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
+ const std::string invert_x_value = params.Get("invert_x", "+");
+ const std::string invert_y_value = params.Get("invert_y", "+");
+ const bool invert_x = invert_x_value == "-";
+ const bool invert_y = invert_y_value == "-";
+
+ return std::make_unique<MouseAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
+ mouse_input.get());
+}
+
+void MouseAnalogFactory::BeginConfiguration() {
+ polling = true;
+ mouse_input->BeginConfiguration();
+}
+
+void MouseAnalogFactory::EndConfiguration() {
+ polling = false;
+ mouse_input->EndConfiguration();
+}
+
+Common::ParamPackage MouseAnalogFactory::GetNextInput() const {
+ MouseInput::MouseStatus pad;
+ Common::ParamPackage params;
+ auto& queue = mouse_input->GetMouseQueue();
+ while (queue.Pop(pad)) {
+ // This while loop will break on the earliest detected button
+ if (pad.button != MouseInput::MouseButton::Undefined) {
+ params.Set("engine", "mouse");
+ params.Set("port", static_cast<u16>(pad.button));
+ params.Set("axis_x", 0);
+ params.Set("axis_y", 1);
+ params.Set("invert_x", "+");
+ params.Set("invert_y", "+");
+ return params;
+ }
+ }
+ return params;
+}
+
+class MouseMotion final : public Input::MotionDevice {
+public:
+ explicit MouseMotion(u32 button_, const MouseInput::Mouse* mouse_input_)
+ : button(button_), mouse_input(mouse_input_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ return mouse_input->GetMouseState(button).motion;
+ }
+
+private:
+ const u32 button;
+ const MouseInput::Mouse* mouse_input;
+};
+
+MouseMotionFactory::MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
+ : mouse_input(std::move(mouse_input_)) {}
+
+std::unique_ptr<Input::MotionDevice> MouseMotionFactory::Create(
+ const Common::ParamPackage& params) {
+ const auto button_id = params.Get("button", 0);
+
+ return std::make_unique<MouseMotion>(button_id, mouse_input.get());
+}
+
+Common::ParamPackage MouseMotionFactory::GetNextInput() const {
+ MouseInput::MouseStatus pad;
+ Common::ParamPackage params;
+ auto& queue = mouse_input->GetMouseQueue();
+ while (queue.Pop(pad)) {
+ // This while loop will break on the earliest detected button
+ if (pad.button != MouseInput::MouseButton::Undefined) {
+ params.Set("engine", "mouse");
+ params.Set("button", static_cast<u16>(pad.button));
+ return params;
+ }
+ }
+ return params;
+}
+
+void MouseMotionFactory::BeginConfiguration() {
+ polling = true;
+ mouse_input->BeginConfiguration();
+}
+
+void MouseMotionFactory::EndConfiguration() {
+ polling = false;
+ mouse_input->EndConfiguration();
+}
+
+class MouseTouch final : public Input::TouchDevice {
+public:
+ explicit MouseTouch(u32 button_, const MouseInput::Mouse* mouse_input_)
+ : button(button_), mouse_input(mouse_input_) {}
+
+ Input::TouchStatus GetStatus() const override {
+ return mouse_input->GetMouseState(button).touch;
+ }
+
+private:
+ const u32 button;
+ const MouseInput::Mouse* mouse_input;
+};
+
+MouseTouchFactory::MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
+ : mouse_input(std::move(mouse_input_)) {}
+
+std::unique_ptr<Input::TouchDevice> MouseTouchFactory::Create(const Common::ParamPackage& params) {
+ const auto button_id = params.Get("button", 0);
+
+ return std::make_unique<MouseTouch>(button_id, mouse_input.get());
+}
+
+Common::ParamPackage MouseTouchFactory::GetNextInput() const {
+ MouseInput::MouseStatus pad;
+ Common::ParamPackage params;
+ auto& queue = mouse_input->GetMouseQueue();
+ while (queue.Pop(pad)) {
+ // This while loop will break on the earliest detected button
+ if (pad.button != MouseInput::MouseButton::Undefined) {
+ params.Set("engine", "mouse");
+ params.Set("button", static_cast<u16>(pad.button));
+ return params;
+ }
+ }
+ return params;
+}
+
+void MouseTouchFactory::BeginConfiguration() {
+ polling = true;
+ mouse_input->BeginConfiguration();
+}
+
+void MouseTouchFactory::EndConfiguration() {
+ polling = false;
+ mouse_input->EndConfiguration();
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/mouse/mouse_poller.h b/src/input_common/mouse/mouse_poller.h
new file mode 100644
index 000000000..cf331293b
--- /dev/null
+++ b/src/input_common/mouse/mouse_poller.h
@@ -0,0 +1,109 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+#include "input_common/mouse/mouse_input.h"
+
+namespace InputCommon {
+
+/**
+ * A button device factory representing a mouse. It receives mouse events and forward them
+ * to all button devices it created.
+ */
+class MouseButtonFactory final : public Input::Factory<Input::ButtonDevice> {
+public:
+ explicit MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
+
+ /**
+ * Creates a button device from a button press
+ * @param params contains parameters for creating the device:
+ * - "code": the code of the key to bind with the button
+ */
+ std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput() const;
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<MouseInput::Mouse> mouse_input;
+ bool polling = false;
+};
+
+/// An analog device factory that creates analog devices from mouse
+class MouseAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
+public:
+ explicit MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
+
+ std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput() const;
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<MouseInput::Mouse> mouse_input;
+ bool polling = false;
+};
+
+/// A motion device factory that creates motion devices from mouse
+class MouseMotionFactory final : public Input::Factory<Input::MotionDevice> {
+public:
+ explicit MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
+
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput() const;
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<MouseInput::Mouse> mouse_input;
+ bool polling = false;
+};
+
+/// An touch device factory that creates touch devices from mouse
+class MouseTouchFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+ explicit MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
+
+ std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput() const;
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<MouseInput::Mouse> mouse_input;
+ bool polling = false;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index f3554be9a..42bbf14d4 100644
--- a/src/input_common/sdl/sdl.h
+++ b/src/input_common/sdl/sdl.h
@@ -23,7 +23,7 @@ public:
/// Unregisters SDL device factories and shut them down.
virtual ~State() = default;
- virtual Pollers GetPollers(Polling::DeviceType type) {
+ virtual Pollers GetPollers(Polling::DeviceType) {
return {};
}
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index e865a6831..a88ae452f 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -3,7 +3,9 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <atomic>
+#include <chrono>
#include <cmath>
#include <functional>
#include <mutex>
@@ -20,6 +22,7 @@
#include "common/param_package.h"
#include "common/threadsafe_queue.h"
#include "core/frontend/input.h"
+#include "input_common/motion_input.h"
#include "input_common/sdl/sdl_impl.h"
#include "input_common/settings.h"
@@ -53,9 +56,9 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) {
class SDLJoystick {
public:
SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
- SDL_GameController* gamecontroller)
+ SDL_GameController* game_controller)
: guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
- sdl_controller{gamecontroller, &SDL_GameControllerClose} {}
+ sdl_controller{game_controller, &SDL_GameControllerClose} {}
void SetButton(int button, bool value) {
std::lock_guard lock{mutex};
@@ -74,7 +77,21 @@ public:
float GetAxis(int axis, float range) const {
std::lock_guard lock{mutex};
- return state.axes.at(axis) / (32767.0f * range);
+ return static_cast<float>(state.axes.at(axis)) / (32767.0f * range);
+ }
+
+ bool RumblePlay(u16 amp_low, u16 amp_high) {
+ constexpr u32 rumble_max_duration_ms = 1000;
+
+ if (sdl_controller) {
+ return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high,
+ rumble_max_duration_ms) == 0;
+ } else if (sdl_joystick) {
+ return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high,
+ rumble_max_duration_ms) == 0;
+ }
+
+ return false;
}
std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const {
@@ -94,6 +111,10 @@ public:
return std::make_tuple(x, y);
}
+ const MotionInput& GetMotion() const {
+ return motion;
+ }
+
void SetHat(int hat, Uint8 direction) {
std::lock_guard lock{mutex};
state.hats.insert_or_assign(hat, direction);
@@ -121,15 +142,15 @@ public:
return sdl_joystick.get();
}
- void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
- sdl_controller.reset(controller);
- sdl_joystick.reset(joystick);
- }
-
SDL_GameController* GetSDLGameController() const {
return sdl_controller.get();
}
+ void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
+ sdl_joystick.reset(joystick);
+ sdl_controller.reset(controller);
+ }
+
private:
struct State {
std::unordered_map<int, bool> buttons;
@@ -141,74 +162,66 @@ private:
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
mutable std::mutex mutex;
+
+ // Motion is initialized without PID values as motion input is not aviable for SDL2
+ MotionInput motion{0.0f, 0.0f, 0.0f};
};
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
std::lock_guard lock{joystick_map_mutex};
const auto it = joystick_map.find(guid);
+
if (it != joystick_map.end()) {
while (it->second.size() <= static_cast<std::size_t>(port)) {
auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
nullptr, nullptr);
it->second.emplace_back(std::move(joystick));
}
- return it->second[port];
+
+ return it->second[static_cast<std::size_t>(port)];
}
+
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
+
return joystick_map[guid].emplace_back(std::move(joystick));
}
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
- auto sdl_controller = SDL_GameControllerFromInstanceID(sdl_id);
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
const auto map_it = joystick_map.find(guid);
- if (map_it != joystick_map.end()) {
- const auto vec_it =
- std::find_if(map_it->second.begin(), map_it->second.end(),
- [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
- return sdl_joystick == joystick->GetSDLJoystick();
- });
- if (vec_it != map_it->second.end()) {
- // This is the common case: There is already an existing SDL_Joystick maped to a
- // SDLJoystick. return the SDLJoystick
- return *vec_it;
- }
-
- // Search for a SDLJoystick without a mapped SDL_Joystick...
- const auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
- [](const std::shared_ptr<SDLJoystick>& joystick) {
- return !joystick->GetSDLJoystick();
- });
- if (nullptr_it != map_it->second.end()) {
- // ... and map it
- (*nullptr_it)->SetSDLJoystick(sdl_joystick, sdl_controller);
- return *nullptr_it;
- }
-
- // There is no SDLJoystick without a mapped SDL_Joystick
- // Create a new SDLJoystick
- const int port = static_cast<int>(map_it->second.size());
- auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_controller);
- return map_it->second.emplace_back(std::move(joystick));
- }
-
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_controller);
- return joystick_map[guid].emplace_back(std::move(joystick));
+
+ if (map_it == joystick_map.end()) {
+ return nullptr;
+ }
+
+ const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
+ [&sdl_joystick](const auto& joystick) {
+ return joystick->GetSDLJoystick() == sdl_joystick;
+ });
+
+ if (vec_it == map_it->second.end()) {
+ return nullptr;
+ }
+
+ return *vec_it;
}
void SDLState::InitJoystick(int joystick_index) {
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
SDL_GameController* sdl_gamecontroller = nullptr;
+
if (SDL_IsGameController(joystick_index)) {
sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
}
+
if (!sdl_joystick) {
- LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
+ LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
return;
}
+
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
@@ -217,14 +230,17 @@ void SDLState::InitJoystick(int joystick_index) {
joystick_map[guid].emplace_back(std::move(joystick));
return;
}
+
auto& joystick_guid_list = joystick_map[guid];
- const auto it = std::find_if(
- joystick_guid_list.begin(), joystick_guid_list.end(),
- [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
- if (it != joystick_guid_list.end()) {
- (*it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
+ const auto joystick_it =
+ std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
+ [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
+
+ if (joystick_it != joystick_guid_list.end()) {
+ (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
return;
}
+
const int port = static_cast<int>(joystick_guid_list.size());
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
joystick_guid_list.emplace_back(std::move(joystick));
@@ -233,22 +249,15 @@ void SDLState::InitJoystick(int joystick_index) {
void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
const std::string guid = GetGUID(sdl_joystick);
- std::shared_ptr<SDLJoystick> joystick;
- {
- std::lock_guard lock{joystick_map_mutex};
- // This call to guid is safe since the joystick is guaranteed to be in the map
- const auto& joystick_guid_list = joystick_map[guid];
- const auto joystick_it =
- std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
- [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
- return joystick->GetSDLJoystick() == sdl_joystick;
- });
- joystick = *joystick_it;
- }
-
- // Destruct SDL_Joystick outside the lock guard because SDL can internally call the
- // event callback which locks the mutex again.
- joystick->SetSDLJoystick(nullptr, nullptr);
+ std::lock_guard lock{joystick_map_mutex};
+ // This call to guid is safe since the joystick is guaranteed to be in the map
+ const auto& joystick_guid_list = joystick_map[guid];
+ const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
+ [&sdl_joystick](const auto& joystick) {
+ return joystick->GetSDLJoystick() == sdl_joystick;
+ });
+
+ (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
}
void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
@@ -346,19 +355,36 @@ private:
class SDLAnalog final : public Input::AnalogDevice {
public:
- SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_,
- float range_)
- : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_),
- range(range_) {}
+ explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_,
+ bool invert_x_, bool invert_y_, float deadzone_, float range_)
+ : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_),
+ invert_y(invert_y_), deadzone(deadzone_), range(range_) {}
std::tuple<float, float> GetStatus() const override {
- const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);
+ auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);
const float r = std::sqrt((x * x) + (y * y));
+ if (invert_x) {
+ x = -x;
+ }
+ if (invert_y) {
+ y = -y;
+ }
+
if (r > deadzone) {
return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
y / r * (r - deadzone) / (1 - deadzone));
}
- return std::make_tuple<float, float>(0.0f, 0.0f);
+ return {};
+ }
+
+ std::tuple<float, float> GetRawStatus() const override {
+ const float x = joystick->GetAxis(axis_x, range);
+ const float y = joystick->GetAxis(axis_y, range);
+ return {x, -y};
+ }
+
+ Input::AnalogProperties GetAnalogProperties() const override {
+ return {deadzone, range, 0.5f};
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
@@ -381,10 +407,100 @@ private:
std::shared_ptr<SDLJoystick> joystick;
const int axis_x;
const int axis_y;
+ const bool invert_x;
+ const bool invert_y;
const float deadzone;
const float range;
};
+class SDLVibration final : public Input::VibrationDevice {
+public:
+ explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_)
+ : joystick(std::move(joystick_)) {}
+
+ u8 GetStatus() const override {
+ joystick->RumblePlay(1, 1);
+ return joystick->RumblePlay(0, 0);
+ }
+
+ bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
+ [[maybe_unused]] f32 freq_high) const override {
+ const auto process_amplitude = [](f32 amplitude) {
+ return static_cast<u16>((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF);
+ };
+
+ const auto processed_amp_low = process_amplitude(amp_low);
+ const auto processed_amp_high = process_amplitude(amp_high);
+
+ return joystick->RumblePlay(processed_amp_low, processed_amp_high);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+};
+
+class SDLDirectionMotion final : public Input::MotionDevice {
+public:
+ explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
+ : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ if (joystick->GetHatDirection(hat, direction)) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int hat;
+ Uint8 direction;
+};
+
+class SDLAxisMotion final : public Input::MotionDevice {
+public:
+ explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
+ bool trigger_if_greater_)
+ : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
+ trigger_if_greater(trigger_if_greater_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ const float axis_value = joystick->GetAxis(axis, 1.0f);
+ bool trigger = axis_value < threshold;
+ if (trigger_if_greater) {
+ trigger = axis_value > threshold;
+ }
+
+ if (trigger) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int axis;
+ float threshold;
+ bool trigger_if_greater;
+};
+
+class SDLButtonMotion final : public Input::MotionDevice {
+public:
+ explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
+ : joystick(std::move(joystick_)), button(button_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ if (joystick->GetButton(button)) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int button;
+};
+
/// A button device factory that creates button devices from SDL joystick
class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
public:
@@ -465,7 +581,7 @@ class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
public:
explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
/**
- * Creates analog device from joystick axes
+ * Creates an analog device from joystick axes
* @param params contains parameters for creating the device:
* - "guid": the guid of the joystick to bind
* - "port": the nth joystick of the same type
@@ -479,12 +595,101 @@ public:
const int axis_y = params.Get("axis_y", 1);
const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
+ const std::string invert_x_value = params.Get("invert_x", "+");
+ const std::string invert_y_value = params.Get("invert_y", "+");
+ const bool invert_x = invert_x_value == "-";
+ const bool invert_y = invert_y_value == "-";
auto joystick = state.GetSDLJoystickByGUID(guid, port);
// This is necessary so accessing GetAxis with axis_x and axis_y won't crash
joystick->SetAxis(axis_x, 0);
joystick->SetAxis(axis_y, 0);
- return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range);
+ return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone,
+ range);
+ }
+
+private:
+ SDLState& state;
+};
+
+/// An vibration device factory that creates vibration devices from SDL joystick
+class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
+public:
+ explicit SDLVibrationFactory(SDLState& state_) : state(state_) {}
+ /**
+ * Creates a vibration device from a joystick
+ * @param params contains parameters for creating the device:
+ * - "guid": the guid of the joystick to bind
+ * - "port": the nth joystick of the same type
+ */
+ std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override {
+ const std::string guid = params.Get("guid", "0");
+ const int port = params.Get("port", 0);
+ return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
+ }
+
+private:
+ SDLState& state;
+};
+
+/// A motion device factory that creates motion devices from SDL joystick
+class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
+public:
+ explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
+ /**
+ * Creates motion device from joystick axes
+ * @param params contains parameters for creating the device:
+ * - "guid": the guid of the joystick to bind
+ * - "port": the nth joystick of the same type
+ */
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
+ const std::string guid = params.Get("guid", "0");
+ const int port = params.Get("port", 0);
+
+ auto joystick = state.GetSDLJoystickByGUID(guid, port);
+
+ if (params.Has("hat")) {
+ const int hat = params.Get("hat", 0);
+ const std::string direction_name = params.Get("direction", "");
+ Uint8 direction;
+ if (direction_name == "up") {
+ direction = SDL_HAT_UP;
+ } else if (direction_name == "down") {
+ direction = SDL_HAT_DOWN;
+ } else if (direction_name == "left") {
+ direction = SDL_HAT_LEFT;
+ } else if (direction_name == "right") {
+ direction = SDL_HAT_RIGHT;
+ } else {
+ direction = 0;
+ }
+ // This is necessary so accessing GetHat with hat won't crash
+ joystick->SetHat(hat, SDL_HAT_CENTERED);
+ return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
+ }
+
+ if (params.Has("axis")) {
+ const int axis = params.Get("axis", 0);
+ const float threshold = params.Get("threshold", 0.5f);
+ const std::string direction_name = params.Get("direction", "");
+ bool trigger_if_greater;
+ if (direction_name == "+") {
+ trigger_if_greater = true;
+ } else if (direction_name == "-") {
+ trigger_if_greater = false;
+ } else {
+ trigger_if_greater = true;
+ LOG_ERROR(Input, "Unknown direction {}", direction_name);
+ }
+ // This is necessary so accessing GetAxis with axis won't crash
+ joystick->SetAxis(axis, 0);
+ return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
+ }
+
+ const int button = params.Get("button", 0);
+ // This is necessary so accessing GetButton with button won't crash
+ joystick->SetButton(button, false);
+ return std::make_unique<SDLButtonMotion>(joystick, button);
}
private:
@@ -493,18 +698,22 @@ private:
SDLState::SDLState() {
using namespace Input;
- analog_factory = std::make_shared<SDLAnalogFactory>(*this);
button_factory = std::make_shared<SDLButtonFactory>(*this);
- RegisterFactory<AnalogDevice>("sdl", analog_factory);
+ analog_factory = std::make_shared<SDLAnalogFactory>(*this);
+ vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
+ motion_factory = std::make_shared<SDLMotionFactory>(*this);
RegisterFactory<ButtonDevice>("sdl", button_factory);
+ RegisterFactory<AnalogDevice>("sdl", analog_factory);
+ RegisterFactory<VibrationDevice>("sdl", vibration_factory);
+ RegisterFactory<MotionDevice>("sdl", motion_factory);
- // If the frontend is going to manage the event loop, then we dont start one here
- start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
+ // If the frontend is going to manage the event loop, then we don't start one here
+ start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0;
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
return;
}
- has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
+ has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0;
if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
}
@@ -524,7 +733,7 @@ SDLState::SDLState() {
using namespace std::chrono_literals;
while (initialized) {
SDL_PumpEvents();
- std::this_thread::sleep_for(5ms);
+ std::this_thread::sleep_for(1ms);
}
});
}
@@ -539,6 +748,8 @@ SDLState::~SDLState() {
using namespace Input;
UnregisterFactory<ButtonDevice>("sdl");
UnregisterFactory<AnalogDevice>("sdl");
+ UnregisterFactory<VibrationDevice>("sdl");
+ UnregisterFactory<MotionDevice>("sdl");
CloseJoysticks();
SDL_DelEventWatch(&SDLEventWatcher, this);
@@ -555,8 +766,7 @@ std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
std::vector<Common::ParamPackage> devices;
for (const auto& [key, value] : joystick_map) {
for (const auto& joystick : value) {
- auto joy = joystick->GetSDLJoystick();
- if (auto controller = joystick->GetSDLGameController()) {
+ if (auto* const controller = joystick->GetSDLGameController()) {
std::string name =
fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort());
devices.emplace_back(Common::ParamPackage{
@@ -565,7 +775,7 @@ std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
{"guid", joystick->GetGUID()},
{"port", std::to_string(joystick->GetPort())},
});
- } else if (joy) {
+ } else if (auto* const joy = joystick->GetSDLJoystick()) {
std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort());
devices.emplace_back(Common::ParamPackage{
{"class", "sdl"},
@@ -580,11 +790,11 @@ std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
}
namespace {
-Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis,
- float value = 0.1) {
+Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
+ float value = 0.1f) {
Common::ParamPackage params({{"engine", "sdl"}});
params.Set("port", port);
- params.Set("guid", guid);
+ params.Set("guid", std::move(guid));
params.Set("axis", axis);
if (value > 0) {
params.Set("direction", "+");
@@ -596,19 +806,19 @@ Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid
return params;
}
-Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) {
+Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) {
Common::ParamPackage params({{"engine", "sdl"}});
params.Set("port", port);
- params.Set("guid", guid);
+ params.Set("guid", std::move(guid));
params.Set("button", button);
return params;
}
-Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u8 hat, u8 value) {
+Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) {
Common::ParamPackage params({{"engine", "sdl"}});
params.Set("port", port);
- params.Set("guid", guid);
+ params.Set("guid", std::move(guid));
params.Set("hat", hat);
switch (value) {
case SDL_HAT_UP:
@@ -632,19 +842,56 @@ Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u
Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
switch (event.type) {
case SDL_JOYAXISMOTION: {
- const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
- event.jaxis.axis, event.jaxis.value);
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
+ return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jaxis.axis),
+ event.jaxis.value);
+ }
+ break;
}
case SDL_JOYBUTTONUP: {
- const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
- return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
- event.jbutton.button);
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
+ return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jbutton.button));
+ }
+ break;
}
case SDL_JOYHATMOTION: {
- const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
- return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
- event.jhat.hat, event.jhat.value);
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
+ return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jhat.hat),
+ static_cast<s32>(event.jhat.value));
+ }
+ break;
+ }
+ }
+ return {};
+}
+
+Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION: {
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
+ return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jaxis.axis),
+ event.jaxis.value);
+ }
+ break;
+ }
+ case SDL_JOYBUTTONUP: {
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
+ return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jbutton.button));
+ }
+ break;
+ }
+ case SDL_JOYHATMOTION: {
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
+ return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ static_cast<s32>(event.jhat.hat),
+ static_cast<s32>(event.jhat.value));
+ }
+ break;
}
}
return {};
@@ -653,6 +900,8 @@ Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Eve
Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
const SDL_GameControllerButtonBind& binding) {
switch (binding.bindType) {
+ case SDL_CONTROLLER_BINDTYPE_NONE:
+ break;
case SDL_CONTROLLER_BINDTYPE_AXIS:
return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
case SDL_CONTROLLER_BINDTYPE_BUTTON:
@@ -672,60 +921,69 @@ Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& gui
params.Set("guid", guid);
params.Set("axis_x", axis_x);
params.Set("axis_y", axis_y);
+ params.Set("invert_x", "+");
+ params.Set("invert_y", "+");
return params;
}
} // Anonymous namespace
ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
- // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
- // We will add those afterwards
- // This list also excludes Screenshot since theres not really a mapping for that
- std::unordered_map<Settings::NativeButton::Values, SDL_GameControllerButton>
- switch_to_sdl_button = {
- {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
- {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
- {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
- {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
- {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
- {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
- {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
- {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
- {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
- {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
- {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
- {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
- {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
- {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
- {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
- {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
- {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
- };
if (!params.Has("guid") || !params.Has("port")) {
return {};
}
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
- auto controller = joystick->GetSDLGameController();
- if (!controller) {
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
return {};
}
- ButtonMapping mapping{};
+ // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
+ // We will add those afterwards
+ // This list also excludes Screenshot since theres not really a mapping for that
+ using ButtonBindings =
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
+ static constexpr ButtonBindings switch_to_sdl_button{{
+ {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
+ {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
+ {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
+ {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
+ {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
+ {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
+ {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
+ {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
+ {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
+ {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
+ {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
+ {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
+ {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
+ }};
+
+ // Add the missing bindings for ZL/ZR
+ using ZBindings =
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
+ static constexpr ZBindings switch_to_sdl_axis{{
+ {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
+ {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
+ }};
+
+ ButtonMapping mapping;
+ mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
+
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
- mapping[switch_button] =
- BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding);
+ mapping.insert_or_assign(
+ switch_button,
+ BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
}
-
- // Add the missing bindings for ZL/ZR
- std::unordered_map<Settings::NativeButton::Values, SDL_GameControllerAxis> switch_to_sdl_axis =
- {
- {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
- {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
- };
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
- mapping[switch_button] =
- BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding);
+ mapping.insert_or_assign(
+ switch_button,
+ BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
}
return mapping;
@@ -736,8 +994,8 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa
return {};
}
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
- auto controller = joystick->GetSDLGameController();
- if (!controller) {
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
return {};
}
@@ -746,16 +1004,18 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
const auto& binding_left_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
- mapping[Settings::NativeAnalog::LStick] =
- BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
- binding_left_x.value.axis, binding_left_y.value.axis);
+ mapping.insert_or_assign(Settings::NativeAnalog::LStick,
+ BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ binding_left_x.value.axis,
+ binding_left_y.value.axis));
const auto& binding_right_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
const auto& binding_right_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
- mapping[Settings::NativeAnalog::RStick] =
- BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
- binding_right_x.value.axis, binding_right_y.value.axis);
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick,
+ BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ binding_right_x.value.axis,
+ binding_right_y.value.axis));
return mapping;
}
@@ -764,7 +1024,7 @@ class SDLPoller : public InputCommon::Polling::DevicePoller {
public:
explicit SDLPoller(SDLState& state_) : state(state_) {}
- void Start(const std::string& device_id) override {
+ void Start([[maybe_unused]] const std::string& device_id) override {
state.event_queue.Clear();
state.polling = true;
}
@@ -791,19 +1051,91 @@ public:
}
return {};
}
- std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) {
+ [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(SDL_Event& event) {
switch (event.type) {
case SDL_JOYAXISMOTION:
- if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ if (!axis_memory.count(event.jaxis.which) ||
+ !axis_memory[event.jaxis.which].count(event.jaxis.axis)) {
+ axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value;
+ axis_event_count[event.jaxis.which][event.jaxis.axis] = 1;
break;
+ } else {
+ axis_event_count[event.jaxis.which][event.jaxis.axis]++;
+ // The joystick and axis exist in our map if we take this branch, so no checks
+ // needed
+ if (std::abs(
+ (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) /
+ 32767.0) < 0.5) {
+ break;
+ } else {
+ if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 &&
+ IsAxisAtPole(event.jaxis.value) &&
+ IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) {
+ // If we have exactly two events and both are near a pole, this is
+ // likely a digital input masquerading as an analog axis; Instead of
+ // trying to look at the direction the axis travelled, assume the first
+ // event was press and the second was release; This should handle most
+ // digital axes while deferring to the direction of travel for analog
+ // axes
+ event.jaxis.value = static_cast<Sint16>(
+ std::copysign(32767, axis_memory[event.jaxis.which][event.jaxis.axis]));
+ } else {
+ // There are more than two events, so this is likely a true analog axis,
+ // check the direction it travelled
+ event.jaxis.value = static_cast<Sint16>(std::copysign(
+ 32767,
+ event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]));
+ }
+ axis_memory.clear();
+ axis_event_count.clear();
+ }
}
[[fallthrough]];
case SDL_JOYBUTTONUP:
case SDL_JOYHATMOTION:
return {SDLEventToButtonParamPackage(state, event)};
}
+ return std::nullopt;
+ }
+
+private:
+ // Determine whether an axis value is close to an extreme or center
+ // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per
+ // axis, which is why the center must be considered a pole
+ bool IsAxisAtPole(int16_t value) const {
+ return std::abs(value) >= 32767 || std::abs(value) < 327;
+ }
+ std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory;
+ std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count;
+};
+
+class SDLMotionPoller final : public SDLPoller {
+public:
+ explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
+
+ Common::ParamPackage GetNextInput() override {
+ SDL_Event event;
+ while (state.event_queue.Pop(event)) {
+ const auto package = FromEvent(event);
+ if (package) {
+ return *package;
+ }
+ }
return {};
}
+ [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION:
+ if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ break;
+ }
+ [[fallthrough]];
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYHATMOTION:
+ return {SDLEventToMotionParamPackage(state, event)};
+ }
+ return std::nullopt;
+ }
};
/**
@@ -818,7 +1150,6 @@ public:
void Start(const std::string& device_id) override {
SDLPoller::Start(device_id);
- // Load the game controller
// Reset stored axes
analog_x_axis = -1;
analog_y_axis = -1;
@@ -831,52 +1162,34 @@ public:
if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {
continue;
}
- // Simplify controller config by testing if game controller support is enabled.
if (event.type == SDL_JOYAXISMOTION) {
const auto axis = event.jaxis.axis;
- const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- const auto controller = joystick->GetSDLGameController();
- if (controller) {
- const auto axis_left_x =
- SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX)
- .value.axis;
- const auto axis_left_y =
- SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY)
- .value.axis;
- const auto axis_right_x =
- SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX)
- .value.axis;
- const auto axis_right_y =
- SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY)
- .value.axis;
-
- if (axis == axis_left_x || axis == axis_left_y) {
- analog_x_axis = axis_left_x;
- analog_y_axis = axis_left_y;
- break;
- } else if (axis == axis_right_x || axis == axis_right_y) {
- analog_x_axis = axis_right_x;
- analog_y_axis = axis_right_y;
- break;
- }
+ // In order to return a complete analog param, we need inputs for both axes.
+ // First we take the x-axis (horizontal) input, then the y-axis (vertical) input.
+ if (analog_x_axis == -1) {
+ analog_x_axis = axis;
+ } else if (analog_y_axis == -1 && analog_x_axis != axis) {
+ analog_y_axis = axis;
+ }
+ } else {
+ // If the press wasn't accepted as a joy axis, check for a button press
+ auto button_press = button_poller.FromEvent(event);
+ if (button_press) {
+ return *button_press;
}
- }
-
- // If the press wasn't accepted as a joy axis, check for a button press
- auto button_press = button_poller.FromEvent(event);
- if (button_press) {
- return *button_press;
}
}
if (analog_x_axis != -1 && analog_y_axis != -1) {
- const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
- analog_x_axis, analog_y_axis);
- analog_x_axis = -1;
- analog_y_axis = -1;
- return params;
+ if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
+ auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ analog_x_axis, analog_y_axis);
+ analog_x_axis = -1;
+ analog_y_axis = -1;
+ return params;
+ }
}
+
return {};
}
@@ -897,6 +1210,9 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
case InputCommon::Polling::DeviceType::Button:
pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
break;
+ case InputCommon::Polling::DeviceType::Motion:
+ pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
+ break;
}
return pollers;
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
index bd19ba61d..08044b00d 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/sdl/sdl_impl.h
@@ -21,6 +21,8 @@ namespace InputCommon::SDL {
class SDLAnalogFactory;
class SDLButtonFactory;
+class SDLMotionFactory;
+class SDLVibrationFactory;
class SDLJoystick;
class SDLState : public State {
@@ -71,6 +73,8 @@ private:
std::shared_ptr<SDLButtonFactory> button_factory;
std::shared_ptr<SDLAnalogFactory> analog_factory;
+ std::shared_ptr<SDLVibrationFactory> vibration_factory;
+ std::shared_ptr<SDLMotionFactory> motion_factory;
bool start_thread = false;
std::atomic<bool> initialized = false;
diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp
index 80c719cf4..557e7a9a0 100644
--- a/src/input_common/settings.cpp
+++ b/src/input_common/settings.cpp
@@ -21,6 +21,20 @@ const std::array<const char*, NumAnalogs> mapping = {{
}};
}
+namespace NativeVibration {
+const std::array<const char*, NumVibrations> mapping = {{
+ "left_vibration_device",
+ "right_vibration_device",
+}};
+}
+
+namespace NativeMotion {
+const std::array<const char*, NumMotions> mapping = {{
+ "motionleft",
+ "motionright",
+}};
+}
+
namespace NativeMouseButton {
const std::array<const char*, NumMouseButtons> mapping = {{
"left",
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
index 2d258960b..a59f5d461 100644
--- a/src/input_common/settings.h
+++ b/src/input_common/settings.h
@@ -66,6 +66,36 @@ constexpr int NUM_STICKS_HID = NumAnalogs;
extern const std::array<const char*, NumAnalogs> mapping;
} // namespace NativeAnalog
+namespace NativeVibration {
+enum Values : int {
+ LeftVibrationDevice,
+ RightVibrationDevice,
+
+ NumVibrations,
+};
+
+constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice;
+constexpr int VIBRATION_HID_END = NumVibrations;
+constexpr int NUM_VIBRATIONS_HID = NumVibrations;
+
+extern const std::array<const char*, NumVibrations> mapping;
+}; // namespace NativeVibration
+
+namespace NativeMotion {
+enum Values : int {
+ MotionLeft,
+ MotionRight,
+
+ NumMotions,
+};
+
+constexpr int MOTION_HID_BEGIN = MotionLeft;
+constexpr int MOTION_HID_END = NumMotions;
+constexpr int NUM_MOTIONS_HID = NumMotions;
+
+extern const std::array<const char*, NumMotions> mapping;
+} // namespace NativeMotion
+
namespace NativeMouseButton {
enum Values {
Left,
@@ -290,8 +320,11 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
} // namespace NativeKeyboard
-using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
+using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
+using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
+using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
+
using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
@@ -307,6 +340,7 @@ enum class ControllerType {
LeftJoycon,
RightJoycon,
Handheld,
+ GameCube,
};
struct PlayerInput {
@@ -314,8 +348,11 @@ struct PlayerInput {
ControllerType controller_type;
ButtonsRaw buttons;
AnalogsRaw analogs;
- std::string lstick_mod;
- std::string rstick_mod;
+ VibrationsRaw vibrations;
+ MotionsRaw motions;
+
+ bool vibration_enabled;
+ int vibration_strength;
u32 body_color_left;
u32 body_color_right;
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
new file mode 100644
index 000000000..ffbe4f2ed
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,52 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/framebuffer_layout.h"
+#include "core/settings.h"
+#include "input_common/touch_from_button.h"
+
+namespace InputCommon {
+
+class TouchFromButtonDevice final : public Input::TouchDevice {
+public:
+ TouchFromButtonDevice() {
+ const auto button_index =
+ static_cast<std::size_t>(Settings::values.touch_from_button_map_index);
+ const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons;
+
+ for (const auto& config_entry : buttons) {
+ const Common::ParamPackage package{config_entry};
+ map.emplace_back(
+ Input::CreateDevice<Input::ButtonDevice>(config_entry),
+ std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
+ std::clamp(package.Get("y", 0), 0,
+ static_cast<int>(Layout::ScreenUndocked::Height)));
+ }
+ }
+
+ Input::TouchStatus GetStatus() const override {
+ Input::TouchStatus touch_status{};
+ for (std::size_t id = 0; id < map.size() && id < touch_status.size(); ++id) {
+ const bool state = std::get<0>(map[id])->GetStatus();
+ if (state) {
+ const float x = static_cast<float>(std::get<1>(map[id])) /
+ static_cast<int>(Layout::ScreenUndocked::Width);
+ const float y = static_cast<float>(std::get<2>(map[id])) /
+ static_cast<int>(Layout::ScreenUndocked::Height);
+ touch_status[id] = {x, y, true};
+ }
+ }
+ return touch_status;
+ }
+
+private:
+ // A vector of the mapped button, its x and its y-coordinate
+ std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
+};
+
+std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(const Common::ParamPackage&) {
+ return std::make_unique<TouchFromButtonDevice>();
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h
new file mode 100644
index 000000000..8b4d1aa96
--- /dev/null
+++ b/src/input_common/touch_from_button.h
@@ -0,0 +1,23 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+/**
+ * A touch device factory that takes a list of button devices and combines them into a touch device.
+ */
+class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+ /**
+ * Creates a touch device from a list of button devices
+ */
+ std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index 3f4eaf448..c4afa4174 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -2,14 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
-#include <array>
#include <chrono>
#include <cstring>
#include <functional>
#include <thread>
#include <boost/asio.hpp>
#include "common/logging/log.h"
+#include "core/settings.h"
#include "input_common/udp/client.h"
#include "input_common/udp/protocol.h"
@@ -27,11 +26,11 @@ class Socket {
public:
using clock = std::chrono::system_clock;
- explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
- SocketCallback callback)
- : callback(std::move(callback)), timer(io_service),
- socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id),
- pad_index(pad_index) {
+ explicit Socket(const std::string& host, u16 port, std::size_t pad_index_, u32 client_id_,
+ SocketCallback callback_)
+ : callback(std::move(callback_)), timer(io_service),
+ socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id_),
+ pad_index(pad_index_) {
boost::system::error_code ec{};
auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
if (ec.value() != boost::system::errc::success) {
@@ -64,7 +63,7 @@ public:
}
private:
- void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
+ void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
switch (*type) {
case Type::Version: {
@@ -91,16 +90,20 @@ private:
StartReceive();
}
- void HandleSend(const boost::system::error_code& error) {
+ void HandleSend(const boost::system::error_code&) {
boost::system::error_code _ignored{};
// Send a request for getting port info for the pad
- Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
+ const Request::PortInfo port_info{1, {static_cast<u8>(pad_index), 0, 0, 0}};
const auto port_message = Request::Create(port_info, client_id);
std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
// Send a request for getting pad data for the pad
- Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
+ const Request::PadData pad_data{
+ Request::PadData::Flags::Id,
+ static_cast<u8>(pad_index),
+ EMPTY_MAC_ADDRESS,
+ };
const auto pad_message = Request::Create(pad_data, client_id);
std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
@@ -113,7 +116,7 @@ private:
udp::socket socket;
u32 client_id{};
- u8 pad_index{};
+ std::size_t pad_index{};
static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
@@ -131,92 +134,301 @@ static void SocketLoop(Socket* socket) {
socket->Loop();
}
-Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
- u8 pad_index, u32 client_id)
- : status(std::move(status)) {
- StartCommunication(host, port, pad_index, client_id);
+Client::Client() {
+ LOG_INFO(Input, "Udp Initialization started");
+ finger_id.fill(MAX_TOUCH_FINGERS);
+ ReloadSockets();
}
Client::~Client() {
- socket->Stop();
- thread.join();
+ Reset();
}
-void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
- socket->Stop();
- thread.join();
- StartCommunication(host, port, pad_index, client_id);
+Client::ClientData::ClientData() = default;
+
+Client::ClientData::~ClientData() = default;
+
+std::vector<Common::ParamPackage> Client::GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ if (!DeviceConnected(client)) {
+ continue;
+ }
+ std::string name = fmt::format("UDP Controller {}", client);
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "cemuhookudp"},
+ {"display", std::move(name)},
+ {"port", std::to_string(client)},
+ });
+ }
+ return devices;
}
-void Client::OnVersion(Response::Version data) {
+bool Client::DeviceConnected(std::size_t client) const {
+ // Use last timestamp to detect if the socket has stopped sending data
+ const auto now = std::chrono::steady_clock::now();
+ const auto time_difference =
+ static_cast<u64>(std::chrono::duration_cast<std::chrono::milliseconds>(
+ now - clients[client].last_motion_update)
+ .count());
+ return time_difference < 1000 && clients[client].active == 1;
+}
+
+void Client::ReloadSockets() {
+ Reset();
+
+ std::stringstream servers_ss(Settings::values.udp_input_servers);
+ std::string server_token;
+ std::size_t client = 0;
+ while (std::getline(servers_ss, server_token, ',')) {
+ if (client == MAX_UDP_CLIENTS) {
+ break;
+ }
+ std::stringstream server_ss(server_token);
+ std::string token;
+ std::getline(server_ss, token, ':');
+ std::string udp_input_address = token;
+ std::getline(server_ss, token, ':');
+ char* temp;
+ const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
+ if (*temp != '\0') {
+ LOG_ERROR(Input, "Port number is not valid {}", token);
+ continue;
+ }
+
+ for (std::size_t pad = 0; pad < 4; ++pad) {
+ const std::size_t client_number =
+ GetClientNumber(udp_input_address, udp_input_port, pad);
+ if (client_number != MAX_UDP_CLIENTS) {
+ LOG_ERROR(Input, "Duplicated UDP servers found");
+ continue;
+ }
+ StartCommunication(client++, udp_input_address, udp_input_port, pad, 24872);
+ }
+ }
+}
+
+std::size_t Client::GetClientNumber(std::string_view host, u16 port, std::size_t pad) const {
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ if (clients[client].active == -1) {
+ continue;
+ }
+ if (clients[client].host == host && clients[client].port == port &&
+ clients[client].pad_index == pad) {
+ return client;
+ }
+ }
+ return MAX_UDP_CLIENTS;
+}
+
+void Client::OnVersion([[maybe_unused]] Response::Version data) {
LOG_TRACE(Input, "Version packet received: {}", data.version);
}
-void Client::OnPortInfo(Response::PortInfo data) {
+void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
}
-void Client::OnPadData(Response::PadData data) {
+void Client::OnPadData(Response::PadData data, std::size_t client) {
+ // Accept packets only for the correct pad
+ if (static_cast<u8>(clients[client].pad_index) != data.info.id) {
+ return;
+ }
+
LOG_TRACE(Input, "PadData packet received");
- if (data.packet_counter <= packet_sequence) {
+ if (data.packet_counter == clients[client].packet_sequence) {
LOG_WARNING(
Input,
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
- packet_sequence, data.packet_counter);
+ clients[client].packet_sequence, data.packet_counter);
return;
}
- packet_sequence = data.packet_counter;
- // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
- // directions correspond to the ones of the Switch
- Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
- Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
+ clients[client].active = static_cast<s8>(data.info.is_pad_active);
+ clients[client].packet_sequence = data.packet_counter;
+ const auto now = std::chrono::steady_clock::now();
+ const auto time_difference =
+ static_cast<u64>(std::chrono::duration_cast<std::chrono::microseconds>(
+ now - clients[client].last_motion_update)
+ .count());
+ clients[client].last_motion_update = now;
+ const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
+ clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
+ // Gyroscope values are not it the correct scale from better joy.
+ // Dividing by 312 allows us to make one full turn = 1 turn
+ // This must be a configurable valued called sensitivity
+ clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
+ clients[client].motion.UpdateRotation(time_difference);
+ clients[client].motion.UpdateOrientation(time_difference);
+
{
- std::lock_guard guard(status->update_mutex);
+ std::lock_guard guard(clients[client].status.update_mutex);
+ clients[client].status.motion_status = clients[client].motion.GetMotion();
+
+ for (std::size_t id = 0; id < data.touch.size(); ++id) {
+ UpdateTouchInput(data.touch[id], client, id);
+ }
+
+ if (configuring) {
+ const Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
+ const Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
+ UpdateYuzuSettings(client, accelerometer, gyroscope);
+ }
+ }
+}
- status->motion_status = {accel, gyro};
+void Client::StartCommunication(std::size_t client, const std::string& host, u16 port,
+ std::size_t pad_index, u32 client_id) {
+ SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
+ [this](Response::PortInfo info) { OnPortInfo(info); },
+ [this, client](Response::PadData data) { OnPadData(data, client); }};
+ LOG_INFO(Input, "Starting communication with UDP input server on {}:{}:{}", host, port,
+ pad_index);
+ clients[client].host = host;
+ clients[client].port = port;
+ clients[client].pad_index = pad_index;
+ clients[client].active = 0;
+ clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
+ clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
+ // Set motion parameters
+ // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
+ // Real HW values are unknown, 0.0001 is an approximate to Standard
+ clients[client].motion.SetGyroThreshold(0.0001f);
+}
- // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
- // between a simple "tap" and a hard press that causes the touch screen to click.
- const bool is_active = data.touch_1.is_active != 0;
+void Client::Reset() {
+ for (auto& client : clients) {
+ if (client.thread.joinable()) {
+ client.active = -1;
+ client.socket->Stop();
+ client.thread.join();
+ }
+ }
+}
- float x = 0;
- float y = 0;
+void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
+ const Common::Vec3<float>& gyro) {
+ if (gyro.Length() > 0.2f) {
+ LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client,
+ gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]);
+ }
+ UDPPadStatus pad{
+ .host = clients[client].host,
+ .port = clients[client].port,
+ .pad_index = clients[client].pad_index,
+ };
+ for (std::size_t i = 0; i < 3; ++i) {
+ if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
+ pad.motion = static_cast<PadMotion>(i);
+ pad.motion_value = gyro[i];
+ pad_queue.Push(pad);
+ }
+ if (acc[i] > 1.75f || acc[i] < -1.75f) {
+ pad.motion = static_cast<PadMotion>(i + 3);
+ pad.motion_value = acc[i];
+ pad_queue.Push(pad);
+ }
+ }
+}
- if (is_active && status->touch_calibration) {
- const u16 min_x = status->touch_calibration->min_x;
- const u16 max_x = status->touch_calibration->max_x;
- const u16 min_y = status->touch_calibration->min_y;
- const u16 max_y = status->touch_calibration->max_y;
+std::optional<std::size_t> Client::GetUnusedFingerID() const {
+ std::size_t first_free_id = 0;
+ while (first_free_id < MAX_TOUCH_FINGERS) {
+ if (!std::get<2>(touch_status[first_free_id])) {
+ return first_free_id;
+ } else {
+ first_free_id++;
+ }
+ }
+ return std::nullopt;
+}
- x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
- static_cast<float>(max_x - min_x);
- y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
- static_cast<float>(max_y - min_y);
+void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) {
+ // TODO: Use custom calibration per device
+ const Common::ParamPackage touch_param(Settings::values.touch_device);
+ const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
+ const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
+ const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
+ const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
+ const std::size_t touch_id = client * 2 + id;
+ if (touch_pad.is_active) {
+ if (finger_id[touch_id] == MAX_TOUCH_FINGERS) {
+ const auto first_free_id = GetUnusedFingerID();
+ if (!first_free_id) {
+ // Invalid finger id skip to next input
+ return;
+ }
+ finger_id[touch_id] = *first_free_id;
}
+ auto& [x, y, pressed] = touch_status[finger_id[touch_id]];
+ x = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
+ static_cast<float>(max_x - min_x);
+ y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
+ static_cast<float>(max_y - min_y);
+ pressed = true;
+ return;
+ }
- status->touch_status = {x, y, is_active};
+ if (finger_id[touch_id] != MAX_TOUCH_FINGERS) {
+ touch_status[finger_id[touch_id]] = {};
+ finger_id[touch_id] = MAX_TOUCH_FINGERS;
}
}
-void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
- SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
- [this](Response::PortInfo info) { OnPortInfo(info); },
- [this](Response::PadData data) { OnPadData(data); }};
- LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
- socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
- thread = std::thread{SocketLoop, this->socket.get()};
+void Client::BeginConfiguration() {
+ pad_queue.Clear();
+ configuring = true;
+}
+
+void Client::EndConfiguration() {
+ pad_queue.Clear();
+ configuring = false;
+}
+
+DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) {
+ const std::size_t client_number = GetClientNumber(host, port, pad);
+ if (client_number == MAX_UDP_CLIENTS) {
+ return clients[0].status;
+ }
+ return clients[client_number].status;
+}
+
+const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const {
+ const std::size_t client_number = GetClientNumber(host, port, pad);
+ if (client_number == MAX_UDP_CLIENTS) {
+ return clients[0].status;
+ }
+ return clients[client_number].status;
+}
+
+Input::TouchStatus& Client::GetTouchState() {
+ return touch_status;
+}
+
+const Input::TouchStatus& Client::GetTouchState() const {
+ return touch_status;
+}
+
+Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() {
+ return pad_queue;
+}
+
+const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const {
+ return pad_queue;
}
-void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
- std::function<void()> success_callback,
- std::function<void()> failure_callback) {
+void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
+ const std::function<void()>& success_callback,
+ const std::function<void()>& failure_callback) {
std::thread([=] {
Common::Event success_event;
- SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
- [&](Response::PadData data) { success_event.Set(); }};
+ SocketCallback callback{
+ .version = [](Response::Version) {},
+ .port_info = [](Response::PortInfo) {},
+ .pad_data = [&](Response::PadData) { success_event.Set(); },
+ };
Socket socket{host, port, pad_index, client_id, std::move(callback)};
std::thread worker_thread{SocketLoop, &socket};
- bool result = success_event.WaitFor(std::chrono::seconds(8));
+ const bool result = success_event.WaitFor(std::chrono::seconds(5));
socket.Stop();
worker_thread.join();
if (result) {
@@ -228,7 +440,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie
}
CalibrationConfigurationJob::CalibrationConfigurationJob(
- const std::string& host, u16 port, u8 pad_index, u32 client_id,
+ const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback) {
@@ -241,31 +453,31 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
u16 max_y{};
Status current_status{Status::Initialized};
- SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
+ SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {},
[&](Response::PadData data) {
if (current_status == Status::Initialized) {
// Receiving data means the communication is ready now
current_status = Status::Ready;
status_callback(current_status);
}
- if (!data.touch_1.is_active) {
+ if (data.touch[0].is_active == 0) {
return;
}
- LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
- data.touch_1.y);
- min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
- min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
+ LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x,
+ data.touch[0].y);
+ min_x = std::min(min_x, static_cast<u16>(data.touch[0].x));
+ min_y = std::min(min_y, static_cast<u16>(data.touch[0].y));
if (current_status == Status::Ready) {
// First touch - min data (min_x/min_y)
current_status = Status::Stage1Completed;
status_callback(current_status);
}
- if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
- data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
+ if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD &&
+ data.touch[0].y - min_y > CALIBRATION_THRESHOLD) {
// Set the current position as max value and finishes
// configuration
- max_x = data.touch_1.x;
- max_y = data.touch_1.y;
+ max_x = data.touch[0].x;
+ max_y = data.touch[0].y;
current_status = Status::Completed;
data_callback(min_x, min_y, max_x, max_y);
status_callback(current_status);
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index b8c654755..a523f6124 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -12,25 +12,52 @@
#include <thread>
#include <tuple>
#include "common/common_types.h"
+#include "common/param_package.h"
#include "common/thread.h"
+#include "common/threadsafe_queue.h"
#include "common/vector_math.h"
+#include "core/frontend/input.h"
+#include "input_common/motion_input.h"
namespace InputCommon::CemuhookUDP {
-constexpr u16 DEFAULT_PORT = 26760;
-constexpr char DEFAULT_ADDR[] = "127.0.0.1";
+constexpr char DEFAULT_SRV[] = "127.0.0.1:26760";
class Socket;
namespace Response {
struct PadData;
struct PortInfo;
+struct TouchPad;
struct Version;
} // namespace Response
+enum class PadMotion {
+ GyroX,
+ GyroY,
+ GyroZ,
+ AccX,
+ AccY,
+ AccZ,
+ Undefined,
+};
+
+enum class PadTouch {
+ Click,
+ Undefined,
+};
+
+struct UDPPadStatus {
+ std::string host{"127.0.0.1"};
+ u16 port{26760};
+ std::size_t pad_index{};
+ PadMotion motion{PadMotion::Undefined};
+ f32 motion_value{0.0f};
+};
+
struct DeviceStatus {
std::mutex update_mutex;
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
+ Input::MotionStatus motion_status;
std::tuple<float, float, bool> touch_status;
// calibration data for scaling the device's touch area to 3ds
@@ -45,22 +72,81 @@ struct DeviceStatus {
class Client {
public:
- explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
- u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
+ // Initialize the UDP client capture and read sequence
+ Client();
+
+ // Close and release the client
~Client();
- void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
- u32 client_id = 24872);
+
+ // Used for polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ std::vector<Common::ParamPackage> GetInputDevices() const;
+
+ bool DeviceConnected(std::size_t client) const;
+ void ReloadSockets();
+
+ Common::SPSCQueue<UDPPadStatus>& GetPadQueue();
+ const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const;
+
+ DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad);
+ const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const;
+
+ Input::TouchStatus& GetTouchState();
+ const Input::TouchStatus& GetTouchState() const;
private:
+ struct ClientData {
+ ClientData();
+ ~ClientData();
+
+ std::string host{"127.0.0.1"};
+ u16 port{26760};
+ std::size_t pad_index{};
+ std::unique_ptr<Socket> socket;
+ DeviceStatus status;
+ std::thread thread;
+ u64 packet_sequence{};
+ s8 active{-1};
+
+ // Realtime values
+ // motion is initalized with PID values for drift correction on joycons
+ InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
+ std::chrono::time_point<std::chrono::steady_clock> last_motion_update;
+ };
+
+ // For shutting down, clear all data, join all threads, release usb
+ void Reset();
+
+ // Translates configuration to client number
+ std::size_t GetClientNumber(std::string_view host, u16 port, std::size_t pad) const;
+
void OnVersion(Response::Version);
void OnPortInfo(Response::PortInfo);
- void OnPadData(Response::PadData);
- void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
+ void OnPadData(Response::PadData, std::size_t client);
+ void StartCommunication(std::size_t client, const std::string& host, u16 port,
+ std::size_t pad_index, u32 client_id);
+ void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
+ const Common::Vec3<float>& gyro);
+
+ // Returns an unused finger id, if there is no fingers available std::nullopt will be
+ // returned
+ std::optional<std::size_t> GetUnusedFingerID() const;
+
+ // Merges and updates all touch inputs into the touch_status array
+ void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id);
+
+ bool configuring = false;
- std::unique_ptr<Socket> socket;
- std::shared_ptr<DeviceStatus> status;
- std::thread thread;
- u64 packet_sequence = 0;
+ // Allocate clients for 8 udp servers
+ static constexpr std::size_t MAX_UDP_CLIENTS = 4 * 8;
+ // Each client can have up 2 touch inputs
+ static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2;
+ std::array<ClientData, MAX_UDP_CLIENTS> clients{};
+ Common::SPSCQueue<UDPPadStatus> pad_queue{};
+ Input::TouchStatus touch_status{};
+ std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{};
};
/// An async job allowing configuration of the touchpad calibration.
@@ -78,7 +164,7 @@ public:
* @param status_callback Callback for job status updates
* @param data_callback Called when calibration data is ready
*/
- explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
+ explicit CalibrationConfigurationJob(const std::string& host, u16 port, std::size_t pad_index,
u32 client_id, std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback);
~CalibrationConfigurationJob();
@@ -88,8 +174,8 @@ private:
Common::Event complete_event;
};
-void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
- std::function<void()> success_callback,
- std::function<void()> failure_callback);
+void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
+ const std::function<void()>& success_callback,
+ const std::function<void()>& failure_callback);
} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h
index 3ba4d1fc8..a3d276697 100644
--- a/src/input_common/udp/protocol.h
+++ b/src/input_common/udp/protocol.h
@@ -7,7 +7,16 @@
#include <array>
#include <optional>
#include <type_traits>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4701)
+#endif
#include <boost/crc.hpp>
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
#include "common/bit_field.h"
#include "common/swap.h"
@@ -93,7 +102,7 @@ static_assert(std::is_trivially_copyable_v<PadData>,
/**
* Creates a message with the proper header data that can be sent to the server.
- * @param T data Request body to send
+ * @param data Request body to send
* @param client_id ID of the udp client (usually not checked on the server)
*/
template <typename T>
@@ -131,6 +140,14 @@ static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong si
static_assert(std::is_trivially_copyable_v<PortInfo>,
"UDP Response PortInfo is not trivially copyable");
+struct TouchPad {
+ u8 is_active{};
+ u8 id{};
+ u16_le x{};
+ u16_le y{};
+};
+static_assert(sizeof(TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
+
#pragma pack(push, 1)
struct PadData {
PortInfo info{};
@@ -181,12 +198,7 @@ struct PadData {
u8 button_13{};
} analog_button;
- struct TouchPad {
- u8 is_active{};
- u8 id{};
- u16_le x{};
- u16_le y{};
- } touch_1, touch_2;
+ std::array<TouchPad, 2> touch;
u64_le motion_timestamp;
@@ -213,7 +225,6 @@ static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
static_assert(sizeof(PadData::AnalogButton) == 12,
"UDP Response AnalogButton struct has wrong size ");
-static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
static_assert(sizeof(PadData::Accelerometer) == 12,
"UDP Response Accelerometer struct has wrong size ");
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 4b347e47e..9829da6f0 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -1,105 +1,110 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <mutex>
-#include <optional>
-#include <tuple>
-
-#include "common/param_package.h"
-#include "core/frontend/input.h"
-#include "core/settings.h"
+#include <utility>
+#include "common/assert.h"
+#include "common/threadsafe_queue.h"
#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
-namespace InputCommon::CemuhookUDP {
+namespace InputCommon {
-class UDPTouchDevice final : public Input::TouchDevice {
+class UDPMotion final : public Input::MotionDevice {
public:
- explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
- std::tuple<float, float, bool> GetStatus() const override {
- std::lock_guard guard(status->update_mutex);
- return status->touch_status;
+ explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
+ : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ return client->GetPadState(ip, port, pad).motion_status;
}
private:
- std::shared_ptr<DeviceStatus> status;
+ const std::string ip;
+ const u16 port;
+ const u16 pad;
+ CemuhookUDP::Client* client;
+ mutable std::mutex mutex;
};
-class UDPMotionDevice final : public Input::MotionDevice {
-public:
- explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
- std::lock_guard guard(status->update_mutex);
- return status->motion_status;
- }
+/// A motion device factory that creates motion devices from a UDP client
+UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
+ : client(std::move(client_)) {}
+
+/**
+ * Creates motion device
+ * @param params contains parameters for creating the device:
+ * - "port": the UDP port number
+ */
+std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
+ auto ip = params.Get("ip", "127.0.0.1");
+ const auto port = static_cast<u16>(params.Get("port", 26760));
+ const auto pad = static_cast<u16>(params.Get("pad_index", 0));
+
+ return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get());
+}
-private:
- std::shared_ptr<DeviceStatus> status;
-};
+void UDPMotionFactory::BeginConfiguration() {
+ polling = true;
+ client->BeginConfiguration();
+}
-class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
-public:
- explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
-
- std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
- {
- std::lock_guard guard(status->update_mutex);
- status->touch_calibration = DeviceStatus::CalibrationData{};
- // These default values work well for DS4 but probably not other touch inputs
- status->touch_calibration->min_x = params.Get("min_x", 100);
- status->touch_calibration->min_y = params.Get("min_y", 50);
- status->touch_calibration->max_x = params.Get("max_x", 1800);
- status->touch_calibration->max_y = params.Get("max_y", 850);
+void UDPMotionFactory::EndConfiguration() {
+ polling = false;
+ client->EndConfiguration();
+}
+
+Common::ParamPackage UDPMotionFactory::GetNextInput() {
+ Common::ParamPackage params;
+ CemuhookUDP::UDPPadStatus pad;
+ auto& queue = client->GetPadQueue();
+ while (queue.Pop(pad)) {
+ if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
+ continue;
}
- return std::make_unique<UDPTouchDevice>(status);
+ params.Set("engine", "cemuhookudp");
+ params.Set("ip", pad.host);
+ params.Set("port", static_cast<u16>(pad.port));
+ params.Set("pad_index", static_cast<u16>(pad.pad_index));
+ params.Set("motion", static_cast<u16>(pad.motion));
+ return params;
}
+ return params;
+}
-private:
- std::shared_ptr<DeviceStatus> status;
-};
-
-class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
+class UDPTouch final : public Input::TouchDevice {
public:
- explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+ explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
+ : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
- std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
- return std::make_unique<UDPMotionDevice>(status);
+ Input::TouchStatus GetStatus() const override {
+ return client->GetTouchState();
}
private:
- std::shared_ptr<DeviceStatus> status;
+ const std::string ip;
+ [[maybe_unused]] const u16 port;
+ [[maybe_unused]] const u16 pad;
+ CemuhookUDP::Client* client;
+ mutable std::mutex mutex;
};
-State::State() {
- auto status = std::make_shared<DeviceStatus>();
- client =
- std::make_unique<Client>(status, Settings::values.udp_input_address,
- Settings::values.udp_input_port, Settings::values.udp_pad_index);
-
- motion_factory = std::make_shared<UDPMotionFactory>(status);
- touch_factory = std::make_shared<UDPTouchFactory>(status);
-
- Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory);
- Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory);
-}
-
-State::~State() {
- Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
- Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
-}
-
-std::vector<Common::ParamPackage> State::GetInputDevices() const {
- // TODO support binding udp devices
- return {};
+/// A motion device factory that creates motion devices from a UDP client
+UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
+ : client(std::move(client_)) {}
+
+/**
+ * Creates motion device
+ * @param params contains parameters for creating the device:
+ * - "port": the UDP port number
+ */
+std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
+ auto ip = params.Get("ip", "127.0.0.1");
+ const auto port = static_cast<u16>(params.Get("port", 26760));
+ const auto pad = static_cast<u16>(params.Get("pad_index", 0));
+
+ return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get());
}
-void State::ReloadUDPClient() {
- client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
- Settings::values.udp_pad_index);
-}
-
-std::unique_ptr<State> Init() {
- return std::make_unique<State>();
-}
-} // namespace InputCommon::CemuhookUDP
+} // namespace InputCommon
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
index 672a5c812..ea3fd4175 100644
--- a/src/input_common/udp/udp.h
+++ b/src/input_common/udp/udp.h
@@ -1,32 +1,57 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
-#include <vector>
-#include "common/param_package.h"
+#include "core/frontend/input.h"
+#include "input_common/udp/client.h"
-namespace InputCommon::CemuhookUDP {
+namespace InputCommon {
-class Client;
-class UDPMotionFactory;
-class UDPTouchFactory;
-
-class State {
+/// A motion device factory that creates motion devices from udp clients
+class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
public:
- State();
- ~State();
- void ReloadUDPClient();
- std::vector<Common::ParamPackage> GetInputDevices() const;
+ explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
+
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
private:
- std::unique_ptr<Client> client;
- std::shared_ptr<UDPMotionFactory> motion_factory;
- std::shared_ptr<UDPTouchFactory> touch_factory;
+ std::shared_ptr<CemuhookUDP::Client> client;
+ bool polling = false;
};
-std::unique_ptr<State> Init();
+/// A touch device factory that creates touch devices from udp clients
+class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+ explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
+
+ std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<CemuhookUDP::Client> client;
+ bool polling = false;
+};
-} // namespace InputCommon::CemuhookUDP
+} // namespace InputCommon
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 47ef30aa9..6a5c18945 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,14 +1,11 @@
add_executable(tests
common/bit_field.cpp
- common/bit_utils.cpp
common/fibers.cpp
- common/multi_level_queue.cpp
common/param_package.cpp
common/ring_buffer.cpp
- core/arm/arm_test_common.cpp
- core/arm/arm_test_common.h
core/core_timing.cpp
tests.cpp
+ video_core/buffer_base.cpp
)
create_target_directory_groups(tests)
diff --git a/src/tests/common/bit_field.cpp b/src/tests/common/bit_field.cpp
index 8ca1889f9..182638000 100644
--- a/src/tests/common/bit_field.cpp
+++ b/src/tests/common/bit_field.cpp
@@ -68,7 +68,7 @@ TEST_CASE("BitField", "[common]") {
}});
// bit fields: 01101100111101'10101110'1011'101100
- REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100);
+ REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100U);
REQUIRE(be_bitfield.a == 0b101100);
REQUIRE(be_bitfield.b == -5); // 1011 as two's complement
REQUIRE(be_bitfield.c == TestEnum::B);
@@ -80,7 +80,7 @@ TEST_CASE("BitField", "[common]") {
be_bitfield.d.Assign(0b01010101010101);
std::memcpy(&raw, &be_bitfield, sizeof(raw));
// bit fields: 01010101010101'00001111'1111'000111
- REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111);
+ REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111U);
REQUIRE(raw == std::array<u8, 4>{{
0b01010101,
0b01010100,
diff --git a/src/tests/common/bit_utils.cpp b/src/tests/common/bit_utils.cpp
deleted file mode 100644
index 479b5995a..000000000
--- a/src/tests/common/bit_utils.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <catch2/catch.hpp>
-#include <math.h>
-#include "common/bit_util.h"
-
-namespace Common {
-
-TEST_CASE("BitUtils::CountTrailingZeroes", "[common]") {
- REQUIRE(Common::CountTrailingZeroes32(0) == 32);
- REQUIRE(Common::CountTrailingZeroes64(0) == 64);
- REQUIRE(Common::CountTrailingZeroes32(9) == 0);
- REQUIRE(Common::CountTrailingZeroes32(8) == 3);
- REQUIRE(Common::CountTrailingZeroes32(0x801000) == 12);
- REQUIRE(Common::CountTrailingZeroes64(9) == 0);
- REQUIRE(Common::CountTrailingZeroes64(8) == 3);
- REQUIRE(Common::CountTrailingZeroes64(0x801000) == 12);
- REQUIRE(Common::CountTrailingZeroes64(0x801000000000UL) == 36);
-}
-
-} // namespace Common
diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp
index 4fd92428f..d94492fc6 100644
--- a/src/tests/common/fibers.cpp
+++ b/src/tests/common/fibers.cpp
@@ -6,18 +6,40 @@
#include <cstdlib>
#include <functional>
#include <memory>
+#include <mutex>
+#include <stdexcept>
#include <thread>
#include <unordered_map>
#include <vector>
#include <catch2/catch.hpp>
-#include <math.h>
+
#include "common/common_types.h"
#include "common/fiber.h"
-#include "common/spin_lock.h"
namespace Common {
+class ThreadIds {
+public:
+ void Register(u32 id) {
+ const auto thread_id = std::this_thread::get_id();
+ std::scoped_lock lock{mutex};
+ if (ids.contains(thread_id)) {
+ throw std::logic_error{"Registering the same thread twice"};
+ }
+ ids.emplace(thread_id, id);
+ }
+
+ [[nodiscard]] u32 Get() const {
+ std::scoped_lock lock{mutex};
+ return ids.at(std::this_thread::get_id());
+ }
+
+private:
+ mutable std::mutex mutex;
+ std::unordered_map<std::thread::id, u32> ids;
+};
+
class TestControl1 {
public:
TestControl1() = default;
@@ -26,7 +48,7 @@ public:
void ExecuteThread(u32 id);
- std::unordered_map<std::thread::id, u32> ids;
+ ThreadIds thread_ids;
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
std::vector<std::shared_ptr<Common::Fiber>> work_fibers;
std::vector<u32> items;
@@ -39,8 +61,7 @@ static void WorkControl1(void* control) {
}
void TestControl1::DoWork() {
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
u32 value = items[id];
for (u32 i = 0; i < id; i++) {
value++;
@@ -50,8 +71,7 @@ void TestControl1::DoWork() {
}
void TestControl1::ExecuteThread(u32 id) {
- std::thread::id this_id = std::this_thread::get_id();
- ids[this_id] = id;
+ thread_ids.Register(id);
auto thread_fiber = Fiber::ThreadToFiber();
thread_fibers[id] = thread_fiber;
work_fibers[id] = std::make_shared<Fiber>(std::function<void(void*)>{WorkControl1}, this);
@@ -98,8 +118,7 @@ public:
value1 += i;
}
Fiber::YieldTo(fiber1, fiber3);
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
assert1 = id == 1;
value2 += 5000;
Fiber::YieldTo(fiber1, thread_fibers[id]);
@@ -115,8 +134,7 @@ public:
}
void DoWork3() {
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
assert2 = id == 0;
value1 += 1000;
Fiber::YieldTo(fiber3, thread_fibers[id]);
@@ -125,14 +143,12 @@ public:
void ExecuteThread(u32 id);
void CallFiber1() {
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
Fiber::YieldTo(thread_fibers[id], fiber1);
}
void CallFiber2() {
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
Fiber::YieldTo(thread_fibers[id], fiber2);
}
@@ -145,7 +161,7 @@ public:
u32 value2{};
std::atomic<bool> trap{true};
std::atomic<bool> trap2{true};
- std::unordered_map<std::thread::id, u32> ids;
+ ThreadIds thread_ids;
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
std::shared_ptr<Common::Fiber> fiber1;
std::shared_ptr<Common::Fiber> fiber2;
@@ -168,15 +184,13 @@ static void WorkControl2_3(void* control) {
}
void TestControl2::ExecuteThread(u32 id) {
- std::thread::id this_id = std::this_thread::get_id();
- ids[this_id] = id;
+ thread_ids.Register(id);
auto thread_fiber = Fiber::ThreadToFiber();
thread_fibers[id] = thread_fiber;
}
void TestControl2::Exit() {
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
thread_fibers[id]->Exit();
}
@@ -193,7 +207,7 @@ static void ThreadStart2_2(u32 id, TestControl2& test_control) {
}
/** This test checks for fiber thread exchange configuration and validates that fibers are
- * that a fiber has been succesfully transfered from one thread to another and that the TLS
+ * that a fiber has been successfully transferred from one thread to another and that the TLS
* region of the thread is kept while changing fibers.
*/
TEST_CASE("Fibers::InterExchange", "[common]") {
@@ -228,24 +242,21 @@ public:
void DoWork1() {
value1 += 1;
Fiber::YieldTo(fiber1, fiber2);
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
value3 += 1;
Fiber::YieldTo(fiber1, thread_fibers[id]);
}
void DoWork2() {
value2 += 1;
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
Fiber::YieldTo(fiber2, thread_fibers[id]);
}
void ExecuteThread(u32 id);
void CallFiber1() {
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
Fiber::YieldTo(thread_fibers[id], fiber1);
}
@@ -254,7 +265,7 @@ public:
u32 value1{};
u32 value2{};
u32 value3{};
- std::unordered_map<std::thread::id, u32> ids;
+ ThreadIds thread_ids;
std::vector<std::shared_ptr<Common::Fiber>> thread_fibers;
std::shared_ptr<Common::Fiber> fiber1;
std::shared_ptr<Common::Fiber> fiber2;
@@ -271,15 +282,13 @@ static void WorkControl3_2(void* control) {
}
void TestControl3::ExecuteThread(u32 id) {
- std::thread::id this_id = std::this_thread::get_id();
- ids[this_id] = id;
+ thread_ids.Register(id);
auto thread_fiber = Fiber::ThreadToFiber();
thread_fibers[id] = thread_fiber;
}
void TestControl3::Exit() {
- std::thread::id this_id = std::this_thread::get_id();
- u32 id = ids[this_id];
+ const u32 id = thread_ids.Get();
thread_fibers[id]->Exit();
}
@@ -290,7 +299,7 @@ static void ThreadStart3(u32 id, TestControl3& test_control) {
}
/** This test checks for one two threads racing for starting the same fiber.
- * It checks execution occured in an ordered manner and by no time there were
+ * It checks execution occurred in an ordered manner and by no time there were
* two contexts at the same time.
*/
TEST_CASE("Fibers::StartRace", "[common]") {
diff --git a/src/tests/common/multi_level_queue.cpp b/src/tests/common/multi_level_queue.cpp
deleted file mode 100644
index cca7ec7da..000000000
--- a/src/tests/common/multi_level_queue.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2019 Yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <catch2/catch.hpp>
-#include <math.h>
-#include "common/common_types.h"
-#include "common/multi_level_queue.h"
-
-namespace Common {
-
-TEST_CASE("MultiLevelQueue", "[common]") {
- std::array<f32, 8> values = {0.0, 5.0, 1.0, 9.0, 8.0, 2.0, 6.0, 7.0};
- Common::MultiLevelQueue<f32, 64> mlq;
- REQUIRE(mlq.empty());
- mlq.add(values[2], 2);
- mlq.add(values[7], 7);
- mlq.add(values[3], 3);
- mlq.add(values[4], 4);
- mlq.add(values[0], 0);
- mlq.add(values[5], 5);
- mlq.add(values[6], 6);
- mlq.add(values[1], 1);
- u32 index = 0;
- bool all_set = true;
- for (auto& f : mlq) {
- all_set &= (f == values[index]);
- index++;
- }
- REQUIRE(all_set);
- REQUIRE(!mlq.empty());
- f32 v = 8.0;
- mlq.add(v, 2);
- v = -7.0;
- mlq.add(v, 2, false);
- REQUIRE(mlq.front(2) == -7.0);
- mlq.yield(2);
- REQUIRE(mlq.front(2) == values[2]);
- REQUIRE(mlq.back(2) == -7.0);
- REQUIRE(mlq.empty(8));
- v = 10.0;
- mlq.add(v, 8);
- mlq.adjust(v, 8, 9);
- REQUIRE(mlq.front(9) == v);
- REQUIRE(mlq.empty(8));
- REQUIRE(!mlq.empty(9));
- mlq.adjust(values[0], 0, 9);
- REQUIRE(mlq.highest_priority_set() == 1);
- REQUIRE(mlq.lowest_priority_set() == 9);
- mlq.remove(values[1], 1);
- REQUIRE(mlq.highest_priority_set() == 2);
- REQUIRE(mlq.empty(1));
-}
-
-} // namespace Common
diff --git a/src/tests/common/ring_buffer.cpp b/src/tests/common/ring_buffer.cpp
index c883c4d56..903626e4b 100644
--- a/src/tests/common/ring_buffer.cpp
+++ b/src/tests/common/ring_buffer.cpp
@@ -14,70 +14,70 @@
namespace Common {
TEST_CASE("RingBuffer: Basic Tests", "[common]") {
- RingBuffer<char, 4, 1> buf;
+ RingBuffer<char, 4> buf;
// Pushing values into a ring buffer with space should succeed.
for (std::size_t i = 0; i < 4; i++) {
const char elem = static_cast<char>(i);
const std::size_t count = buf.Push(&elem, 1);
- REQUIRE(count == 1);
+ REQUIRE(count == 1U);
}
- REQUIRE(buf.Size() == 4);
+ REQUIRE(buf.Size() == 4U);
// Pushing values into a full ring buffer should fail.
{
const char elem = static_cast<char>(42);
const std::size_t count = buf.Push(&elem, 1);
- REQUIRE(count == 0);
+ REQUIRE(count == 0U);
}
- REQUIRE(buf.Size() == 4);
+ REQUIRE(buf.Size() == 4U);
// Popping multiple values from a ring buffer with values should succeed.
{
const std::vector<char> popped = buf.Pop(2);
- REQUIRE(popped.size() == 2);
+ REQUIRE(popped.size() == 2U);
REQUIRE(popped[0] == 0);
REQUIRE(popped[1] == 1);
}
- REQUIRE(buf.Size() == 2);
+ REQUIRE(buf.Size() == 2U);
// Popping a single value from a ring buffer with values should succeed.
{
const std::vector<char> popped = buf.Pop(1);
- REQUIRE(popped.size() == 1);
+ REQUIRE(popped.size() == 1U);
REQUIRE(popped[0] == 2);
}
- REQUIRE(buf.Size() == 1);
+ REQUIRE(buf.Size() == 1U);
// Pushing more values than space available should partially suceed.
{
std::vector<char> to_push(6);
std::iota(to_push.begin(), to_push.end(), 88);
const std::size_t count = buf.Push(to_push);
- REQUIRE(count == 3);
+ REQUIRE(count == 3U);
}
- REQUIRE(buf.Size() == 4);
+ REQUIRE(buf.Size() == 4U);
// Doing an unlimited pop should pop all values.
{
const std::vector<char> popped = buf.Pop();
- REQUIRE(popped.size() == 4);
+ REQUIRE(popped.size() == 4U);
REQUIRE(popped[0] == 3);
REQUIRE(popped[1] == 88);
REQUIRE(popped[2] == 89);
REQUIRE(popped[3] == 90);
}
- REQUIRE(buf.Size() == 0);
+ REQUIRE(buf.Size() == 0U);
}
TEST_CASE("RingBuffer: Threaded Test", "[common]") {
- RingBuffer<char, 4, 2> buf;
+ RingBuffer<char, 8> buf;
const char seed = 42;
const std::size_t count = 1000000;
std::size_t full = 0;
@@ -92,8 +92,8 @@ TEST_CASE("RingBuffer: Threaded Test", "[common]") {
std::array<char, 2> value = {seed, seed};
std::size_t i = 0;
while (i < count) {
- if (const std::size_t c = buf.Push(&value[0], 1); c > 0) {
- REQUIRE(c == 1);
+ if (const std::size_t c = buf.Push(&value[0], 2); c > 0) {
+ REQUIRE(c == 2U);
i++;
next_value(value);
} else {
@@ -107,8 +107,8 @@ TEST_CASE("RingBuffer: Threaded Test", "[common]") {
std::array<char, 2> value = {seed, seed};
std::size_t i = 0;
while (i < count) {
- if (const std::vector<char> v = buf.Pop(1); v.size() > 0) {
- REQUIRE(v.size() == 2);
+ if (const std::vector<char> v = buf.Pop(2); v.size() > 0) {
+ REQUIRE(v.size() == 2U);
REQUIRE(v[0] == value[0]);
REQUIRE(v[1] == value[1]);
i++;
@@ -123,7 +123,7 @@ TEST_CASE("RingBuffer: Threaded Test", "[common]") {
producer.join();
consumer.join();
- REQUIRE(buf.Size() == 0);
+ REQUIRE(buf.Size() == 0U);
printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty);
}
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
deleted file mode 100644
index e54674d11..000000000
--- a/src/tests/core/arm/arm_test_common.cpp
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-
-#include "common/page_table.h"
-#include "core/core.h"
-#include "core/hle/kernel/memory/page_table.h"
-#include "core/hle/kernel/process.h"
-#include "core/memory.h"
-#include "tests/core/arm/arm_test_common.h"
-
-namespace ArmTests {
-
-TestEnvironment::TestEnvironment(bool mutable_memory_)
- : mutable_memory(mutable_memory_),
- test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} {
- auto& system = Core::System::GetInstance();
-
- auto process = Kernel::Process::Create(system, "", Kernel::Process::ProcessType::Userland);
- page_table = &process->PageTable().PageTableImpl();
-
- system.Memory().MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
- system.Memory().MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
-
- kernel.MakeCurrentProcess(process.get());
-}
-
-TestEnvironment::~TestEnvironment() {
- auto& system = Core::System::GetInstance();
- system.Memory().UnmapRegion(*page_table, 0x80000000, 0x80000000);
- system.Memory().UnmapRegion(*page_table, 0x00000000, 0x80000000);
-}
-
-void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) {
- SetMemory32(vaddr + 0, static_cast<u32>(value));
- SetMemory32(vaddr + 4, static_cast<u32>(value >> 32));
-}
-
-void TestEnvironment::SetMemory32(VAddr vaddr, u32 value) {
- SetMemory16(vaddr + 0, static_cast<u16>(value));
- SetMemory16(vaddr + 2, static_cast<u16>(value >> 16));
-}
-
-void TestEnvironment::SetMemory16(VAddr vaddr, u16 value) {
- SetMemory8(vaddr + 0, static_cast<u8>(value));
- SetMemory8(vaddr + 1, static_cast<u8>(value >> 8));
-}
-
-void TestEnvironment::SetMemory8(VAddr vaddr, u8 value) {
- test_memory->data[vaddr] = value;
-}
-
-std::vector<WriteRecord> TestEnvironment::GetWriteRecords() const {
- return write_records;
-}
-
-void TestEnvironment::ClearWriteRecords() {
- write_records.clear();
-}
-
-TestEnvironment::TestMemory::~TestMemory() {}
-
-std::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
- return true;
-}
-
-std::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
- const auto iter = data.find(addr);
-
- if (iter == data.end()) {
- // Some arbitrary data
- return static_cast<u8>(addr);
- }
-
- return iter->second;
-}
-
-std::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
- return *Read8(addr) | static_cast<u16>(*Read8(addr + 1)) << 8;
-}
-
-std::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
- return *Read16(addr) | static_cast<u32>(*Read16(addr + 2)) << 16;
-}
-
-std::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
- return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32;
-}
-
-bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) {
- VAddr addr = src_addr;
- u8* data = static_cast<u8*>(dest_buffer);
-
- for (std::size_t i = 0; i < size; i++, addr++, data++) {
- *data = *Read8(addr);
- }
-
- return true;
-}
-
-bool TestEnvironment::TestMemory::Write8(VAddr addr, u8 data) {
- env->write_records.emplace_back(8, addr, data);
- if (env->mutable_memory)
- env->SetMemory8(addr, data);
- return true;
-}
-
-bool TestEnvironment::TestMemory::Write16(VAddr addr, u16 data) {
- env->write_records.emplace_back(16, addr, data);
- if (env->mutable_memory)
- env->SetMemory16(addr, data);
- return true;
-}
-
-bool TestEnvironment::TestMemory::Write32(VAddr addr, u32 data) {
- env->write_records.emplace_back(32, addr, data);
- if (env->mutable_memory)
- env->SetMemory32(addr, data);
- return true;
-}
-
-bool TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) {
- env->write_records.emplace_back(64, addr, data);
- if (env->mutable_memory)
- env->SetMemory64(addr, data);
- return true;
-}
-
-bool TestEnvironment::TestMemory::WriteBlock(VAddr dest_addr, const void* src_buffer,
- std::size_t size) {
- VAddr addr = dest_addr;
- const u8* data = static_cast<const u8*>(src_buffer);
-
- for (std::size_t i = 0; i < size; i++, addr++, data++) {
- env->write_records.emplace_back(8, addr, *data);
- if (env->mutable_memory)
- env->SetMemory8(addr, *data);
- }
-
- return true;
-}
-
-} // namespace ArmTests
diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h
deleted file mode 100644
index d145dbfcc..000000000
--- a/src/tests/core/arm/arm_test_common.h
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <tuple>
-#include <unordered_map>
-#include <vector>
-
-#include "common/common_types.h"
-#include "common/memory_hook.h"
-#include "core/hle/kernel/kernel.h"
-
-namespace Common {
-struct PageTable;
-}
-
-namespace ArmTests {
-
-struct WriteRecord {
- WriteRecord(std::size_t size, VAddr addr, u64 data) : size(size), addr(addr), data(data) {}
- std::size_t size;
- VAddr addr;
- u64 data;
- bool operator==(const WriteRecord& o) const {
- return std::tie(size, addr, data) == std::tie(o.size, o.addr, o.data);
- }
-};
-
-class TestEnvironment final {
-public:
- /*
- * Inititalise test environment
- * @param mutable_memory If false, writes to memory can never be read back.
- * (Memory is immutable.)
- */
- explicit TestEnvironment(bool mutable_memory = false);
-
- /// Shutdown test environment
- ~TestEnvironment();
-
- /// Sets value at memory location vaddr.
- void SetMemory8(VAddr vaddr, u8 value);
- void SetMemory16(VAddr vaddr, u16 value);
- void SetMemory32(VAddr vaddr, u32 value);
- void SetMemory64(VAddr vaddr, u64 value);
-
- /**
- * Whenever Memory::Write{8,16,32,64} is called within the test environment,
- * a new write-record is made.
- * @returns A vector of write records made since they were last cleared.
- */
- std::vector<WriteRecord> GetWriteRecords() const;
-
- /// Empties the internal write-record store.
- void ClearWriteRecords();
-
-private:
- friend struct TestMemory;
- struct TestMemory final : Common::MemoryHook {
- explicit TestMemory(TestEnvironment* env_) : env(env_) {}
- TestEnvironment* env;
-
- ~TestMemory() override;
-
- std::optional<bool> IsValidAddress(VAddr addr) override;
-
- std::optional<u8> Read8(VAddr addr) override;
- std::optional<u16> Read16(VAddr addr) override;
- std::optional<u32> Read32(VAddr addr) override;
- std::optional<u64> Read64(VAddr addr) override;
-
- bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) override;
-
- bool Write8(VAddr addr, u8 data) override;
- bool Write16(VAddr addr, u16 data) override;
- bool Write32(VAddr addr, u32 data) override;
- bool Write64(VAddr addr, u64 data) override;
-
- bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) override;
-
- std::unordered_map<VAddr, u8> data;
- };
-
- bool mutable_memory;
- std::shared_ptr<TestMemory> test_memory;
- std::vector<WriteRecord> write_records;
- Common::PageTable* page_table = nullptr;
- Kernel::KernelCore kernel;
-};
-
-} // namespace ArmTests
diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp
new file mode 100644
index 000000000..edced69bb
--- /dev/null
+++ b/src/tests/video_core/buffer_base.cpp
@@ -0,0 +1,549 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <stdexcept>
+#include <unordered_map>
+
+#include <catch2/catch.hpp>
+
+#include "common/alignment.h"
+#include "common/common_types.h"
+#include "video_core/buffer_cache/buffer_base.h"
+
+namespace {
+using VideoCommon::BufferBase;
+using Range = std::pair<u64, u64>;
+
+constexpr u64 PAGE = 4096;
+constexpr u64 WORD = 4096 * 64;
+
+constexpr VAddr c = 0x1328914000;
+
+class RasterizerInterface {
+public:
+ void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
+ const u64 page_start{addr >> Core::Memory::PAGE_BITS};
+ const u64 page_end{(addr + size + Core::Memory::PAGE_SIZE - 1) >> Core::Memory::PAGE_BITS};
+ for (u64 page = page_start; page < page_end; ++page) {
+ int& value = page_table[page];
+ value += delta;
+ if (value < 0) {
+ throw std::logic_error{"negative page"};
+ }
+ if (value == 0) {
+ page_table.erase(page);
+ }
+ }
+ }
+
+ [[nodiscard]] int Count(VAddr addr) const noexcept {
+ const auto it = page_table.find(addr >> Core::Memory::PAGE_BITS);
+ return it == page_table.end() ? 0 : it->second;
+ }
+
+ [[nodiscard]] unsigned Count() const noexcept {
+ unsigned count = 0;
+ for (const auto [index, value] : page_table) {
+ count += value;
+ }
+ return count;
+ }
+
+private:
+ std::unordered_map<u64, int> page_table;
+};
+} // Anonymous namespace
+
+TEST_CASE("BufferBase: Small buffer", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == WORD / PAGE);
+ REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{0, 0});
+
+ buffer.MarkRegionAsCpuModified(c + PAGE, 1);
+ REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{PAGE * 1, PAGE * 2});
+}
+
+TEST_CASE("BufferBase: Large buffer", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 32);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 32);
+ buffer.MarkRegionAsCpuModified(c + 4096, WORD * 4);
+ REQUIRE(buffer.ModifiedCpuRegion(c, WORD + PAGE * 2) == Range{PAGE, WORD + PAGE * 2});
+ REQUIRE(buffer.ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) == Range{PAGE * 2, PAGE * 8});
+ REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 4 + PAGE});
+ REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 4, PAGE) == Range{WORD * 4, WORD * 4 + PAGE});
+ REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) ==
+ Range{WORD * 3 + PAGE * 63, WORD * 4});
+
+ buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE);
+ buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
+ REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) ==
+ Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 9});
+
+ buffer.UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
+ REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) ==
+ Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 7});
+
+ buffer.MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63);
+ REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 32});
+
+ buffer.UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE);
+ buffer.UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE);
+
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 32);
+ REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{0, 0});
+}
+
+TEST_CASE("BufferBase: Rasterizer counting", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, PAGE * 2);
+ REQUIRE(rasterizer.Count() == 0);
+ buffer.UnmarkRegionAsCpuModified(c, PAGE);
+ REQUIRE(rasterizer.Count() == 1);
+ buffer.MarkRegionAsCpuModified(c, PAGE * 2);
+ REQUIRE(rasterizer.Count() == 0);
+ buffer.UnmarkRegionAsCpuModified(c, PAGE);
+ buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE);
+ REQUIRE(rasterizer.Count() == 2);
+ buffer.MarkRegionAsCpuModified(c, PAGE * 2);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("BufferBase: Basic range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ buffer.MarkRegionAsCpuModified(c, PAGE);
+ int num = 0;
+ buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == 0U);
+ REQUIRE(size == PAGE);
+ ++num;
+ });
+ REQUIRE(num == 1U);
+}
+
+TEST_CASE("BufferBase: Border upload", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 2);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
+ buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ buffer.ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) {
+ REQUIRE(offset == WORD - PAGE);
+ REQUIRE(size == PAGE * 2);
+ });
+}
+
+TEST_CASE("BufferBase: Border upload range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 2);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
+ buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ buffer.ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) {
+ REQUIRE(offset == WORD - PAGE);
+ REQUIRE(size == PAGE * 2);
+ });
+ buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ buffer.ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) {
+ REQUIRE(offset == WORD - PAGE);
+ REQUIRE(size == PAGE);
+ });
+ buffer.ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) {
+ REQUIRE(offset == WORD);
+ REQUIRE(size == PAGE);
+ });
+}
+
+TEST_CASE("BufferBase: Border upload partial range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 2);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
+ buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ buffer.ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) {
+ REQUIRE(offset == WORD - PAGE);
+ REQUIRE(size == PAGE * 2);
+ });
+ buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ buffer.ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) {
+ REQUIRE(offset == WORD - PAGE);
+ REQUIRE(size == PAGE);
+ });
+ buffer.ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) {
+ REQUIRE(offset == WORD);
+ REQUIRE(size == PAGE);
+ });
+}
+
+TEST_CASE("BufferBase: Partial word uploads", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, 0x9d000);
+ int num = 0;
+ buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == 0U);
+ REQUIRE(size == WORD);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == WORD);
+ REQUIRE(size == WORD);
+ ++num;
+ });
+ REQUIRE(num == 2);
+ buffer.ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) {
+ REQUIRE(offset == WORD * 2);
+ REQUIRE(size == PAGE * 0x1d);
+ ++num;
+ });
+ REQUIRE(num == 3);
+}
+
+TEST_CASE("BufferBase: Partial page upload", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ int num = 0;
+ buffer.MarkRegionAsCpuModified(c + PAGE * 2, PAGE);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 9, PAGE);
+ buffer.ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) {
+ REQUIRE(offset == PAGE * 2);
+ REQUIRE(size == PAGE);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ buffer.ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) {
+ REQUIRE(offset == PAGE * 9);
+ REQUIRE(size == PAGE);
+ ++num;
+ });
+ REQUIRE(num == 2);
+}
+
+TEST_CASE("BufferBase: Partial page upload with multiple words on the right") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 8);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
+ int num = 0;
+ buffer.ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) {
+ REQUIRE(offset == PAGE * 13);
+ REQUIRE(size == WORD * 7 - PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ buffer.ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) {
+ REQUIRE(offset == WORD * 7 + PAGE * 10);
+ REQUIRE(size == PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 2);
+}
+
+TEST_CASE("BufferBase: Partial page upload with multiple words on the left", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 8);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
+ int num = 0;
+ buffer.ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) {
+ REQUIRE(offset == PAGE * 16);
+ REQUIRE(size == WORD * 7 - PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ buffer.ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == PAGE * 13);
+ REQUIRE(size == PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 2);
+}
+
+TEST_CASE("BufferBase: Partial page upload with multiple words in the middle", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 8);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140);
+ int num = 0;
+ buffer.ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == PAGE * 16);
+ REQUIRE(size == WORD);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == PAGE * 13);
+ REQUIRE(size == PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 2);
+ buffer.ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) {
+ REQUIRE(offset == WORD + PAGE * 16);
+ REQUIRE(size == PAGE * 73);
+ ++num;
+ });
+ REQUIRE(num == 3);
+}
+
+TEST_CASE("BufferBase: Empty right bits", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 2048);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 2048);
+ buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ buffer.ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) {
+ REQUIRE(offset == WORD - PAGE);
+ REQUIRE(size == PAGE * 2);
+ });
+}
+
+TEST_CASE("BufferBase: Out of bound ranges 1", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ buffer.MarkRegionAsCpuModified(c, PAGE);
+ int num = 0;
+ buffer.ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; });
+ buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; });
+ buffer.ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 0);
+ buffer.ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 1);
+ buffer.MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("BufferBase: Out of bound ranges 2", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, 0x22000);
+ REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x22000, PAGE));
+ REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x28000, PAGE));
+ REQUIRE(rasterizer.Count() == 0);
+ REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100));
+ REQUIRE(rasterizer.Count() == 1);
+ REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c - 0x1000, PAGE * 2));
+ buffer.UnmarkRegionAsCpuModified(c - 0x3000, PAGE * 2);
+ buffer.UnmarkRegionAsCpuModified(c - 0x2000, PAGE * 2);
+ REQUIRE(rasterizer.Count() == 2);
+}
+
+TEST_CASE("BufferBase: Out of bound ranges 3", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, 0x310720);
+ buffer.UnmarkRegionAsCpuModified(c, 0x310720);
+ REQUIRE(rasterizer.Count(c) == 1);
+ REQUIRE(rasterizer.Count(c + PAGE) == 1);
+ REQUIRE(rasterizer.Count(c + WORD) == 1);
+ REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1);
+}
+
+TEST_CASE("BufferBase: Sparse regions 1", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 1, PAGE);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4);
+ buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
+ static constexpr std::array<u64, 2> offsets{PAGE, PAGE * 3};
+ static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4};
+ REQUIRE(offset == offsets.at(i));
+ REQUIRE(size == sizes.at(i));
+ ++i;
+ });
+}
+
+TEST_CASE("BufferBase: Sparse regions 2", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, 0x22000);
+ buffer.UnmarkRegionAsCpuModified(c, 0x22000);
+ REQUIRE(rasterizer.Count() == 0x22);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE);
+ buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
+ static constexpr std::array<u64, 2> offsets{PAGE * 0x1B, PAGE * 0x21};
+ static constexpr std::array<u64, 2> sizes{PAGE, PAGE};
+ REQUIRE(offset == offsets.at(i));
+ REQUIRE(size == sizes.at(i));
+ ++i;
+ });
+}
+
+TEST_CASE("BufferBase: Single page modified range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, PAGE);
+ REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
+ buffer.UnmarkRegionAsCpuModified(c, PAGE);
+ REQUIRE(!buffer.IsRegionCpuModified(c, PAGE));
+}
+
+TEST_CASE("BufferBase: Two page modified range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, PAGE * 2);
+ REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c, PAGE * 2));
+ buffer.UnmarkRegionAsCpuModified(c, PAGE);
+ REQUIRE(!buffer.IsRegionCpuModified(c, PAGE));
+}
+
+TEST_CASE("BufferBase: Multi word modified ranges", "[video_core]") {
+ for (int offset = 0; offset < 4; ++offset) {
+ const VAddr address = c + WORD * offset;
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, address, WORD * 4);
+ REQUIRE(buffer.IsRegionCpuModified(address, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 48, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 56, PAGE));
+
+ buffer.UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE);
+ REQUIRE(buffer.IsRegionCpuModified(address + PAGE, WORD));
+ REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE));
+ REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 33, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE * 2));
+ REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
+
+ buffer.UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE);
+ REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
+ }
+}
+
+TEST_CASE("BufferBase: Single page in large buffer", "[video_core]") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 16);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 16);
+ REQUIRE(!buffer.IsRegionCpuModified(c, WORD * 16));
+
+ buffer.MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE);
+ REQUIRE(buffer.IsRegionCpuModified(c, WORD * 16));
+ REQUIRE(buffer.IsRegionCpuModified(c + WORD * 10, WORD * 2));
+ REQUIRE(buffer.IsRegionCpuModified(c + WORD * 11, WORD * 2));
+ REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12, WORD * 2));
+ REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8));
+ REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8));
+ REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2));
+ REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2));
+}
+
+TEST_CASE("BufferBase: Out of bounds region query") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 16);
+ REQUIRE(!buffer.IsRegionCpuModified(c - PAGE, PAGE));
+ REQUIRE(!buffer.IsRegionCpuModified(c - PAGE * 2, PAGE));
+ REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + WORD * 16 - PAGE, WORD * 64));
+ REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, WORD * 64));
+}
+
+TEST_CASE("BufferBase: Wrap word regions") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD * 2);
+ buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
+ buffer.MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2);
+ REQUIRE(buffer.IsRegionCpuModified(c, WORD * 2));
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 62, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 64, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 2));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 8));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 60, PAGE * 8));
+
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16));
+ buffer.MarkRegionAsCpuModified(c + PAGE * 127, PAGE);
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, PAGE));
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 126, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 126, PAGE * 2));
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 128, WORD * 16));
+}
+
+TEST_CASE("BufferBase: Unaligned page region query") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ buffer.MarkRegionAsCpuModified(c + 4000, 1000);
+ REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1000));
+ REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1));
+}
+
+TEST_CASE("BufferBase: Cached write") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ buffer.CachedCpuWrite(c + PAGE, PAGE);
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ buffer.FlushCachedWrites();
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ buffer.MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("BufferBase: Multiple cached write") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ buffer.CachedCpuWrite(c + PAGE, PAGE);
+ buffer.CachedCpuWrite(c + PAGE * 3, PAGE);
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 3, PAGE));
+ buffer.FlushCachedWrites();
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 3, PAGE));
+ buffer.MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("BufferBase: Cached write unmarked") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ buffer.CachedCpuWrite(c + PAGE, PAGE);
+ buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE);
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ buffer.FlushCachedWrites();
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ buffer.MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("BufferBase: Cached write iterated") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ buffer.CachedCpuWrite(c + PAGE, PAGE);
+ int num = 0;
+ buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 0);
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ buffer.FlushCachedWrites();
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ buffer.MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("BufferBase: Cached write downloads") {
+ RasterizerInterface rasterizer;
+ BufferBase buffer(rasterizer, c, WORD);
+ buffer.UnmarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 64);
+ buffer.CachedCpuWrite(c + PAGE, PAGE);
+ REQUIRE(rasterizer.Count() == 63);
+ buffer.MarkRegionAsGpuModified(c + PAGE, PAGE);
+ int num = 0;
+ buffer.ForEachDownloadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
+ buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 0);
+ REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
+ buffer.FlushCachedWrites();
+ REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
+ buffer.MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index d85f1e9d1..9b931976a 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,12 +1,30 @@
add_subdirectory(host_shaders)
add_library(video_core STATIC
- buffer_cache/buffer_block.h
+ buffer_cache/buffer_base.h
+ buffer_cache/buffer_cache.cpp
buffer_cache/buffer_cache.h
- buffer_cache/map_interval.cpp
- buffer_cache/map_interval.h
+ cdma_pusher.cpp
+ cdma_pusher.h
+ command_classes/codecs/codec.cpp
+ command_classes/codecs/codec.h
+ command_classes/codecs/h264.cpp
+ command_classes/codecs/h264.h
+ command_classes/codecs/vp9.cpp
+ command_classes/codecs/vp9.h
+ command_classes/codecs/vp9_types.h
+ command_classes/host1x.cpp
+ command_classes/host1x.h
+ command_classes/nvdec.cpp
+ command_classes/nvdec.h
+ command_classes/nvdec_common.h
+ command_classes/sync_manager.cpp
+ command_classes/sync_manager.h
+ command_classes/vic.cpp
+ command_classes/vic.h
compatible_formats.cpp
compatible_formats.h
+ delayed_destruction_ring.h
dirty_flags.cpp
dirty_flags.h
dma_pusher.cpp
@@ -29,6 +47,7 @@ add_library(video_core STATIC
engines/shader_bytecode.h
engines/shader_header.h
engines/shader_type.h
+ framebuffer_config.h
macro/macro.cpp
macro/macro.h
macro/macro_hle.cpp
@@ -40,18 +59,12 @@ add_library(video_core STATIC
fence_manager.h
gpu.cpp
gpu.h
- gpu_asynch.cpp
- gpu_asynch.h
- gpu_synch.cpp
- gpu_synch.h
gpu_thread.cpp
gpu_thread.h
guest_driver.cpp
guest_driver.h
memory_manager.cpp
memory_manager.h
- morton.cpp
- morton.h
query_cache.h
rasterizer_accelerated.cpp
rasterizer_accelerated.h
@@ -66,14 +79,10 @@ add_library(video_core STATIC
renderer_opengl/gl_device.h
renderer_opengl/gl_fence_manager.cpp
renderer_opengl/gl_fence_manager.h
- renderer_opengl/gl_framebuffer_cache.cpp
- renderer_opengl/gl_framebuffer_cache.h
renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h
renderer_opengl/gl_resource_manager.cpp
renderer_opengl/gl_resource_manager.h
- renderer_opengl/gl_sampler_cache.cpp
- renderer_opengl/gl_sampler_cache.h
renderer_opengl/gl_shader_cache.cpp
renderer_opengl/gl_shader_cache.h
renderer_opengl/gl_shader_decompiler.cpp
@@ -95,10 +104,58 @@ add_library(video_core STATIC
renderer_opengl/maxwell_to_gl.h
renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h
- renderer_opengl/utils.cpp
- renderer_opengl/utils.h
- sampler_cache.cpp
- sampler_cache.h
+ renderer_opengl/util_shaders.cpp
+ renderer_opengl/util_shaders.h
+ renderer_vulkan/blit_image.cpp
+ renderer_vulkan/blit_image.h
+ renderer_vulkan/fixed_pipeline_state.cpp
+ renderer_vulkan/fixed_pipeline_state.h
+ renderer_vulkan/maxwell_to_vk.cpp
+ renderer_vulkan/maxwell_to_vk.h
+ renderer_vulkan/renderer_vulkan.h
+ renderer_vulkan/renderer_vulkan.cpp
+ renderer_vulkan/vk_blit_screen.cpp
+ renderer_vulkan/vk_blit_screen.h
+ renderer_vulkan/vk_buffer_cache.cpp
+ renderer_vulkan/vk_buffer_cache.h
+ renderer_vulkan/vk_command_pool.cpp
+ renderer_vulkan/vk_command_pool.h
+ renderer_vulkan/vk_compute_pass.cpp
+ renderer_vulkan/vk_compute_pass.h
+ renderer_vulkan/vk_compute_pipeline.cpp
+ renderer_vulkan/vk_compute_pipeline.h
+ renderer_vulkan/vk_descriptor_pool.cpp
+ renderer_vulkan/vk_descriptor_pool.h
+ renderer_vulkan/vk_fence_manager.cpp
+ renderer_vulkan/vk_fence_manager.h
+ renderer_vulkan/vk_graphics_pipeline.cpp
+ renderer_vulkan/vk_graphics_pipeline.h
+ renderer_vulkan/vk_master_semaphore.cpp
+ renderer_vulkan/vk_master_semaphore.h
+ renderer_vulkan/vk_pipeline_cache.cpp
+ renderer_vulkan/vk_pipeline_cache.h
+ renderer_vulkan/vk_query_cache.cpp
+ renderer_vulkan/vk_query_cache.h
+ renderer_vulkan/vk_rasterizer.cpp
+ renderer_vulkan/vk_rasterizer.h
+ renderer_vulkan/vk_resource_pool.cpp
+ renderer_vulkan/vk_resource_pool.h
+ renderer_vulkan/vk_scheduler.cpp
+ renderer_vulkan/vk_scheduler.h
+ renderer_vulkan/vk_shader_decompiler.cpp
+ renderer_vulkan/vk_shader_decompiler.h
+ renderer_vulkan/vk_shader_util.cpp
+ renderer_vulkan/vk_shader_util.h
+ renderer_vulkan/vk_staging_buffer_pool.cpp
+ renderer_vulkan/vk_staging_buffer_pool.h
+ renderer_vulkan/vk_state_tracker.cpp
+ renderer_vulkan/vk_state_tracker.h
+ renderer_vulkan/vk_swapchain.cpp
+ renderer_vulkan/vk_swapchain.h
+ renderer_vulkan/vk_texture_cache.cpp
+ renderer_vulkan/vk_texture_cache.h
+ renderer_vulkan/vk_update_descriptor.cpp
+ renderer_vulkan/vk_update_descriptor.h
shader_cache.h
shader_notify.cpp
shader_notify.h
@@ -155,119 +212,104 @@ add_library(video_core STATIC
shader/transform_feedback.h
surface.cpp
surface.h
+ texture_cache/accelerated_swizzle.cpp
+ texture_cache/accelerated_swizzle.h
+ texture_cache/decode_bc4.cpp
+ texture_cache/decode_bc4.h
+ texture_cache/descriptor_table.h
+ texture_cache/formatter.cpp
+ texture_cache/formatter.h
texture_cache/format_lookup_table.cpp
texture_cache/format_lookup_table.h
- texture_cache/surface_base.cpp
- texture_cache/surface_base.h
- texture_cache/surface_params.cpp
- texture_cache/surface_params.h
- texture_cache/surface_view.cpp
- texture_cache/surface_view.h
+ texture_cache/image_base.cpp
+ texture_cache/image_base.h
+ texture_cache/image_info.cpp
+ texture_cache/image_info.h
+ texture_cache/image_view_base.cpp
+ texture_cache/image_view_base.h
+ texture_cache/image_view_info.cpp
+ texture_cache/image_view_info.h
+ texture_cache/render_targets.h
+ texture_cache/samples_helper.h
+ texture_cache/slot_vector.h
texture_cache/texture_cache.h
+ texture_cache/types.h
+ texture_cache/util.cpp
+ texture_cache/util.h
textures/astc.cpp
textures/astc.h
- textures/convert.cpp
- textures/convert.h
textures/decoders.cpp
textures/decoders.h
textures/texture.cpp
textures/texture.h
video_core.cpp
video_core.h
+ vulkan_common/vulkan_debug_callback.cpp
+ vulkan_common/vulkan_debug_callback.h
+ vulkan_common/vulkan_device.cpp
+ vulkan_common/vulkan_device.h
+ vulkan_common/vulkan_instance.cpp
+ vulkan_common/vulkan_instance.h
+ vulkan_common/vulkan_library.cpp
+ vulkan_common/vulkan_library.h
+ vulkan_common/vulkan_memory_allocator.cpp
+ vulkan_common/vulkan_memory_allocator.h
+ vulkan_common/vulkan_surface.cpp
+ vulkan_common/vulkan_surface.h
+ vulkan_common/vulkan_wrapper.cpp
+ vulkan_common/vulkan_wrapper.h
+ vulkan_common/nsight_aftermath_tracker.cpp
+ vulkan_common/nsight_aftermath_tracker.h
)
-if (ENABLE_VULKAN)
- target_sources(video_core PRIVATE
- renderer_vulkan/fixed_pipeline_state.cpp
- renderer_vulkan/fixed_pipeline_state.h
- renderer_vulkan/maxwell_to_vk.cpp
- renderer_vulkan/maxwell_to_vk.h
- renderer_vulkan/nsight_aftermath_tracker.cpp
- renderer_vulkan/nsight_aftermath_tracker.h
- renderer_vulkan/renderer_vulkan.h
- renderer_vulkan/renderer_vulkan.cpp
- renderer_vulkan/vk_blit_screen.cpp
- renderer_vulkan/vk_blit_screen.h
- renderer_vulkan/vk_buffer_cache.cpp
- renderer_vulkan/vk_buffer_cache.h
- renderer_vulkan/vk_compute_pass.cpp
- renderer_vulkan/vk_compute_pass.h
- renderer_vulkan/vk_compute_pipeline.cpp
- renderer_vulkan/vk_compute_pipeline.h
- renderer_vulkan/vk_descriptor_pool.cpp
- renderer_vulkan/vk_descriptor_pool.h
- renderer_vulkan/vk_device.cpp
- renderer_vulkan/vk_device.h
- renderer_vulkan/vk_fence_manager.cpp
- renderer_vulkan/vk_fence_manager.h
- renderer_vulkan/vk_graphics_pipeline.cpp
- renderer_vulkan/vk_graphics_pipeline.h
- renderer_vulkan/vk_image.cpp
- renderer_vulkan/vk_image.h
- renderer_vulkan/vk_memory_manager.cpp
- renderer_vulkan/vk_memory_manager.h
- renderer_vulkan/vk_pipeline_cache.cpp
- renderer_vulkan/vk_pipeline_cache.h
- renderer_vulkan/vk_query_cache.cpp
- renderer_vulkan/vk_query_cache.h
- renderer_vulkan/vk_rasterizer.cpp
- renderer_vulkan/vk_rasterizer.h
- renderer_vulkan/vk_renderpass_cache.cpp
- renderer_vulkan/vk_renderpass_cache.h
- renderer_vulkan/vk_resource_manager.cpp
- renderer_vulkan/vk_resource_manager.h
- renderer_vulkan/vk_sampler_cache.cpp
- renderer_vulkan/vk_sampler_cache.h
- renderer_vulkan/vk_scheduler.cpp
- renderer_vulkan/vk_scheduler.h
- renderer_vulkan/vk_shader_decompiler.cpp
- renderer_vulkan/vk_shader_decompiler.h
- renderer_vulkan/vk_shader_util.cpp
- renderer_vulkan/vk_shader_util.h
- renderer_vulkan/vk_staging_buffer_pool.cpp
- renderer_vulkan/vk_staging_buffer_pool.h
- renderer_vulkan/vk_state_tracker.cpp
- renderer_vulkan/vk_state_tracker.h
- renderer_vulkan/vk_stream_buffer.cpp
- renderer_vulkan/vk_stream_buffer.h
- renderer_vulkan/vk_swapchain.cpp
- renderer_vulkan/vk_swapchain.h
- renderer_vulkan/vk_texture_cache.cpp
- renderer_vulkan/vk_texture_cache.h
- renderer_vulkan/vk_update_descriptor.cpp
- renderer_vulkan/vk_update_descriptor.h
- renderer_vulkan/wrapper.cpp
- renderer_vulkan/wrapper.h
- )
-endif()
-
create_target_directory_groups(video_core)
target_link_libraries(video_core PUBLIC common core)
target_link_libraries(video_core PRIVATE glad xbyak)
+if (YUZU_USE_BUNDLED_FFMPEG AND NOT WIN32)
+ add_dependencies(video_core ffmpeg-build)
+endif()
+
+target_include_directories(video_core PRIVATE ${FFmpeg_INCLUDE_DIR})
+target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
+
add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
-
-if (ENABLE_VULKAN)
- target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
- target_compile_definitions(video_core PRIVATE HAS_VULKAN)
- target_link_libraries(video_core PRIVATE sirit)
-endif()
+target_include_directories(video_core PRIVATE sirit ../../externals/Vulkan-Headers/include)
+target_link_libraries(video_core PRIVATE sirit)
if (ENABLE_NSIGHT_AFTERMATH)
if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
- message(ERROR "Environment variable NSIGHT_AFTERMATH_SDK has to be provided")
+ message(FATAL_ERROR "Environment variable NSIGHT_AFTERMATH_SDK has to be provided")
endif()
if (NOT WIN32)
- message(ERROR "Nsight Aftermath doesn't support non-Windows platforms")
+ message(FATAL_ERROR "Nsight Aftermath doesn't support non-Windows platforms")
endif()
target_compile_definitions(video_core PRIVATE HAS_NSIGHT_AFTERMATH)
target_include_directories(video_core PRIVATE "$ENV{NSIGHT_AFTERMATH_SDK}/include")
endif()
if (MSVC)
- target_compile_options(video_core PRIVATE /we4267)
+ target_compile_options(video_core PRIVATE
+ /we4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data
+ /we4456 # Declaration of 'identifier' hides previous local declaration
+ /we4457 # Declaration of 'identifier' hides function parameter
+ /we4458 # Declaration of 'identifier' hides class member
+ /we4459 # Declaration of 'identifier' hides global declaration
+ /we4715 # 'function' : not all control paths return a value
+ )
else()
- target_compile_options(video_core PRIVATE -Werror=conversion -Wno-error=sign-conversion)
+ target_compile_options(video_core PRIVATE
+ -Werror=conversion
+ -Wno-error=sign-conversion
+ -Werror=pessimizing-move
+ -Werror=redundant-move
+ -Werror=shadow
+ -Werror=type-limits
+
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=class-memaccess>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
+ $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
+ )
endif()
diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h
new file mode 100644
index 000000000..0c00ae280
--- /dev/null
+++ b/src/video_core/buffer_cache/buffer_base.h
@@ -0,0 +1,590 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <bit>
+#include <limits>
+#include <utility>
+
+#include "common/alignment.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/div_ceil.h"
+#include "core/memory.h"
+
+namespace VideoCommon {
+
+enum class BufferFlagBits {
+ Picked = 1 << 0,
+ CachedWrites = 1 << 1,
+};
+DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits)
+
+/// Tag for creating null buffers with no storage or size
+struct NullBufferParams {};
+
+/**
+ * Range tracking buffer container.
+ *
+ * It keeps track of the modified CPU and GPU ranges on a CPU page granularity, notifying the given
+ * rasterizer about state changes in the tracking behavior of the buffer.
+ *
+ * The buffer size and address is forcefully aligned to CPU page boundaries.
+ */
+template <class RasterizerInterface>
+class BufferBase {
+ static constexpr u64 PAGES_PER_WORD = 64;
+ static constexpr u64 BYTES_PER_PAGE = Core::Memory::PAGE_SIZE;
+ static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
+
+ /// Vector tracking modified pages tightly packed with small vector optimization
+ union WordsArray {
+ /// Returns the pointer to the words state
+ [[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
+ return is_short ? &stack : heap;
+ }
+
+ /// Returns the pointer to the words state
+ [[nodiscard]] u64* Pointer(bool is_short) noexcept {
+ return is_short ? &stack : heap;
+ }
+
+ u64 stack = 0; ///< Small buffers storage
+ u64* heap; ///< Not-small buffers pointer to the storage
+ };
+
+ struct Words {
+ explicit Words() = default;
+ explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
+ if (IsShort()) {
+ cpu.stack = ~u64{0};
+ gpu.stack = 0;
+ cached_cpu.stack = 0;
+ untracked.stack = ~u64{0};
+ } else {
+ // Share allocation between CPU and GPU pages and set their default values
+ const size_t num_words = NumWords();
+ u64* const alloc = new u64[num_words * 4];
+ cpu.heap = alloc;
+ gpu.heap = alloc + num_words;
+ cached_cpu.heap = alloc + num_words * 2;
+ untracked.heap = alloc + num_words * 3;
+ std::fill_n(cpu.heap, num_words, ~u64{0});
+ std::fill_n(gpu.heap, num_words, 0);
+ std::fill_n(cached_cpu.heap, num_words, 0);
+ std::fill_n(untracked.heap, num_words, ~u64{0});
+ }
+ // Clean up tailing bits
+ const u64 last_word_size = size_bytes % BYTES_PER_WORD;
+ const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
+ const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
+ const u64 last_word = (~u64{0} << shift) >> shift;
+ cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
+ untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
+ }
+
+ ~Words() {
+ Release();
+ }
+
+ Words& operator=(Words&& rhs) noexcept {
+ Release();
+ size_bytes = rhs.size_bytes;
+ cpu = rhs.cpu;
+ gpu = rhs.gpu;
+ cached_cpu = rhs.cached_cpu;
+ untracked = rhs.untracked;
+ rhs.cpu.heap = nullptr;
+ return *this;
+ }
+
+ Words(Words&& rhs) noexcept
+ : size_bytes{rhs.size_bytes}, cpu{rhs.cpu}, gpu{rhs.gpu},
+ cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked} {
+ rhs.cpu.heap = nullptr;
+ }
+
+ Words& operator=(const Words&) = delete;
+ Words(const Words&) = delete;
+
+ /// Returns true when the buffer fits in the small vector optimization
+ [[nodiscard]] bool IsShort() const noexcept {
+ return size_bytes <= BYTES_PER_WORD;
+ }
+
+ /// Returns the number of words of the buffer
+ [[nodiscard]] size_t NumWords() const noexcept {
+ return Common::DivCeil(size_bytes, BYTES_PER_WORD);
+ }
+
+ /// Release buffer resources
+ void Release() {
+ if (!IsShort()) {
+ // CPU written words is the base for the heap allocation
+ delete[] cpu.heap;
+ }
+ }
+
+ u64 size_bytes = 0;
+ WordsArray cpu;
+ WordsArray gpu;
+ WordsArray cached_cpu;
+ WordsArray untracked;
+ };
+
+ enum class Type {
+ CPU,
+ GPU,
+ CachedCPU,
+ Untracked,
+ };
+
+public:
+ explicit BufferBase(RasterizerInterface& rasterizer_, VAddr cpu_addr_, u64 size_bytes)
+ : rasterizer{&rasterizer_}, cpu_addr{Common::AlignDown(cpu_addr_, BYTES_PER_PAGE)},
+ words(Common::AlignUp(size_bytes + (cpu_addr_ - cpu_addr), BYTES_PER_PAGE)) {}
+
+ explicit BufferBase(NullBufferParams) {}
+
+ BufferBase& operator=(const BufferBase&) = delete;
+ BufferBase(const BufferBase&) = delete;
+
+ BufferBase& operator=(BufferBase&&) = default;
+ BufferBase(BufferBase&&) = default;
+
+ /// Returns the inclusive CPU modified range in a begin end pair
+ [[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
+ u64 query_size) const noexcept {
+ const u64 offset = query_cpu_addr - cpu_addr;
+ return ModifiedRegion<Type::CPU>(offset, query_size);
+ }
+
+ /// Returns the inclusive GPU modified range in a begin end pair
+ [[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
+ u64 query_size) const noexcept {
+ const u64 offset = query_cpu_addr - cpu_addr;
+ return ModifiedRegion<Type::GPU>(offset, query_size);
+ }
+
+ /// Returns true if a region has been modified from the CPU
+ [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept {
+ const u64 offset = query_cpu_addr - cpu_addr;
+ return IsRegionModified<Type::CPU>(offset, query_size);
+ }
+
+ /// Returns true if a region has been modified from the GPU
+ [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept {
+ const u64 offset = query_cpu_addr - cpu_addr;
+ return IsRegionModified<Type::GPU>(offset, query_size);
+ }
+
+ /// Mark region as CPU modified, notifying the rasterizer about this change
+ void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) {
+ ChangeRegionState<Type::CPU, true>(dirty_cpu_addr, size);
+ }
+
+ /// Unmark region as CPU modified, notifying the rasterizer about this change
+ void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) {
+ ChangeRegionState<Type::CPU, false>(dirty_cpu_addr, size);
+ }
+
+ /// Mark region as modified from the host GPU
+ void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept {
+ ChangeRegionState<Type::GPU, true>(dirty_cpu_addr, size);
+ }
+
+ /// Unmark region as modified from the host GPU
+ void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept {
+ ChangeRegionState<Type::GPU, false>(dirty_cpu_addr, size);
+ }
+
+ /// Mark region as modified from the CPU
+ /// but don't mark it as modified until FlusHCachedWrites is called.
+ void CachedCpuWrite(VAddr dirty_cpu_addr, u64 size) {
+ flags |= BufferFlagBits::CachedWrites;
+ ChangeRegionState<Type::CachedCPU, true>(dirty_cpu_addr, size);
+ }
+
+ /// Flushes cached CPU writes, and notify the rasterizer about the deltas
+ void FlushCachedWrites() noexcept {
+ flags &= ~BufferFlagBits::CachedWrites;
+ const u64 num_words = NumWords();
+ const u64* const cached_words = Array<Type::CachedCPU>();
+ u64* const untracked_words = Array<Type::Untracked>();
+ u64* const cpu_words = Array<Type::CPU>();
+ for (u64 word_index = 0; word_index < num_words; ++word_index) {
+ const u64 cached_bits = cached_words[word_index];
+ NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
+ untracked_words[word_index] |= cached_bits;
+ cpu_words[word_index] |= cached_bits;
+ }
+ }
+
+ /// Call 'func' for each CPU modified range and unmark those pages as CPU modified
+ template <typename Func>
+ void ForEachUploadRange(VAddr query_cpu_range, u64 size, Func&& func) {
+ ForEachModifiedRange<Type::CPU>(query_cpu_range, size, func);
+ }
+
+ /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
+ template <typename Func>
+ void ForEachDownloadRange(VAddr query_cpu_range, u64 size, Func&& func) {
+ ForEachModifiedRange<Type::GPU>(query_cpu_range, size, func);
+ }
+
+ /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
+ template <typename Func>
+ void ForEachDownloadRange(Func&& func) {
+ ForEachModifiedRange<Type::GPU>(cpu_addr, SizeBytes(), func);
+ }
+
+ /// Mark buffer as picked
+ void Pick() noexcept {
+ flags |= BufferFlagBits::Picked;
+ }
+
+ /// Unmark buffer as picked
+ void Unpick() noexcept {
+ flags &= ~BufferFlagBits::Picked;
+ }
+
+ /// Increases the likeliness of this being a stream buffer
+ void IncreaseStreamScore(int score) noexcept {
+ stream_score += score;
+ }
+
+ /// Returns the likeliness of this being a stream buffer
+ [[nodiscard]] int StreamScore() const noexcept {
+ return stream_score;
+ }
+
+ /// Returns true when vaddr -> vaddr+size is fully contained in the buffer
+ [[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept {
+ return addr >= cpu_addr && addr + size <= cpu_addr + SizeBytes();
+ }
+
+ /// Returns true if the buffer has been marked as picked
+ [[nodiscard]] bool IsPicked() const noexcept {
+ return True(flags & BufferFlagBits::Picked);
+ }
+
+ /// Returns true when the buffer has pending cached writes
+ [[nodiscard]] bool HasCachedWrites() const noexcept {
+ return True(flags & BufferFlagBits::CachedWrites);
+ }
+
+ /// Returns the base CPU address of the buffer
+ [[nodiscard]] VAddr CpuAddr() const noexcept {
+ return cpu_addr;
+ }
+
+ /// Returns the offset relative to the given CPU address
+ /// @pre IsInBounds returns true
+ [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept {
+ return static_cast<u32>(other_cpu_addr - cpu_addr);
+ }
+
+ /// Returns the size in bytes of the buffer
+ [[nodiscard]] u64 SizeBytes() const noexcept {
+ return words.size_bytes;
+ }
+
+private:
+ template <Type type>
+ u64* Array() noexcept {
+ if constexpr (type == Type::CPU) {
+ return words.cpu.Pointer(IsShort());
+ } else if constexpr (type == Type::GPU) {
+ return words.gpu.Pointer(IsShort());
+ } else if constexpr (type == Type::CachedCPU) {
+ return words.cached_cpu.Pointer(IsShort());
+ } else if constexpr (type == Type::Untracked) {
+ return words.untracked.Pointer(IsShort());
+ }
+ }
+
+ template <Type type>
+ const u64* Array() const noexcept {
+ if constexpr (type == Type::CPU) {
+ return words.cpu.Pointer(IsShort());
+ } else if constexpr (type == Type::GPU) {
+ return words.gpu.Pointer(IsShort());
+ } else if constexpr (type == Type::CachedCPU) {
+ return words.cached_cpu.Pointer(IsShort());
+ } else if constexpr (type == Type::Untracked) {
+ return words.untracked.Pointer(IsShort());
+ }
+ }
+
+ /**
+ * Change the state of a range of pages
+ *
+ * @param dirty_addr Base address to mark or unmark as modified
+ * @param size Size in bytes to mark or unmark as modified
+ */
+ template <Type type, bool enable>
+ void ChangeRegionState(u64 dirty_addr, s64 size) noexcept(type == Type::GPU) {
+ const s64 difference = dirty_addr - cpu_addr;
+ const u64 offset = std::max<s64>(difference, 0);
+ size += std::min<s64>(difference, 0);
+ if (offset >= SizeBytes() || size < 0) {
+ return;
+ }
+ u64* const untracked_words = Array<Type::Untracked>();
+ u64* const state_words = Array<type>();
+ const u64 offset_end = std::min(offset + size, SizeBytes());
+ const u64 begin_page_index = offset / BYTES_PER_PAGE;
+ const u64 begin_word_index = begin_page_index / PAGES_PER_WORD;
+ const u64 end_page_index = Common::DivCeil(offset_end, BYTES_PER_PAGE);
+ const u64 end_word_index = Common::DivCeil(end_page_index, PAGES_PER_WORD);
+ u64 page_index = begin_page_index % PAGES_PER_WORD;
+ u64 word_index = begin_word_index;
+ while (word_index < end_word_index) {
+ const u64 next_word_first_page = (word_index + 1) * PAGES_PER_WORD;
+ const u64 left_offset =
+ std::min(next_word_first_page - end_page_index, PAGES_PER_WORD) % PAGES_PER_WORD;
+ const u64 right_offset = page_index;
+ u64 bits = ~u64{0};
+ bits = (bits >> right_offset) << right_offset;
+ bits = (bits << left_offset) >> left_offset;
+ if constexpr (type == Type::CPU || type == Type::CachedCPU) {
+ NotifyRasterizer<!enable>(word_index, untracked_words[word_index], bits);
+ }
+ if constexpr (enable) {
+ state_words[word_index] |= bits;
+ if constexpr (type == Type::CPU || type == Type::CachedCPU) {
+ untracked_words[word_index] |= bits;
+ }
+ } else {
+ state_words[word_index] &= ~bits;
+ if constexpr (type == Type::CPU || type == Type::CachedCPU) {
+ untracked_words[word_index] &= ~bits;
+ }
+ }
+ page_index = 0;
+ ++word_index;
+ }
+ }
+
+ /**
+ * Notify rasterizer about changes in the CPU tracking state of a word in the buffer
+ *
+ * @param word_index Index to the word to notify to the rasterizer
+ * @param current_bits Current state of the word
+ * @param new_bits New state of the word
+ *
+ * @tparam add_to_rasterizer True when the rasterizer should start tracking the new pages
+ */
+ template <bool add_to_rasterizer>
+ void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
+ u64 changed_bits = (add_to_rasterizer ? current_bits : ~current_bits) & new_bits;
+ VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
+ while (changed_bits != 0) {
+ const int empty_bits = std::countr_zero(changed_bits);
+ addr += empty_bits * BYTES_PER_PAGE;
+ changed_bits >>= empty_bits;
+
+ const u32 continuous_bits = std::countr_one(changed_bits);
+ const u64 size = continuous_bits * BYTES_PER_PAGE;
+ const VAddr begin_addr = addr;
+ addr += size;
+ changed_bits = continuous_bits < PAGES_PER_WORD ? (changed_bits >> continuous_bits) : 0;
+ rasterizer->UpdatePagesCachedCount(begin_addr, size, add_to_rasterizer ? 1 : -1);
+ }
+ }
+
+ /**
+ * Loop over each page in the given range, turn off those bits and notify the rasterizer if
+ * needed. Call the given function on each turned off range.
+ *
+ * @param query_cpu_range Base CPU address to loop over
+ * @param size Size in bytes of the CPU range to loop over
+ * @param func Function to call for each turned off region
+ */
+ template <Type type, typename Func>
+ void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
+ static_assert(type != Type::Untracked);
+
+ const s64 difference = query_cpu_range - cpu_addr;
+ const u64 query_begin = std::max<s64>(difference, 0);
+ size += std::min<s64>(difference, 0);
+ if (query_begin >= SizeBytes() || size < 0) {
+ return;
+ }
+ u64* const untracked_words = Array<Type::Untracked>();
+ u64* const state_words = Array<type>();
+ const u64 query_end = query_begin + std::min(static_cast<u64>(size), SizeBytes());
+ u64* const words_begin = state_words + query_begin / BYTES_PER_WORD;
+ u64* const words_end = state_words + Common::DivCeil(query_end, BYTES_PER_WORD);
+
+ const auto modified = [](u64 word) { return word != 0; };
+ const auto first_modified_word = std::find_if(words_begin, words_end, modified);
+ if (first_modified_word == words_end) {
+ // Exit early when the buffer is not modified
+ return;
+ }
+ const auto last_modified_word = std::find_if_not(first_modified_word, words_end, modified);
+
+ const u64 word_index_begin = std::distance(state_words, first_modified_word);
+ const u64 word_index_end = std::distance(state_words, last_modified_word);
+
+ const unsigned local_page_begin = std::countr_zero(*first_modified_word);
+ const unsigned local_page_end =
+ static_cast<unsigned>(PAGES_PER_WORD) - std::countl_zero(last_modified_word[-1]);
+ const u64 word_page_begin = word_index_begin * PAGES_PER_WORD;
+ const u64 word_page_end = (word_index_end - 1) * PAGES_PER_WORD;
+ const u64 query_page_begin = query_begin / BYTES_PER_PAGE;
+ const u64 query_page_end = Common::DivCeil(query_end, BYTES_PER_PAGE);
+ const u64 page_index_begin = std::max(word_page_begin + local_page_begin, query_page_begin);
+ const u64 page_index_end = std::min(word_page_end + local_page_end, query_page_end);
+ const u64 first_word_page_begin = page_index_begin % PAGES_PER_WORD;
+ const u64 last_word_page_end = (page_index_end - 1) % PAGES_PER_WORD + 1;
+
+ u64 page_begin = first_word_page_begin;
+ u64 current_base = 0;
+ u64 current_size = 0;
+ bool on_going = false;
+ for (u64 word_index = word_index_begin; word_index < word_index_end; ++word_index) {
+ const bool is_last_word = word_index + 1 == word_index_end;
+ const u64 page_end = is_last_word ? last_word_page_end : PAGES_PER_WORD;
+ const u64 right_offset = page_begin;
+ const u64 left_offset = PAGES_PER_WORD - page_end;
+ u64 bits = ~u64{0};
+ bits = (bits >> right_offset) << right_offset;
+ bits = (bits << left_offset) >> left_offset;
+
+ const u64 current_word = state_words[word_index] & bits;
+ state_words[word_index] &= ~bits;
+
+ if constexpr (type == Type::CPU) {
+ const u64 current_bits = untracked_words[word_index] & bits;
+ untracked_words[word_index] &= ~bits;
+ NotifyRasterizer<true>(word_index, current_bits, ~u64{0});
+ }
+ // Exclude CPU modified pages when visiting GPU pages
+ const u64 word = current_word & ~(type == Type::GPU ? untracked_words[word_index] : 0);
+ u64 page = page_begin;
+ page_begin = 0;
+
+ while (page < page_end) {
+ const int empty_bits = std::countr_zero(word >> page);
+ if (on_going && empty_bits != 0) {
+ InvokeModifiedRange(func, current_size, current_base);
+ current_size = 0;
+ on_going = false;
+ }
+ page += empty_bits;
+
+ const int continuous_bits = std::countr_one(word >> page);
+ if (!on_going && continuous_bits != 0) {
+ current_base = word_index * PAGES_PER_WORD + page;
+ on_going = true;
+ }
+ current_size += continuous_bits;
+ page += continuous_bits;
+ }
+ }
+ if (on_going && current_size > 0) {
+ InvokeModifiedRange(func, current_size, current_base);
+ }
+ }
+
+ template <typename Func>
+ void InvokeModifiedRange(Func&& func, u64 current_size, u64 current_base) {
+ const u64 current_size_bytes = current_size * BYTES_PER_PAGE;
+ const u64 offset_begin = current_base * BYTES_PER_PAGE;
+ const u64 offset_end = std::min(offset_begin + current_size_bytes, SizeBytes());
+ func(offset_begin, offset_end - offset_begin);
+ }
+
+ /**
+ * Returns true when a region has been modified
+ *
+ * @param offset Offset in bytes from the start of the buffer
+ * @param size Size in bytes of the region to query for modifications
+ */
+ template <Type type>
+ [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
+ static_assert(type != Type::Untracked);
+
+ const u64* const untracked_words = Array<Type::Untracked>();
+ const u64* const state_words = Array<type>();
+ const u64 num_query_words = size / BYTES_PER_WORD + 1;
+ const u64 word_begin = offset / BYTES_PER_WORD;
+ const u64 word_end = std::min(word_begin + num_query_words, NumWords());
+ const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
+ u64 page_index = (offset / BYTES_PER_PAGE) % PAGES_PER_WORD;
+ for (u64 word_index = word_begin; word_index < word_end; ++word_index, page_index = 0) {
+ const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
+ const u64 word = state_words[word_index] & ~off_word;
+ if (word == 0) {
+ continue;
+ }
+ const u64 page_end = std::min((word_index + 1) * PAGES_PER_WORD, page_limit);
+ const u64 local_page_end = page_end % PAGES_PER_WORD;
+ const u64 page_end_shift = (PAGES_PER_WORD - local_page_end) % PAGES_PER_WORD;
+ if (((word >> page_index) << page_index) << page_end_shift != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a begin end pair with the inclusive modified region
+ *
+ * @param offset Offset in bytes from the start of the buffer
+ * @param size Size in bytes of the region to query for modifications
+ */
+ template <Type type>
+ [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
+ static_assert(type != Type::Untracked);
+
+ const u64* const untracked_words = Array<Type::Untracked>();
+ const u64* const state_words = Array<type>();
+ const u64 num_query_words = size / BYTES_PER_WORD + 1;
+ const u64 word_begin = offset / BYTES_PER_WORD;
+ const u64 word_end = std::min(word_begin + num_query_words, NumWords());
+ const u64 page_base = offset / BYTES_PER_PAGE;
+ const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
+ u64 begin = std::numeric_limits<u64>::max();
+ u64 end = 0;
+ for (u64 word_index = word_begin; word_index < word_end; ++word_index) {
+ const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
+ const u64 word = state_words[word_index] & ~off_word;
+ if (word == 0) {
+ continue;
+ }
+ const u64 local_page_begin = std::countr_zero(word);
+ const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
+ const u64 page_index = word_index * PAGES_PER_WORD;
+ const u64 page_begin = std::max(page_index + local_page_begin, page_base);
+ const u64 page_end = std::min(page_index + local_page_end, page_limit);
+ begin = std::min(begin, page_begin);
+ end = std::max(end, page_end);
+ }
+ static constexpr std::pair<u64, u64> EMPTY{0, 0};
+ return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
+ }
+
+ /// Returns the number of words of the buffer
+ [[nodiscard]] size_t NumWords() const noexcept {
+ return words.NumWords();
+ }
+
+ /// Returns true when the buffer fits in the small vector optimization
+ [[nodiscard]] bool IsShort() const noexcept {
+ return words.IsShort();
+ }
+
+ RasterizerInterface* rasterizer = nullptr;
+ VAddr cpu_addr = 0;
+ Words words;
+ BufferFlagBits flags{};
+ int stream_score = 0;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/buffer_block.h b/src/video_core/buffer_cache/buffer_block.h
deleted file mode 100644
index e64170e66..000000000
--- a/src/video_core/buffer_cache/buffer_block.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <unordered_set>
-#include <utility>
-
-#include "common/alignment.h"
-#include "common/common_types.h"
-#include "video_core/gpu.h"
-
-namespace VideoCommon {
-
-class BufferBlock {
-public:
- bool Overlaps(VAddr start, VAddr end) const {
- return (cpu_addr < end) && (cpu_addr_end > start);
- }
-
- bool IsInside(VAddr other_start, VAddr other_end) const {
- return cpu_addr <= other_start && other_end <= cpu_addr_end;
- }
-
- std::size_t Offset(VAddr in_addr) const {
- return static_cast<std::size_t>(in_addr - cpu_addr);
- }
-
- VAddr CpuAddr() const {
- return cpu_addr;
- }
-
- VAddr CpuAddrEnd() const {
- return cpu_addr_end;
- }
-
- void SetCpuAddr(VAddr new_addr) {
- cpu_addr = new_addr;
- cpu_addr_end = new_addr + size;
- }
-
- std::size_t Size() const {
- return size;
- }
-
- u64 Epoch() const {
- return epoch;
- }
-
- void SetEpoch(u64 new_epoch) {
- epoch = new_epoch;
- }
-
-protected:
- explicit BufferBlock(VAddr cpu_addr_, std::size_t size_) : size{size_} {
- SetCpuAddr(cpu_addr_);
- }
-
-private:
- VAddr cpu_addr{};
- VAddr cpu_addr_end{};
- std::size_t size{};
- u64 epoch{};
-};
-
-} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp
new file mode 100644
index 000000000..ab32294c8
--- /dev/null
+++ b/src/video_core/buffer_cache/buffer_cache.cpp
@@ -0,0 +1,13 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/microprofile.h"
+
+namespace VideoCommon {
+
+MICROPROFILE_DEFINE(GPU_PrepareBuffers, "GPU", "Prepare buffers", MP_RGB(224, 128, 128));
+MICROPROFILE_DEFINE(GPU_BindUploadBuffers, "GPU", "Bind and upload buffers", MP_RGB(224, 128, 128));
+MICROPROFILE_DEFINE(GPU_DownloadMemory, "GPU", "Download buffers", MP_RGB(224, 128, 128));
+
+} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index b5dc68902..2a6844ab1 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -4,597 +4,1289 @@
#pragma once
-#include <list>
+#include <algorithm>
+#include <array>
+#include <deque>
#include <memory>
#include <mutex>
+#include <span>
#include <unordered_map>
-#include <unordered_set>
-#include <utility>
#include <vector>
#include <boost/container/small_vector.hpp>
-#include <boost/icl/interval_set.hpp>
-#include <boost/intrusive/set.hpp>
-#include "common/alignment.h"
-#include "common/assert.h"
#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "core/core.h"
+#include "common/div_ceil.h"
+#include "common/microprofile.h"
+#include "common/scope_exit.h"
#include "core/memory.h"
#include "core/settings.h"
-#include "video_core/buffer_cache/buffer_block.h"
-#include "video_core/buffer_cache/map_interval.h"
+#include "video_core/buffer_cache/buffer_base.h"
+#include "video_core/delayed_destruction_ring.h"
+#include "video_core/dirty_flags.h"
+#include "video_core/engines/kepler_compute.h"
+#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/texture_cache/slot_vector.h"
+#include "video_core/texture_cache/types.h"
namespace VideoCommon {
-template <typename Buffer, typename BufferType, typename StreamBuffer>
+MICROPROFILE_DECLARE(GPU_PrepareBuffers);
+MICROPROFILE_DECLARE(GPU_BindUploadBuffers);
+MICROPROFILE_DECLARE(GPU_DownloadMemory);
+
+using BufferId = SlotId;
+
+constexpr u32 NUM_VERTEX_BUFFERS = 32;
+constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
+constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
+constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
+constexpr u32 NUM_STORAGE_BUFFERS = 16;
+constexpr u32 NUM_STAGES = 5;
+
+template <typename P>
class BufferCache {
- using IntervalSet = boost::icl::interval_set<VAddr>;
- using IntervalType = typename IntervalSet::interval_type;
- using VectorMapInterval = boost::container::small_vector<MapInterval*, 1>;
+ // Page size for caching purposes.
+ // This is unrelated to the CPU page size and it can be changed as it seems optimal.
+ static constexpr u32 PAGE_BITS = 16;
+ static constexpr u64 PAGE_SIZE = u64{1} << PAGE_BITS;
+
+ static constexpr bool IS_OPENGL = P::IS_OPENGL;
+ static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS =
+ P::HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS;
+ static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT =
+ P::HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT;
+ static constexpr bool NEEDS_BIND_UNIFORM_INDEX = P::NEEDS_BIND_UNIFORM_INDEX;
+ static constexpr bool NEEDS_BIND_STORAGE_INDEX = P::NEEDS_BIND_STORAGE_INDEX;
+ static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
+
+ static constexpr BufferId NULL_BUFFER_ID{0};
+
+ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+ using Runtime = typename P::Runtime;
+ using Buffer = typename P::Buffer;
+
+ struct Empty {};
+
+ struct OverlapResult {
+ std::vector<BufferId> ids;
+ VAddr begin;
+ VAddr end;
+ bool has_stream_leap = false;
+ };
- static constexpr u64 WRITE_PAGE_BIT = 11;
- static constexpr u64 BLOCK_PAGE_BITS = 21;
- static constexpr u64 BLOCK_PAGE_SIZE = 1ULL << BLOCK_PAGE_BITS;
+ struct Binding {
+ VAddr cpu_addr{};
+ u32 size{};
+ BufferId buffer_id;
+ };
-public:
- struct BufferInfo {
- BufferType handle;
- u64 offset;
- u64 address;
+ static constexpr Binding NULL_BINDING{
+ .cpu_addr = 0,
+ .size = 0,
+ .buffer_id = NULL_BUFFER_ID,
};
- BufferInfo UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
- bool is_written = false, bool use_fast_cbuf = false) {
- std::lock_guard lock{mutex};
+public:
+ static constexpr u32 SKIP_CACHE_SIZE = 4096;
- auto& memory_manager = system.GPU().MemoryManager();
- const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr);
- if (!cpu_addr_opt) {
- return GetEmptyBuffer(size);
- }
- const VAddr cpu_addr = *cpu_addr_opt;
-
- // Cache management is a big overhead, so only cache entries with a given size.
- // TODO: Figure out which size is the best for given games.
- constexpr std::size_t max_stream_size = 0x800;
- if (use_fast_cbuf || size < max_stream_size) {
- if (!is_written && !IsRegionWritten(cpu_addr, cpu_addr + size - 1)) {
- const bool is_granular = memory_manager.IsGranularRange(gpu_addr, size);
- if (use_fast_cbuf) {
- u8* dest;
- if (is_granular) {
- dest = memory_manager.GetPointer(gpu_addr);
- } else {
- staging_buffer.resize(size);
- dest = staging_buffer.data();
- memory_manager.ReadBlockUnsafe(gpu_addr, dest, size);
- }
- return ConstBufferUpload(dest, size);
- }
- if (is_granular) {
- u8* const host_ptr = memory_manager.GetPointer(gpu_addr);
- return StreamBufferUpload(size, alignment, [host_ptr, size](u8* dest) {
- std::memcpy(dest, host_ptr, size);
- });
- } else {
- return StreamBufferUpload(
- size, alignment, [&memory_manager, gpu_addr, size](u8* dest) {
- memory_manager.ReadBlockUnsafe(gpu_addr, dest, size);
- });
- }
- }
- }
+ explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
+ Runtime& runtime_);
- Buffer* const block = GetBlock(cpu_addr, size);
- MapInterval* const map = MapAddress(block, gpu_addr, cpu_addr, size);
- if (!map) {
- return GetEmptyBuffer(size);
- }
- if (is_written) {
- map->MarkAsModified(true, GetModifiedTicks());
- if (Settings::IsGPULevelHigh() &&
- Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
- MarkForAsyncFlush(map);
- }
- if (!map->is_written) {
- map->is_written = true;
- MarkRegionAsWritten(map->start, map->end - 1);
- }
- }
+ void TickFrame();
- return BufferInfo{block->Handle(), block->Offset(cpu_addr), block->Address()};
- }
+ void WriteMemory(VAddr cpu_addr, u64 size);
- /// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset.
- BufferInfo UploadHostMemory(const void* raw_pointer, std::size_t size,
- std::size_t alignment = 4) {
- std::lock_guard lock{mutex};
- return StreamBufferUpload(size, alignment, [raw_pointer, size](u8* dest) {
- std::memcpy(dest, raw_pointer, size);
- });
- }
+ void CachedWriteMemory(VAddr cpu_addr, u64 size);
- /// Prepares the buffer cache for data uploading
- /// @param max_size Maximum number of bytes that will be uploaded
- /// @return True when a stream buffer invalidation was required, false otherwise
- bool Map(std::size_t max_size) {
- std::lock_guard lock{mutex};
+ void DownloadMemory(VAddr cpu_addr, u64 size);
- bool invalidated;
- std::tie(buffer_ptr, buffer_offset_base, invalidated) = stream_buffer->Map(max_size, 4);
- buffer_offset = buffer_offset_base;
+ void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
- return invalidated;
- }
+ void UpdateGraphicsBuffers(bool is_indexed);
- /// Finishes the upload stream
- void Unmap() {
- std::lock_guard lock{mutex};
- stream_buffer->Unmap(buffer_offset - buffer_offset_base);
- }
+ void UpdateComputeBuffers();
- /// Function called at the end of each frame, inteded for deferred operations
- void TickFrame() {
- ++epoch;
+ void BindHostGeometryBuffers(bool is_indexed);
- while (!pending_destruction.empty()) {
- // Delay at least 4 frames before destruction.
- // This is due to triple buffering happening on some drivers.
- static constexpr u64 epochs_to_destroy = 5;
- if (pending_destruction.front()->Epoch() + epochs_to_destroy > epoch) {
- break;
- }
- pending_destruction.pop();
- }
- }
+ void BindHostStageBuffers(size_t stage);
- /// Write any cached resources overlapping the specified region back to memory
- void FlushRegion(VAddr addr, std::size_t size) {
- std::lock_guard lock{mutex};
+ void BindHostComputeBuffers();
- VectorMapInterval objects = GetMapsInRange(addr, size);
- std::sort(objects.begin(), objects.end(),
- [](MapInterval* lhs, MapInterval* rhs) { return lhs->ticks < rhs->ticks; });
- for (MapInterval* object : objects) {
- if (object->is_modified && object->is_registered) {
- mutex.unlock();
- FlushMap(object);
- mutex.lock();
- }
- }
- }
+ void SetEnabledUniformBuffers(size_t stage, u32 enabled);
- bool MustFlushRegion(VAddr addr, std::size_t size) {
- std::lock_guard lock{mutex};
+ void SetEnabledComputeUniformBuffers(u32 enabled);
- const VectorMapInterval objects = GetMapsInRange(addr, size);
- return std::any_of(objects.cbegin(), objects.cend(), [](const MapInterval* map) {
- return map->is_modified && map->is_registered;
- });
- }
+ void UnbindGraphicsStorageBuffers(size_t stage);
- /// Mark the specified region as being invalidated
- void InvalidateRegion(VAddr addr, u64 size) {
- std::lock_guard lock{mutex};
+ void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
+ bool is_written);
- for (auto& object : GetMapsInRange(addr, size)) {
- if (object->is_registered) {
- Unregister(object);
- }
- }
- }
+ void UnbindComputeStorageBuffers();
- void OnCPUWrite(VAddr addr, std::size_t size) {
- std::lock_guard lock{mutex};
+ void BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
+ bool is_written);
- for (MapInterval* object : GetMapsInRange(addr, size)) {
- if (object->is_memory_marked && object->is_registered) {
- UnmarkMemory(object);
- object->is_sync_pending = true;
- marked_for_unregister.emplace_back(object);
- }
- }
- }
+ void FlushCachedWrites();
- void SyncGuestHost() {
- std::lock_guard lock{mutex};
+ /// Return true when there are uncommitted buffers to be downloaded
+ [[nodiscard]] bool HasUncommittedFlushes() const noexcept;
- for (auto& object : marked_for_unregister) {
- if (object->is_registered) {
- object->is_sync_pending = false;
- Unregister(object);
- }
+ /// Return true when the caller should wait for async downloads
+ [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept;
+
+ /// Commit asynchronous downloads
+ void CommitAsyncFlushes();
+
+ /// Pop asynchronous downloads
+ void PopAsyncFlushes();
+
+ /// Return true when a CPU region is modified from the GPU
+ [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
+
+ std::mutex mutex;
+
+private:
+ template <typename Func>
+ static void ForEachEnabledBit(u32 enabled_mask, Func&& func) {
+ for (u32 index = 0; enabled_mask != 0; ++index, enabled_mask >>= 1) {
+ const int disabled_bits = std::countr_zero(enabled_mask);
+ index += disabled_bits;
+ enabled_mask >>= disabled_bits;
+ func(index);
}
- marked_for_unregister.clear();
}
- void CommitAsyncFlushes() {
- if (uncommitted_flushes) {
- auto commit_list = std::make_shared<std::list<MapInterval*>>();
- for (MapInterval* map : *uncommitted_flushes) {
- if (map->is_registered && map->is_modified) {
- // TODO(Blinkhawk): Implement backend asynchronous flushing
- // AsyncFlushMap(map)
- commit_list->push_back(map);
- }
- }
- if (!commit_list->empty()) {
- committed_flushes.push_back(commit_list);
- } else {
- committed_flushes.emplace_back();
+ template <typename Func>
+ void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) {
+ const u64 page_end = Common::DivCeil(cpu_addr + size, PAGE_SIZE);
+ for (u64 page = cpu_addr >> PAGE_BITS; page < page_end;) {
+ const BufferId buffer_id = page_table[page];
+ if (!buffer_id) {
+ ++page;
+ continue;
}
- } else {
- committed_flushes.emplace_back();
+ Buffer& buffer = slot_buffers[buffer_id];
+ func(buffer_id, buffer);
+
+ const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
+ page = Common::DivCeil(end_addr, PAGE_SIZE);
}
- uncommitted_flushes.reset();
}
- bool ShouldWaitAsyncFlushes() const {
- return !committed_flushes.empty() && committed_flushes.front() != nullptr;
+ static bool IsRangeGranular(VAddr cpu_addr, size_t size) {
+ return (cpu_addr & ~Core::Memory::PAGE_MASK) ==
+ ((cpu_addr + size) & ~Core::Memory::PAGE_MASK);
}
- bool HasUncommittedFlushes() const {
- return uncommitted_flushes != nullptr;
- }
+ void BindHostIndexBuffer();
- void PopAsyncFlushes() {
- if (committed_flushes.empty()) {
- return;
- }
- auto& flush_list = committed_flushes.front();
- if (!flush_list) {
- committed_flushes.pop_front();
- return;
- }
- for (MapInterval* map : *flush_list) {
- if (map->is_registered) {
- // TODO(Blinkhawk): Replace this for reading the asynchronous flush
- FlushMap(map);
- }
- }
- committed_flushes.pop_front();
- }
+ void BindHostVertexBuffers();
- virtual BufferInfo GetEmptyBuffer(std::size_t size) = 0;
+ void BindHostGraphicsUniformBuffers(size_t stage);
-protected:
- explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
- std::unique_ptr<StreamBuffer> stream_buffer)
- : rasterizer{rasterizer}, system{system}, stream_buffer{std::move(stream_buffer)} {}
+ void BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind);
- ~BufferCache() = default;
+ void BindHostGraphicsStorageBuffers(size_t stage);
- virtual std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) = 0;
+ void BindHostTransformFeedbackBuffers();
- virtual BufferInfo ConstBufferUpload(const void* raw_pointer, std::size_t size) {
- return {};
- }
+ void BindHostComputeUniformBuffers();
- /// Register an object into the cache
- MapInterval* Register(MapInterval new_map, bool inherit_written = false) {
- const VAddr cpu_addr = new_map.start;
- if (!cpu_addr) {
- LOG_CRITICAL(HW_GPU, "Failed to register buffer with unmapped gpu_address 0x{:016x}",
- new_map.gpu_addr);
- return nullptr;
- }
- const std::size_t size = new_map.end - new_map.start;
- new_map.is_registered = true;
- rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1);
- new_map.is_memory_marked = true;
- if (inherit_written) {
- MarkRegionAsWritten(new_map.start, new_map.end - 1);
- new_map.is_written = true;
- }
- MapInterval* const storage = mapped_addresses_allocator.Allocate();
- *storage = new_map;
- mapped_addresses.insert(*storage);
- return storage;
- }
+ void BindHostComputeStorageBuffers();
- void UnmarkMemory(MapInterval* map) {
- if (!map->is_memory_marked) {
- return;
- }
- const std::size_t size = map->end - map->start;
- rasterizer.UpdatePagesCachedCount(map->start, size, -1);
- map->is_memory_marked = false;
- }
-
- /// Unregisters an object from the cache
- void Unregister(MapInterval* map) {
- UnmarkMemory(map);
- map->is_registered = false;
- if (map->is_sync_pending) {
- map->is_sync_pending = false;
- marked_for_unregister.remove(map);
+ void DoUpdateGraphicsBuffers(bool is_indexed);
+
+ void DoUpdateComputeBuffers();
+
+ void UpdateIndexBuffer();
+
+ void UpdateVertexBuffers();
+
+ void UpdateVertexBuffer(u32 index);
+
+ void UpdateUniformBuffers(size_t stage);
+
+ void UpdateStorageBuffers(size_t stage);
+
+ void UpdateTransformFeedbackBuffers();
+
+ void UpdateTransformFeedbackBuffer(u32 index);
+
+ void UpdateComputeUniformBuffers();
+
+ void UpdateComputeStorageBuffers();
+
+ void MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size);
+
+ [[nodiscard]] BufferId FindBuffer(VAddr cpu_addr, u32 size);
+
+ [[nodiscard]] OverlapResult ResolveOverlaps(VAddr cpu_addr, u32 wanted_size);
+
+ void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score);
+
+ [[nodiscard]] BufferId CreateBuffer(VAddr cpu_addr, u32 wanted_size);
+
+ void Register(BufferId buffer_id);
+
+ void Unregister(BufferId buffer_id);
+
+ template <bool insert>
+ void ChangeRegister(BufferId buffer_id);
+
+ void SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);
+
+ void SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size);
+
+ void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
+ std::span<BufferCopy> copies);
+
+ void ImmediateUploadMemory(Buffer& buffer, u64 largest_copy,
+ std::span<const BufferCopy> copies);
+
+ void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
+
+ void DeleteBuffer(BufferId buffer_id);
+
+ void ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id);
+
+ void NotifyBufferDeletion();
+
+ [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr) const;
+
+ [[nodiscard]] std::span<const u8> ImmediateBufferWithData(VAddr cpu_addr, size_t size);
+
+ [[nodiscard]] std::span<u8> ImmediateBuffer(size_t wanted_capacity);
+
+ [[nodiscard]] bool HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept;
+
+ VideoCore::RasterizerInterface& rasterizer;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
+ Core::Memory::Memory& cpu_memory;
+ Runtime& runtime;
+
+ SlotVector<Buffer> slot_buffers;
+ DelayedDestructionRing<Buffer, 8> delayed_destruction_ring;
+
+ u32 last_index_count = 0;
+
+ Binding index_buffer;
+ std::array<Binding, NUM_VERTEX_BUFFERS> vertex_buffers;
+ std::array<std::array<Binding, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES> uniform_buffers;
+ std::array<std::array<Binding, NUM_STORAGE_BUFFERS>, NUM_STAGES> storage_buffers;
+ std::array<Binding, NUM_TRANSFORM_FEEDBACK_BUFFERS> transform_feedback_buffers;
+
+ std::array<Binding, NUM_COMPUTE_UNIFORM_BUFFERS> compute_uniform_buffers;
+ std::array<Binding, NUM_STORAGE_BUFFERS> compute_storage_buffers;
+
+ std::array<u32, NUM_STAGES> enabled_uniform_buffers{};
+ u32 enabled_compute_uniform_buffers = 0;
+
+ std::array<u32, NUM_STAGES> enabled_storage_buffers{};
+ std::array<u32, NUM_STAGES> written_storage_buffers{};
+ u32 enabled_compute_storage_buffers = 0;
+ u32 written_compute_storage_buffers = 0;
+
+ std::array<u32, NUM_STAGES> fast_bound_uniform_buffers{};
+
+ bool has_deleted_buffers = false;
+
+ std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS, std::array<u32, NUM_STAGES>, Empty>
+ dirty_uniform_buffers{};
+
+ std::vector<BufferId> cached_write_buffer_ids;
+
+ // TODO: This data structure is not optimal and it should be reworked
+ std::vector<BufferId> uncommitted_downloads;
+ std::deque<std::vector<BufferId>> committed_downloads;
+
+ size_t immediate_buffer_capacity = 0;
+ std::unique_ptr<u8[]> immediate_buffer_alloc;
+
+ std::array<BufferId, ((1ULL << 39) >> PAGE_BITS)> page_table;
+};
+
+template <class P>
+BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
+ Runtime& runtime_)
+ : rasterizer{rasterizer_}, maxwell3d{maxwell3d_}, kepler_compute{kepler_compute_},
+ gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_}, runtime{runtime_} {
+ // Ensure the first slot is used for the null buffer
+ void(slot_buffers.insert(runtime, NullBufferParams{}));
+}
+
+template <class P>
+void BufferCache<P>::TickFrame() {
+ delayed_destruction_ring.Tick();
+}
+
+template <class P>
+void BufferCache<P>::WriteMemory(VAddr cpu_addr, u64 size) {
+ ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) {
+ buffer.MarkRegionAsCpuModified(cpu_addr, size);
+ });
+}
+
+template <class P>
+void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) {
+ ForEachBufferInRange(cpu_addr, size, [&](BufferId buffer_id, Buffer& buffer) {
+ if (!buffer.HasCachedWrites()) {
+ cached_write_buffer_ids.push_back(buffer_id);
}
- if (map->is_written) {
- UnmarkRegionAsWritten(map->start, map->end - 1);
+ buffer.CachedCpuWrite(cpu_addr, size);
+ });
+}
+
+template <class P>
+void BufferCache<P>::DownloadMemory(VAddr cpu_addr, u64 size) {
+ ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) {
+ boost::container::small_vector<BufferCopy, 1> copies;
+ u64 total_size_bytes = 0;
+ u64 largest_copy = 0;
+ buffer.ForEachDownloadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) {
+ copies.push_back(BufferCopy{
+ .src_offset = range_offset,
+ .dst_offset = total_size_bytes,
+ .size = range_size,
+ });
+ total_size_bytes += range_size;
+ largest_copy = std::max(largest_copy, range_size);
+ });
+ if (total_size_bytes == 0) {
+ return;
}
- const auto it = mapped_addresses.find(*map);
- ASSERT(it != mapped_addresses.end());
- mapped_addresses.erase(it);
- mapped_addresses_allocator.Release(map);
- }
-
-private:
- MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) {
- const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size);
- if (overlaps.empty()) {
- auto& memory_manager = system.GPU().MemoryManager();
- const VAddr cpu_addr_end = cpu_addr + size;
- if (memory_manager.IsGranularRange(gpu_addr, size)) {
- u8* host_ptr = memory_manager.GetPointer(gpu_addr);
- block->Upload(block->Offset(cpu_addr), size, host_ptr);
- } else {
- staging_buffer.resize(size);
- memory_manager.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
- block->Upload(block->Offset(cpu_addr), size, staging_buffer.data());
+ MICROPROFILE_SCOPE(GPU_DownloadMemory);
+
+ if constexpr (USE_MEMORY_MAPS) {
+ auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
+ const u8* const mapped_memory = download_staging.mapped_span.data();
+ const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
+ for (BufferCopy& copy : copies) {
+ // Modify copies to have the staging offset in mind
+ copy.dst_offset += download_staging.offset;
}
- return Register(MapInterval(cpu_addr, cpu_addr_end, gpu_addr));
- }
-
- const VAddr cpu_addr_end = cpu_addr + size;
- if (overlaps.size() == 1) {
- MapInterval* const current_map = overlaps[0];
- if (current_map->IsInside(cpu_addr, cpu_addr_end)) {
- return current_map;
+ runtime.CopyBuffer(download_staging.buffer, buffer, copies_span);
+ runtime.Finish();
+ for (const BufferCopy& copy : copies) {
+ const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
+ // Undo the modified offset
+ const u64 dst_offset = copy.dst_offset - download_staging.offset;
+ const u8* copy_mapped_memory = mapped_memory + dst_offset;
+ cpu_memory.WriteBlockUnsafe(copy_cpu_addr, copy_mapped_memory, copy.size);
+ }
+ } else {
+ const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy);
+ for (const BufferCopy& copy : copies) {
+ buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size));
+ const VAddr copy_cpu_addr = buffer.CpuAddr() + copy.src_offset;
+ cpu_memory.WriteBlockUnsafe(copy_cpu_addr, immediate_buffer.data(), copy.size);
}
}
- VAddr new_start = cpu_addr;
- VAddr new_end = cpu_addr_end;
- bool write_inheritance = false;
- bool modified_inheritance = false;
- // Calculate new buffer parameters
- for (MapInterval* overlap : overlaps) {
- new_start = std::min(overlap->start, new_start);
- new_end = std::max(overlap->end, new_end);
- write_inheritance |= overlap->is_written;
- modified_inheritance |= overlap->is_modified;
+ });
+}
+
+template <class P>
+void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
+ u32 size) {
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ if (!cpu_addr) {
+ uniform_buffers[stage][index] = NULL_BINDING;
+ return;
+ }
+ const Binding binding{
+ .cpu_addr = *cpu_addr,
+ .size = size,
+ .buffer_id = BufferId{},
+ };
+ uniform_buffers[stage][index] = binding;
+}
+
+template <class P>
+void BufferCache<P>::UpdateGraphicsBuffers(bool is_indexed) {
+ MICROPROFILE_SCOPE(GPU_PrepareBuffers);
+ do {
+ has_deleted_buffers = false;
+ DoUpdateGraphicsBuffers(is_indexed);
+ } while (has_deleted_buffers);
+}
+
+template <class P>
+void BufferCache<P>::UpdateComputeBuffers() {
+ MICROPROFILE_SCOPE(GPU_PrepareBuffers);
+ do {
+ has_deleted_buffers = false;
+ DoUpdateComputeBuffers();
+ } while (has_deleted_buffers);
+}
+
+template <class P>
+void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
+ MICROPROFILE_SCOPE(GPU_BindUploadBuffers);
+ if (is_indexed) {
+ BindHostIndexBuffer();
+ } else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
+ const auto& regs = maxwell3d.regs;
+ if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
+ runtime.BindQuadArrayIndexBuffer(regs.vertex_buffer.first, regs.vertex_buffer.count);
}
- GPUVAddr new_gpu_addr = gpu_addr + new_start - cpu_addr;
- for (auto& overlap : overlaps) {
- Unregister(overlap);
+ }
+ BindHostVertexBuffers();
+ BindHostTransformFeedbackBuffers();
+}
+
+template <class P>
+void BufferCache<P>::BindHostStageBuffers(size_t stage) {
+ MICROPROFILE_SCOPE(GPU_BindUploadBuffers);
+ BindHostGraphicsUniformBuffers(stage);
+ BindHostGraphicsStorageBuffers(stage);
+}
+
+template <class P>
+void BufferCache<P>::BindHostComputeBuffers() {
+ MICROPROFILE_SCOPE(GPU_BindUploadBuffers);
+ BindHostComputeUniformBuffers();
+ BindHostComputeStorageBuffers();
+}
+
+template <class P>
+void BufferCache<P>::SetEnabledUniformBuffers(size_t stage, u32 enabled) {
+ if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
+ if (enabled_uniform_buffers[stage] != enabled) {
+ dirty_uniform_buffers[stage] = ~u32{0};
}
- UpdateBlock(block, new_start, new_end, overlaps);
-
- const MapInterval new_map{new_start, new_end, new_gpu_addr};
- MapInterval* const map = Register(new_map, write_inheritance);
- if (!map) {
- return nullptr;
+ }
+ enabled_uniform_buffers[stage] = enabled;
+}
+
+template <class P>
+void BufferCache<P>::SetEnabledComputeUniformBuffers(u32 enabled) {
+ enabled_compute_uniform_buffers = enabled;
+}
+
+template <class P>
+void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) {
+ enabled_storage_buffers[stage] = 0;
+ written_storage_buffers[stage] = 0;
+}
+
+template <class P>
+void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index,
+ u32 cbuf_offset, bool is_written) {
+ enabled_storage_buffers[stage] |= 1U << ssbo_index;
+ written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
+
+ const auto& cbufs = maxwell3d.state.shader_stages[stage];
+ const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
+ storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr);
+}
+
+template <class P>
+void BufferCache<P>::UnbindComputeStorageBuffers() {
+ enabled_compute_storage_buffers = 0;
+ written_compute_storage_buffers = 0;
+}
+
+template <class P>
+void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
+ bool is_written) {
+ enabled_compute_storage_buffers |= 1U << ssbo_index;
+ written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index;
+
+ const auto& launch_desc = kepler_compute.launch_description;
+ ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0);
+
+ const auto& cbufs = launch_desc.const_buffer_config;
+ const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset;
+ compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr);
+}
+
+template <class P>
+void BufferCache<P>::FlushCachedWrites() {
+ for (const BufferId buffer_id : cached_write_buffer_ids) {
+ slot_buffers[buffer_id].FlushCachedWrites();
+ }
+ cached_write_buffer_ids.clear();
+}
+
+template <class P>
+bool BufferCache<P>::HasUncommittedFlushes() const noexcept {
+ return !uncommitted_downloads.empty();
+}
+
+template <class P>
+bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
+ return !committed_downloads.empty() && !committed_downloads.front().empty();
+}
+
+template <class P>
+void BufferCache<P>::CommitAsyncFlushes() {
+ // This is intentionally passing the value by copy
+ committed_downloads.push_front(uncommitted_downloads);
+ uncommitted_downloads.clear();
+}
+
+template <class P>
+void BufferCache<P>::PopAsyncFlushes() {
+ if (committed_downloads.empty()) {
+ return;
+ }
+ auto scope_exit_pop_download = detail::ScopeExit([this] { committed_downloads.pop_back(); });
+ const std::span<const BufferId> download_ids = committed_downloads.back();
+ if (download_ids.empty()) {
+ return;
+ }
+ MICROPROFILE_SCOPE(GPU_DownloadMemory);
+
+ boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
+ u64 total_size_bytes = 0;
+ u64 largest_copy = 0;
+ for (const BufferId buffer_id : download_ids) {
+ slot_buffers[buffer_id].ForEachDownloadRange([&](u64 range_offset, u64 range_size) {
+ downloads.push_back({
+ BufferCopy{
+ .src_offset = range_offset,
+ .dst_offset = total_size_bytes,
+ .size = range_size,
+ },
+ buffer_id,
+ });
+ total_size_bytes += range_size;
+ largest_copy = std::max(largest_copy, range_size);
+ });
+ }
+ if (downloads.empty()) {
+ return;
+ }
+ if constexpr (USE_MEMORY_MAPS) {
+ auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
+ for (auto& [copy, buffer_id] : downloads) {
+ // Have in mind the staging buffer offset for the copy
+ copy.dst_offset += download_staging.offset;
+ const std::array copies{copy};
+ runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies);
}
- if (modified_inheritance) {
- map->MarkAsModified(true, GetModifiedTicks());
- if (Settings::IsGPULevelHigh() &&
- Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
- MarkForAsyncFlush(map);
- }
+ runtime.Finish();
+ for (const auto [copy, buffer_id] : downloads) {
+ const Buffer& buffer = slot_buffers[buffer_id];
+ const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset;
+ // Undo the modified offset
+ const u64 dst_offset = copy.dst_offset - download_staging.offset;
+ const u8* read_mapped_memory = download_staging.mapped_span.data() + dst_offset;
+ cpu_memory.WriteBlockUnsafe(cpu_addr, read_mapped_memory, copy.size);
+ }
+ } else {
+ const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy);
+ for (const auto [copy, buffer_id] : downloads) {
+ Buffer& buffer = slot_buffers[buffer_id];
+ buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size));
+ const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset;
+ cpu_memory.WriteBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size);
}
- return map;
}
-
- void UpdateBlock(Buffer* block, VAddr start, VAddr end, const VectorMapInterval& overlaps) {
- const IntervalType base_interval{start, end};
- IntervalSet interval_set{};
- interval_set.add(base_interval);
- for (auto& overlap : overlaps) {
- const IntervalType subtract{overlap->start, overlap->end};
- interval_set.subtract(subtract);
+}
+
+template <class P>
+bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
+ const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
+ for (u64 page = addr >> PAGE_BITS; page < page_end;) {
+ const BufferId image_id = page_table[page];
+ if (!image_id) {
+ ++page;
+ continue;
}
- for (auto& interval : interval_set) {
- const std::size_t size = interval.upper() - interval.lower();
- if (size == 0) {
- continue;
- }
- staging_buffer.resize(size);
- system.Memory().ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size);
- block->Upload(block->Offset(interval.lower()), size, staging_buffer.data());
+ Buffer& buffer = slot_buffers[image_id];
+ if (buffer.IsRegionGpuModified(addr, size)) {
+ return true;
}
+ const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
+ page = Common::DivCeil(end_addr, PAGE_SIZE);
}
-
- VectorMapInterval GetMapsInRange(VAddr addr, std::size_t size) {
- VectorMapInterval result;
- if (size == 0) {
- return result;
+ return false;
+}
+
+template <class P>
+void BufferCache<P>::BindHostIndexBuffer() {
+ Buffer& buffer = slot_buffers[index_buffer.buffer_id];
+ const u32 offset = buffer.Offset(index_buffer.cpu_addr);
+ const u32 size = index_buffer.size;
+ SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
+ if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
+ runtime.BindIndexBuffer(buffer, offset, size);
+ } else {
+ runtime.BindIndexBuffer(maxwell3d.regs.draw.topology, maxwell3d.regs.index_array.format,
+ maxwell3d.regs.index_array.first, maxwell3d.regs.index_array.count,
+ buffer, offset, size);
+ }
+}
+
+template <class P>
+void BufferCache<P>::BindHostVertexBuffers() {
+ auto& flags = maxwell3d.dirty.flags;
+ for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
+ const Binding& binding = vertex_buffers[index];
+ Buffer& buffer = slot_buffers[binding.buffer_id];
+ SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
+ if (!flags[Dirty::VertexBuffer0 + index]) {
+ continue;
}
+ flags[Dirty::VertexBuffer0 + index] = false;
+
+ const u32 stride = maxwell3d.regs.vertex_array[index].stride;
+ const u32 offset = buffer.Offset(binding.cpu_addr);
+ runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride);
+ }
+}
- const VAddr addr_end = addr + size;
- auto it = mapped_addresses.lower_bound(addr);
- if (it != mapped_addresses.begin()) {
- --it;
+template <class P>
+void BufferCache<P>::BindHostGraphicsUniformBuffers(size_t stage) {
+ u32 dirty = ~0U;
+ if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
+ dirty = std::exchange(dirty_uniform_buffers[stage], 0);
+ }
+ u32 binding_index = 0;
+ ForEachEnabledBit(enabled_uniform_buffers[stage], [&](u32 index) {
+ const bool needs_bind = ((dirty >> index) & 1) != 0;
+ BindHostGraphicsUniformBuffer(stage, index, binding_index, needs_bind);
+ if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
+ ++binding_index;
}
- while (it != mapped_addresses.end() && it->start < addr_end) {
- if (it->Overlaps(addr, addr_end)) {
- result.push_back(&*it);
+ });
+}
+
+template <class P>
+void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index,
+ bool needs_bind) {
+ const Binding& binding = uniform_buffers[stage][index];
+ const VAddr cpu_addr = binding.cpu_addr;
+ const u32 size = binding.size;
+ Buffer& buffer = slot_buffers[binding.buffer_id];
+ if (size <= SKIP_CACHE_SIZE && !buffer.IsRegionGpuModified(cpu_addr, size)) {
+ if constexpr (IS_OPENGL) {
+ if (runtime.HasFastBufferSubData()) {
+ // Fast path for Nvidia
+ if (!HasFastUniformBufferBound(stage, binding_index)) {
+ // We only have to bind when the currently bound buffer is not the fast version
+ runtime.BindFastUniformBuffer(stage, binding_index, size);
+ }
+ const auto span = ImmediateBufferWithData(cpu_addr, size);
+ runtime.PushFastUniformBuffer(stage, binding_index, span);
+ return;
}
- ++it;
}
- return result;
- }
+ fast_bound_uniform_buffers[stage] |= 1U << binding_index;
- /// Returns a ticks counter used for tracking when cached objects were last modified
- u64 GetModifiedTicks() {
- return ++modified_ticks;
+ // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan
+ const std::span<u8> span = runtime.BindMappedUniformBuffer(stage, binding_index, size);
+ cpu_memory.ReadBlockUnsafe(cpu_addr, span.data(), size);
+ return;
}
-
- void FlushMap(MapInterval* map) {
- const auto it = blocks.find(map->start >> BLOCK_PAGE_BITS);
- ASSERT_OR_EXECUTE(it != blocks.end(), return;);
-
- std::shared_ptr<Buffer> block = it->second;
-
- const std::size_t size = map->end - map->start;
- staging_buffer.resize(size);
- block->Download(block->Offset(map->start), size, staging_buffer.data());
- system.Memory().WriteBlockUnsafe(map->start, staging_buffer.data(), size);
- map->MarkAsModified(false, 0);
+ // Classic cached path
+ SynchronizeBuffer(buffer, cpu_addr, size);
+ if (!needs_bind && !HasFastUniformBufferBound(stage, binding_index)) {
+ // Skip binding if it's not needed and if the bound buffer is not the fast version
+ // This exists to avoid instances where the fast buffer is bound and a GPU write happens
+ return;
}
+ fast_bound_uniform_buffers[stage] &= ~(1U << binding_index);
- template <typename Callable>
- BufferInfo StreamBufferUpload(std::size_t size, std::size_t alignment, Callable&& callable) {
- AlignBuffer(alignment);
- const std::size_t uploaded_offset = buffer_offset;
- callable(buffer_ptr);
-
- buffer_ptr += size;
- buffer_offset += size;
- return BufferInfo{stream_buffer->Handle(), uploaded_offset, stream_buffer->Address()};
+ const u32 offset = buffer.Offset(cpu_addr);
+ if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
+ runtime.BindUniformBuffer(stage, binding_index, buffer, offset, size);
+ } else {
+ runtime.BindUniformBuffer(buffer, offset, size);
}
+}
+
+template <class P>
+void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
+ u32 binding_index = 0;
+ ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) {
+ const Binding& binding = storage_buffers[stage][index];
+ Buffer& buffer = slot_buffers[binding.buffer_id];
+ const u32 size = binding.size;
+ SynchronizeBuffer(buffer, binding.cpu_addr, size);
+
+ const u32 offset = buffer.Offset(binding.cpu_addr);
+ const bool is_written = ((written_storage_buffers[stage] >> index) & 1) != 0;
+ if constexpr (NEEDS_BIND_STORAGE_INDEX) {
+ runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
+ ++binding_index;
+ } else {
+ runtime.BindStorageBuffer(buffer, offset, size, is_written);
+ }
+ });
+}
- void AlignBuffer(std::size_t alignment) {
- // Align the offset, not the mapped pointer
- const std::size_t offset_aligned = Common::AlignUp(buffer_offset, alignment);
- buffer_ptr += offset_aligned - buffer_offset;
- buffer_offset = offset_aligned;
+template <class P>
+void BufferCache<P>::BindHostTransformFeedbackBuffers() {
+ if (maxwell3d.regs.tfb_enabled == 0) {
+ return;
}
+ for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
+ const Binding& binding = transform_feedback_buffers[index];
+ Buffer& buffer = slot_buffers[binding.buffer_id];
+ const u32 size = binding.size;
+ SynchronizeBuffer(buffer, binding.cpu_addr, size);
+
+ const u32 offset = buffer.Offset(binding.cpu_addr);
+ runtime.BindTransformFeedbackBuffer(index, buffer, offset, size);
+ }
+}
- std::shared_ptr<Buffer> EnlargeBlock(std::shared_ptr<Buffer> buffer) {
- const std::size_t old_size = buffer->Size();
- const std::size_t new_size = old_size + BLOCK_PAGE_SIZE;
- const VAddr cpu_addr = buffer->CpuAddr();
- std::shared_ptr<Buffer> new_buffer = CreateBlock(cpu_addr, new_size);
- new_buffer->CopyFrom(*buffer, 0, 0, old_size);
- QueueDestruction(std::move(buffer));
-
- const VAddr cpu_addr_end = cpu_addr + new_size - 1;
- const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
- for (u64 page_start = cpu_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
- blocks.insert_or_assign(page_start, new_buffer);
+template <class P>
+void BufferCache<P>::BindHostComputeUniformBuffers() {
+ if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
+ // Mark all uniform buffers as dirty
+ dirty_uniform_buffers.fill(~u32{0});
+ }
+ u32 binding_index = 0;
+ ForEachEnabledBit(enabled_compute_uniform_buffers, [&](u32 index) {
+ const Binding& binding = compute_uniform_buffers[index];
+ Buffer& buffer = slot_buffers[binding.buffer_id];
+ const u32 size = binding.size;
+ SynchronizeBuffer(buffer, binding.cpu_addr, size);
+
+ const u32 offset = buffer.Offset(binding.cpu_addr);
+ if constexpr (NEEDS_BIND_UNIFORM_INDEX) {
+ runtime.BindComputeUniformBuffer(binding_index, buffer, offset, size);
+ ++binding_index;
+ } else {
+ runtime.BindUniformBuffer(buffer, offset, size);
}
+ });
+}
+
+template <class P>
+void BufferCache<P>::BindHostComputeStorageBuffers() {
+ u32 binding_index = 0;
+ ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) {
+ const Binding& binding = compute_storage_buffers[index];
+ Buffer& buffer = slot_buffers[binding.buffer_id];
+ const u32 size = binding.size;
+ SynchronizeBuffer(buffer, binding.cpu_addr, size);
+
+ const u32 offset = buffer.Offset(binding.cpu_addr);
+ const bool is_written = ((written_compute_storage_buffers >> index) & 1) != 0;
+ if constexpr (NEEDS_BIND_STORAGE_INDEX) {
+ runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
+ ++binding_index;
+ } else {
+ runtime.BindStorageBuffer(buffer, offset, size, is_written);
+ }
+ });
+}
- return new_buffer;
+template <class P>
+void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
+ if (is_indexed) {
+ UpdateIndexBuffer();
}
+ UpdateVertexBuffers();
+ UpdateTransformFeedbackBuffers();
+ for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
+ UpdateUniformBuffers(stage);
+ UpdateStorageBuffers(stage);
+ }
+}
+
+template <class P>
+void BufferCache<P>::DoUpdateComputeBuffers() {
+ UpdateComputeUniformBuffers();
+ UpdateComputeStorageBuffers();
+}
+
+template <class P>
+void BufferCache<P>::UpdateIndexBuffer() {
+ // We have to check for the dirty flags and index count
+ // The index count is currently changed without updating the dirty flags
+ const auto& index_array = maxwell3d.regs.index_array;
+ auto& flags = maxwell3d.dirty.flags;
+ if (!flags[Dirty::IndexBuffer] && last_index_count == index_array.count) {
+ return;
+ }
+ flags[Dirty::IndexBuffer] = false;
+ last_index_count = index_array.count;
+
+ const GPUVAddr gpu_addr_begin = index_array.StartAddress();
+ const GPUVAddr gpu_addr_end = index_array.EndAddress();
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
+ const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
+ const u32 draw_size = index_array.count * index_array.FormatSizeInBytes();
+ const u32 size = std::min(address_size, draw_size);
+ if (size == 0 || !cpu_addr) {
+ index_buffer = NULL_BINDING;
+ return;
+ }
+ index_buffer = Binding{
+ .cpu_addr = *cpu_addr,
+ .size = size,
+ .buffer_id = FindBuffer(*cpu_addr, size),
+ };
+}
- std::shared_ptr<Buffer> MergeBlocks(std::shared_ptr<Buffer> first,
- std::shared_ptr<Buffer> second) {
- const std::size_t size_1 = first->Size();
- const std::size_t size_2 = second->Size();
- const VAddr first_addr = first->CpuAddr();
- const VAddr second_addr = second->CpuAddr();
- const VAddr new_addr = std::min(first_addr, second_addr);
- const std::size_t new_size = size_1 + size_2;
-
- std::shared_ptr<Buffer> new_buffer = CreateBlock(new_addr, new_size);
- new_buffer->CopyFrom(*first, 0, new_buffer->Offset(first_addr), size_1);
- new_buffer->CopyFrom(*second, 0, new_buffer->Offset(second_addr), size_2);
- QueueDestruction(std::move(first));
- QueueDestruction(std::move(second));
+template <class P>
+void BufferCache<P>::UpdateVertexBuffers() {
+ auto& flags = maxwell3d.dirty.flags;
+ if (!maxwell3d.dirty.flags[Dirty::VertexBuffers]) {
+ return;
+ }
+ flags[Dirty::VertexBuffers] = false;
- const VAddr cpu_addr_end = new_addr + new_size - 1;
- const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
- for (u64 page_start = new_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
- blocks.insert_or_assign(page_start, new_buffer);
- }
- return new_buffer;
+ for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
+ UpdateVertexBuffer(index);
}
+}
- Buffer* GetBlock(VAddr cpu_addr, std::size_t size) {
- std::shared_ptr<Buffer> found;
+template <class P>
+void BufferCache<P>::UpdateVertexBuffer(u32 index) {
+ if (!maxwell3d.dirty.flags[Dirty::VertexBuffer0 + index]) {
+ return;
+ }
+ const auto& array = maxwell3d.regs.vertex_array[index];
+ const auto& limit = maxwell3d.regs.vertex_array_limit[index];
+ const GPUVAddr gpu_addr_begin = array.StartAddress();
+ const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1;
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
+ const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
+ const u32 size = address_size; // TODO: Analyze stride and number of vertices
+ if (array.enable == 0 || size == 0 || !cpu_addr) {
+ vertex_buffers[index] = NULL_BINDING;
+ return;
+ }
+ vertex_buffers[index] = Binding{
+ .cpu_addr = *cpu_addr,
+ .size = size,
+ .buffer_id = FindBuffer(*cpu_addr, size),
+ };
+}
+
+template <class P>
+void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
+ ForEachEnabledBit(enabled_uniform_buffers[stage], [&](u32 index) {
+ Binding& binding = uniform_buffers[stage][index];
+ if (binding.buffer_id) {
+ // Already updated
+ return;
+ }
+ // Mark as dirty
+ if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
+ dirty_uniform_buffers[stage] |= 1U << index;
+ }
+ // Resolve buffer
+ binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
+ });
+}
+
+template <class P>
+void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
+ const u32 written_mask = written_storage_buffers[stage];
+ ForEachEnabledBit(enabled_storage_buffers[stage], [&](u32 index) {
+ // Resolve buffer
+ Binding& binding = storage_buffers[stage][index];
+ const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
+ binding.buffer_id = buffer_id;
+ // Mark buffer as written if needed
+ if (((written_mask >> index) & 1) != 0) {
+ MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
+ }
+ });
+}
- const VAddr cpu_addr_end = cpu_addr + size - 1;
- const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
- for (u64 page_start = cpu_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
- auto it = blocks.find(page_start);
- if (it == blocks.end()) {
- if (found) {
- found = EnlargeBlock(found);
- continue;
- }
- const VAddr start_addr = page_start << BLOCK_PAGE_BITS;
- found = CreateBlock(start_addr, BLOCK_PAGE_SIZE);
- blocks.insert_or_assign(page_start, found);
- continue;
- }
- if (!found) {
- found = it->second;
- continue;
- }
- if (found != it->second) {
- found = MergeBlocks(std::move(found), it->second);
+template <class P>
+void BufferCache<P>::UpdateTransformFeedbackBuffers() {
+ if (maxwell3d.regs.tfb_enabled == 0) {
+ return;
+ }
+ for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
+ UpdateTransformFeedbackBuffer(index);
+ }
+}
+
+template <class P>
+void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
+ const auto& binding = maxwell3d.regs.tfb_bindings[index];
+ const GPUVAddr gpu_addr = binding.Address() + binding.buffer_offset;
+ const u32 size = binding.buffer_size;
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ if (binding.buffer_enable == 0 || size == 0 || !cpu_addr) {
+ transform_feedback_buffers[index] = NULL_BINDING;
+ return;
+ }
+ const BufferId buffer_id = FindBuffer(*cpu_addr, size);
+ transform_feedback_buffers[index] = Binding{
+ .cpu_addr = *cpu_addr,
+ .size = size,
+ .buffer_id = buffer_id,
+ };
+ MarkWrittenBuffer(buffer_id, *cpu_addr, size);
+}
+
+template <class P>
+void BufferCache<P>::UpdateComputeUniformBuffers() {
+ ForEachEnabledBit(enabled_compute_uniform_buffers, [&](u32 index) {
+ Binding& binding = compute_uniform_buffers[index];
+ binding = NULL_BINDING;
+ const auto& launch_desc = kepler_compute.launch_description;
+ if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) {
+ const auto& cbuf = launch_desc.const_buffer_config[index];
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(cbuf.Address());
+ if (cpu_addr) {
+ binding.cpu_addr = *cpu_addr;
+ binding.size = cbuf.size;
}
}
- return found.get();
+ binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
+ });
+}
+
+template <class P>
+void BufferCache<P>::UpdateComputeStorageBuffers() {
+ ForEachEnabledBit(enabled_compute_storage_buffers, [&](u32 index) {
+ // Resolve buffer
+ Binding& binding = compute_storage_buffers[index];
+ const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
+ binding.buffer_id = buffer_id;
+ // Mark as written if needed
+ if (((written_compute_storage_buffers >> index) & 1) != 0) {
+ MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
+ }
+ });
+}
+
+template <class P>
+void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
+ Buffer& buffer = slot_buffers[buffer_id];
+ buffer.MarkRegionAsGpuModified(cpu_addr, size);
+
+ const bool is_accuracy_high = Settings::IsGPULevelHigh();
+ const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue();
+ if (!is_accuracy_high || !is_async) {
+ return;
+ }
+ if (std::ranges::find(uncommitted_downloads, buffer_id) != uncommitted_downloads.end()) {
+ // Already inserted
+ return;
}
+ uncommitted_downloads.push_back(buffer_id);
+}
- void MarkRegionAsWritten(VAddr start, VAddr end) {
- const u64 page_end = end >> WRITE_PAGE_BIT;
- for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
- if (const auto [it, inserted] = written_pages.emplace(page_start, 1); !inserted) {
- ++it->second;
- }
+template <class P>
+BufferId BufferCache<P>::FindBuffer(VAddr cpu_addr, u32 size) {
+ if (cpu_addr == 0) {
+ return NULL_BUFFER_ID;
+ }
+ const u64 page = cpu_addr >> PAGE_BITS;
+ const BufferId buffer_id = page_table[page];
+ if (!buffer_id) {
+ return CreateBuffer(cpu_addr, size);
+ }
+ const Buffer& buffer = slot_buffers[buffer_id];
+ if (buffer.IsInBounds(cpu_addr, size)) {
+ return buffer_id;
+ }
+ return CreateBuffer(cpu_addr, size);
+}
+
+template <class P>
+typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu_addr,
+ u32 wanted_size) {
+ static constexpr int STREAM_LEAP_THRESHOLD = 16;
+ std::vector<BufferId> overlap_ids;
+ VAddr begin = cpu_addr;
+ VAddr end = cpu_addr + wanted_size;
+ int stream_score = 0;
+ bool has_stream_leap = false;
+ for (; cpu_addr >> PAGE_BITS < Common::DivCeil(end, PAGE_SIZE); cpu_addr += PAGE_SIZE) {
+ const BufferId overlap_id = page_table[cpu_addr >> PAGE_BITS];
+ if (!overlap_id) {
+ continue;
+ }
+ Buffer& overlap = slot_buffers[overlap_id];
+ if (overlap.IsPicked()) {
+ continue;
+ }
+ overlap_ids.push_back(overlap_id);
+ overlap.Pick();
+ const VAddr overlap_cpu_addr = overlap.CpuAddr();
+ if (overlap_cpu_addr < begin) {
+ cpu_addr = begin = overlap_cpu_addr;
+ }
+ end = std::max(end, overlap_cpu_addr + overlap.SizeBytes());
+
+ stream_score += overlap.StreamScore();
+ if (stream_score > STREAM_LEAP_THRESHOLD && !has_stream_leap) {
+ // When this memory region has been joined a bunch of times, we assume it's being used
+ // as a stream buffer. Increase the size to skip constantly recreating buffers.
+ has_stream_leap = true;
+ end += PAGE_SIZE * 256;
}
}
-
- void UnmarkRegionAsWritten(VAddr start, VAddr end) {
- const u64 page_end = end >> WRITE_PAGE_BIT;
- for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
- auto it = written_pages.find(page_start);
- if (it != written_pages.end()) {
- if (it->second > 1) {
- --it->second;
- } else {
- written_pages.erase(it);
- }
- }
+ return OverlapResult{
+ .ids = std::move(overlap_ids),
+ .begin = begin,
+ .end = end,
+ .has_stream_leap = has_stream_leap,
+ };
+}
+
+template <class P>
+void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
+ bool accumulate_stream_score) {
+ Buffer& new_buffer = slot_buffers[new_buffer_id];
+ Buffer& overlap = slot_buffers[overlap_id];
+ if (accumulate_stream_score) {
+ new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1);
+ }
+ std::vector<BufferCopy> copies;
+ const size_t dst_base_offset = overlap.CpuAddr() - new_buffer.CpuAddr();
+ overlap.ForEachDownloadRange([&](u64 begin, u64 range_size) {
+ copies.push_back(BufferCopy{
+ .src_offset = begin,
+ .dst_offset = dst_base_offset + begin,
+ .size = range_size,
+ });
+ new_buffer.UnmarkRegionAsCpuModified(begin, range_size);
+ new_buffer.MarkRegionAsGpuModified(begin, range_size);
+ });
+ if (!copies.empty()) {
+ runtime.CopyBuffer(slot_buffers[new_buffer_id], overlap, copies);
+ }
+ ReplaceBufferDownloads(overlap_id, new_buffer_id);
+ DeleteBuffer(overlap_id);
+}
+
+template <class P>
+BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
+ const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);
+ const u32 size = static_cast<u32>(overlap.end - overlap.begin);
+ const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
+ for (const BufferId overlap_id : overlap.ids) {
+ JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
+ }
+ Register(new_buffer_id);
+ return new_buffer_id;
+}
+
+template <class P>
+void BufferCache<P>::Register(BufferId buffer_id) {
+ ChangeRegister<true>(buffer_id);
+}
+
+template <class P>
+void BufferCache<P>::Unregister(BufferId buffer_id) {
+ ChangeRegister<false>(buffer_id);
+}
+
+template <class P>
+template <bool insert>
+void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
+ const Buffer& buffer = slot_buffers[buffer_id];
+ const VAddr cpu_addr_begin = buffer.CpuAddr();
+ const VAddr cpu_addr_end = cpu_addr_begin + buffer.SizeBytes();
+ const u64 page_begin = cpu_addr_begin / PAGE_SIZE;
+ const u64 page_end = Common::DivCeil(cpu_addr_end, PAGE_SIZE);
+ for (u64 page = page_begin; page != page_end; ++page) {
+ if constexpr (insert) {
+ page_table[page] = buffer_id;
+ } else {
+ page_table[page] = BufferId{};
}
}
+}
- bool IsRegionWritten(VAddr start, VAddr end) const {
- const u64 page_end = end >> WRITE_PAGE_BIT;
- for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
- if (written_pages.count(page_start) > 0) {
- return true;
+template <class P>
+void BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) {
+ if (buffer.CpuAddr() == 0) {
+ return;
+ }
+ SynchronizeBufferImpl(buffer, cpu_addr, size);
+}
+
+template <class P>
+void BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size) {
+ boost::container::small_vector<BufferCopy, 4> copies;
+ u64 total_size_bytes = 0;
+ u64 largest_copy = 0;
+ buffer.ForEachUploadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) {
+ copies.push_back(BufferCopy{
+ .src_offset = total_size_bytes,
+ .dst_offset = range_offset,
+ .size = range_size,
+ });
+ total_size_bytes += range_size;
+ largest_copy = std::max(largest_copy, range_size);
+ });
+ if (total_size_bytes == 0) {
+ return;
+ }
+ const std::span<BufferCopy> copies_span(copies.data(), copies.size());
+ UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
+}
+
+template <class P>
+void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
+ std::span<BufferCopy> copies) {
+ if constexpr (USE_MEMORY_MAPS) {
+ MappedUploadMemory(buffer, total_size_bytes, copies);
+ } else {
+ ImmediateUploadMemory(buffer, largest_copy, copies);
+ }
+}
+
+template <class P>
+void BufferCache<P>::ImmediateUploadMemory(Buffer& buffer, u64 largest_copy,
+ std::span<const BufferCopy> copies) {
+ std::span<u8> immediate_buffer;
+ for (const BufferCopy& copy : copies) {
+ std::span<const u8> upload_span;
+ const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset;
+ if (IsRangeGranular(cpu_addr, copy.size)) {
+ upload_span = std::span(cpu_memory.GetPointer(cpu_addr), copy.size);
+ } else {
+ if (immediate_buffer.empty()) {
+ immediate_buffer = ImmediateBuffer(largest_copy);
}
+ cpu_memory.ReadBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size);
+ upload_span = immediate_buffer.subspan(0, copy.size);
}
- return false;
+ buffer.ImmediateUpload(copy.dst_offset, upload_span);
}
-
- void QueueDestruction(std::shared_ptr<Buffer> buffer) {
- buffer->SetEpoch(epoch);
- pending_destruction.push(std::move(buffer));
+}
+
+template <class P>
+void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes,
+ std::span<BufferCopy> copies) {
+ auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes);
+ const std::span<u8> staging_pointer = upload_staging.mapped_span;
+ for (BufferCopy& copy : copies) {
+ u8* const src_pointer = staging_pointer.data() + copy.src_offset;
+ const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset;
+ cpu_memory.ReadBlockUnsafe(cpu_addr, src_pointer, copy.size);
+
+ // Apply the staging offset
+ copy.src_offset += upload_staging.offset;
}
-
- void MarkForAsyncFlush(MapInterval* map) {
- if (!uncommitted_flushes) {
- uncommitted_flushes = std::make_shared<std::unordered_set<MapInterval*>>();
+ runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
+}
+
+template <class P>
+void BufferCache<P>::DeleteBuffer(BufferId buffer_id) {
+ const auto scalar_replace = [buffer_id](Binding& binding) {
+ if (binding.buffer_id == buffer_id) {
+ binding.buffer_id = BufferId{};
+ }
+ };
+ const auto replace = [scalar_replace](std::span<Binding> bindings) {
+ std::ranges::for_each(bindings, scalar_replace);
+ };
+ scalar_replace(index_buffer);
+ replace(vertex_buffers);
+ std::ranges::for_each(uniform_buffers, replace);
+ std::ranges::for_each(storage_buffers, replace);
+ replace(transform_feedback_buffers);
+ replace(compute_uniform_buffers);
+ replace(compute_storage_buffers);
+ std::erase(cached_write_buffer_ids, buffer_id);
+
+ // Mark the whole buffer as CPU written to stop tracking CPU writes
+ Buffer& buffer = slot_buffers[buffer_id];
+ buffer.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes());
+
+ Unregister(buffer_id);
+ delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
+
+ NotifyBufferDeletion();
+}
+
+template <class P>
+void BufferCache<P>::ReplaceBufferDownloads(BufferId old_buffer_id, BufferId new_buffer_id) {
+ const auto replace = [old_buffer_id, new_buffer_id](std::vector<BufferId>& buffers) {
+ std::ranges::replace(buffers, old_buffer_id, new_buffer_id);
+ if (auto it = std::ranges::find(buffers, new_buffer_id); it != buffers.end()) {
+ buffers.erase(std::remove(it + 1, buffers.end(), new_buffer_id), buffers.end());
}
- uncommitted_flushes->insert(map);
+ };
+ replace(uncommitted_downloads);
+ std::ranges::for_each(committed_downloads, replace);
+}
+
+template <class P>
+void BufferCache<P>::NotifyBufferDeletion() {
+ if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
+ dirty_uniform_buffers.fill(~u32{0});
}
+ auto& flags = maxwell3d.dirty.flags;
+ flags[Dirty::IndexBuffer] = true;
+ flags[Dirty::VertexBuffers] = true;
+ for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
+ flags[Dirty::VertexBuffer0 + index] = true;
+ }
+ has_deleted_buffers = true;
+}
+
+template <class P>
+typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr) const {
+ const GPUVAddr gpu_addr = gpu_memory.Read<u64>(ssbo_addr);
+ const u32 size = gpu_memory.Read<u32>(ssbo_addr + 8);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ if (!cpu_addr || size == 0) {
+ return NULL_BINDING;
+ }
+ // HACK(Rodrigo): This is the number of bytes bound in host beyond the guest API's range.
+ // It exists due to some games like Astral Chain operate out of bounds.
+ // Binding the whole map range would be technically correct, but games have large maps that make
+ // this approach unaffordable for now.
+ static constexpr u32 arbitrary_extra_bytes = 0xc000;
+ const u32 bytes_to_map_end = static_cast<u32>(gpu_memory.BytesToMapEnd(gpu_addr));
+ const Binding binding{
+ .cpu_addr = *cpu_addr,
+ .size = std::min(size + arbitrary_extra_bytes, bytes_to_map_end),
+ .buffer_id = BufferId{},
+ };
+ return binding;
+}
+
+template <class P>
+std::span<const u8> BufferCache<P>::ImmediateBufferWithData(VAddr cpu_addr, size_t size) {
+ u8* const base_pointer = cpu_memory.GetPointer(cpu_addr);
+ if (IsRangeGranular(cpu_addr, size) ||
+ base_pointer + size == cpu_memory.GetPointer(cpu_addr + size)) {
+ return std::span(base_pointer, size);
+ } else {
+ const std::span<u8> span = ImmediateBuffer(size);
+ cpu_memory.ReadBlockUnsafe(cpu_addr, span.data(), size);
+ return span;
+ }
+}
- VideoCore::RasterizerInterface& rasterizer;
- Core::System& system;
-
- std::unique_ptr<StreamBuffer> stream_buffer;
- BufferType stream_buffer_handle;
-
- u8* buffer_ptr = nullptr;
- u64 buffer_offset = 0;
- u64 buffer_offset_base = 0;
-
- MapIntervalAllocator mapped_addresses_allocator;
- boost::intrusive::set<MapInterval, boost::intrusive::compare<MapIntervalCompare>>
- mapped_addresses;
-
- std::unordered_map<u64, u32> written_pages;
- std::unordered_map<u64, std::shared_ptr<Buffer>> blocks;
-
- std::queue<std::shared_ptr<Buffer>> pending_destruction;
- u64 epoch = 0;
- u64 modified_ticks = 0;
-
- std::vector<u8> staging_buffer;
-
- std::list<MapInterval*> marked_for_unregister;
-
- std::shared_ptr<std::unordered_set<MapInterval*>> uncommitted_flushes;
- std::list<std::shared_ptr<std::list<MapInterval*>>> committed_flushes;
-
- std::recursive_mutex mutex;
-};
+template <class P>
+std::span<u8> BufferCache<P>::ImmediateBuffer(size_t wanted_capacity) {
+ if (wanted_capacity > immediate_buffer_capacity) {
+ immediate_buffer_capacity = wanted_capacity;
+ immediate_buffer_alloc = std::make_unique<u8[]>(wanted_capacity);
+ }
+ return std::span<u8>(immediate_buffer_alloc.get(), wanted_capacity);
+}
+
+template <class P>
+bool BufferCache<P>::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept {
+ if constexpr (IS_OPENGL) {
+ return ((fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0;
+ } else {
+ // Only OpenGL has fast uniform buffers
+ return false;
+ }
+}
} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/map_interval.cpp b/src/video_core/buffer_cache/map_interval.cpp
deleted file mode 100644
index 62587e18a..000000000
--- a/src/video_core/buffer_cache/map_interval.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <array>
-#include <cstddef>
-#include <memory>
-
-#include "video_core/buffer_cache/map_interval.h"
-
-namespace VideoCommon {
-
-MapIntervalAllocator::MapIntervalAllocator() {
- FillFreeList(first_chunk);
-}
-
-MapIntervalAllocator::~MapIntervalAllocator() = default;
-
-void MapIntervalAllocator::AllocateNewChunk() {
- *new_chunk = std::make_unique<Chunk>();
- FillFreeList(**new_chunk);
- new_chunk = &(*new_chunk)->next;
-}
-
-void MapIntervalAllocator::FillFreeList(Chunk& chunk) {
- const std::size_t old_size = free_list.size();
- free_list.resize(old_size + chunk.data.size());
- std::transform(chunk.data.rbegin(), chunk.data.rend(), free_list.begin() + old_size,
- [](MapInterval& interval) { return &interval; });
-}
-
-} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/map_interval.h b/src/video_core/buffer_cache/map_interval.h
deleted file mode 100644
index fe0bcd1d8..000000000
--- a/src/video_core/buffer_cache/map_interval.h
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <cstddef>
-#include <memory>
-#include <vector>
-
-#include <boost/intrusive/set_hook.hpp>
-
-#include "common/common_types.h"
-#include "video_core/gpu.h"
-
-namespace VideoCommon {
-
-struct MapInterval : public boost::intrusive::set_base_hook<boost::intrusive::optimize_size<true>> {
- MapInterval() = default;
-
- /*implicit*/ MapInterval(VAddr start_) noexcept : start{start_} {}
-
- explicit MapInterval(VAddr start_, VAddr end_, GPUVAddr gpu_addr_) noexcept
- : start{start_}, end{end_}, gpu_addr{gpu_addr_} {}
-
- bool IsInside(VAddr other_start, VAddr other_end) const noexcept {
- return start <= other_start && other_end <= end;
- }
-
- bool Overlaps(VAddr other_start, VAddr other_end) const noexcept {
- return start < other_end && other_start < end;
- }
-
- void MarkAsModified(bool is_modified_, u64 ticks_) noexcept {
- is_modified = is_modified_;
- ticks = ticks_;
- }
-
- boost::intrusive::set_member_hook<> member_hook_;
- VAddr start = 0;
- VAddr end = 0;
- GPUVAddr gpu_addr = 0;
- u64 ticks = 0;
- bool is_written = false;
- bool is_modified = false;
- bool is_registered = false;
- bool is_memory_marked = false;
- bool is_sync_pending = false;
-};
-
-struct MapIntervalCompare {
- constexpr bool operator()(const MapInterval& lhs, const MapInterval& rhs) const noexcept {
- return lhs.start < rhs.start;
- }
-};
-
-class MapIntervalAllocator {
-public:
- MapIntervalAllocator();
- ~MapIntervalAllocator();
-
- MapInterval* Allocate() {
- if (free_list.empty()) {
- AllocateNewChunk();
- }
- MapInterval* const interval = free_list.back();
- free_list.pop_back();
- return interval;
- }
-
- void Release(MapInterval* interval) {
- free_list.push_back(interval);
- }
-
-private:
- struct Chunk {
- std::unique_ptr<Chunk> next;
- std::array<MapInterval, 0x8000> data;
- };
-
- void AllocateNewChunk();
-
- void FillFreeList(Chunk& chunk);
-
- std::vector<MapInterval*> free_list;
- std::unique_ptr<Chunk>* new_chunk = &first_chunk.next;
-
- Chunk first_chunk;
-};
-
-} // namespace VideoCommon
diff --git a/src/video_core/cdma_pusher.cpp b/src/video_core/cdma_pusher.cpp
new file mode 100644
index 000000000..33b3c060b
--- /dev/null
+++ b/src/video_core/cdma_pusher.cpp
@@ -0,0 +1,170 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include <bit>
+#include "command_classes/host1x.h"
+#include "command_classes/nvdec.h"
+#include "command_classes/vic.h"
+#include "video_core/cdma_pusher.h"
+#include "video_core/command_classes/nvdec_common.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra {
+CDmaPusher::CDmaPusher(GPU& gpu_)
+ : gpu{gpu_}, nvdec_processor(std::make_shared<Nvdec>(gpu)),
+ vic_processor(std::make_unique<Vic>(gpu, nvdec_processor)),
+ host1x_processor(std::make_unique<Host1x>(gpu)),
+ sync_manager(std::make_unique<SyncptIncrManager>(gpu)) {}
+
+CDmaPusher::~CDmaPusher() = default;
+
+void CDmaPusher::Push(ChCommandHeaderList&& entries) {
+ cdma_queue.push(std::move(entries));
+}
+
+void CDmaPusher::DispatchCalls() {
+ while (!cdma_queue.empty()) {
+ Step();
+ }
+}
+
+void CDmaPusher::Step() {
+ const auto entries{cdma_queue.front()};
+ cdma_queue.pop();
+
+ std::vector<u32> values(entries.size());
+ std::memcpy(values.data(), entries.data(), entries.size() * sizeof(u32));
+
+ for (const u32 value : values) {
+ if (mask != 0) {
+ const auto lbs = static_cast<u32>(std::countr_zero(mask));
+ mask &= ~(1U << lbs);
+ ExecuteCommand(static_cast<u32>(offset + lbs), value);
+ continue;
+ } else if (count != 0) {
+ --count;
+ ExecuteCommand(static_cast<u32>(offset), value);
+ if (incrementing) {
+ ++offset;
+ }
+ continue;
+ }
+ const auto mode = static_cast<ChSubmissionMode>((value >> 28) & 0xf);
+ switch (mode) {
+ case ChSubmissionMode::SetClass: {
+ mask = value & 0x3f;
+ offset = (value >> 16) & 0xfff;
+ current_class = static_cast<ChClassId>((value >> 6) & 0x3ff);
+ break;
+ }
+ case ChSubmissionMode::Incrementing:
+ case ChSubmissionMode::NonIncrementing:
+ count = value & 0xffff;
+ offset = (value >> 16) & 0xfff;
+ incrementing = mode == ChSubmissionMode::Incrementing;
+ break;
+ case ChSubmissionMode::Mask:
+ mask = value & 0xffff;
+ offset = (value >> 16) & 0xfff;
+ break;
+ case ChSubmissionMode::Immediate: {
+ const u32 data = value & 0xfff;
+ offset = (value >> 16) & 0xfff;
+ ExecuteCommand(static_cast<u32>(offset), data);
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", static_cast<u32>(mode));
+ break;
+ }
+ }
+}
+
+void CDmaPusher::ExecuteCommand(u32 state_offset, u32 data) {
+ switch (current_class) {
+ case ChClassId::NvDec:
+ ThiStateWrite(nvdec_thi_state, state_offset, {data});
+ switch (static_cast<ThiMethod>(state_offset)) {
+ case ThiMethod::IncSyncpt: {
+ LOG_DEBUG(Service_NVDRV, "NVDEC Class IncSyncpt Method");
+ const auto syncpoint_id = static_cast<u32>(data & 0xFF);
+ const auto cond = static_cast<u32>((data >> 8) & 0xFF);
+ if (cond == 0) {
+ sync_manager->Increment(syncpoint_id);
+ } else {
+ sync_manager->SignalDone(
+ sync_manager->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id));
+ }
+ break;
+ }
+ case ThiMethod::SetMethod1:
+ LOG_DEBUG(Service_NVDRV, "NVDEC method 0x{:X}",
+ static_cast<u32>(nvdec_thi_state.method_0));
+ nvdec_processor->ProcessMethod(static_cast<Nvdec::Method>(nvdec_thi_state.method_0),
+ {data});
+ break;
+ default:
+ break;
+ }
+ break;
+ case ChClassId::GraphicsVic:
+ ThiStateWrite(vic_thi_state, static_cast<u32>(state_offset), {data});
+ switch (static_cast<ThiMethod>(state_offset)) {
+ case ThiMethod::IncSyncpt: {
+ LOG_DEBUG(Service_NVDRV, "VIC Class IncSyncpt Method");
+ const auto syncpoint_id = static_cast<u32>(data & 0xFF);
+ const auto cond = static_cast<u32>((data >> 8) & 0xFF);
+ if (cond == 0) {
+ sync_manager->Increment(syncpoint_id);
+ } else {
+ sync_manager->SignalDone(
+ sync_manager->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id));
+ }
+ break;
+ }
+ case ThiMethod::SetMethod1:
+ LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})",
+ static_cast<u32>(vic_thi_state.method_0), data);
+ vic_processor->ProcessMethod(static_cast<Vic::Method>(vic_thi_state.method_0), {data});
+ break;
+ default:
+ break;
+ }
+ break;
+ case ChClassId::Host1x:
+ // This device is mainly for syncpoint synchronization
+ LOG_DEBUG(Service_NVDRV, "Host1X Class Method");
+ host1x_processor->ProcessMethod(static_cast<Host1x::Method>(state_offset), {data});
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class));
+ break;
+ }
+}
+
+void CDmaPusher::ThiStateWrite(ThiRegisters& state, u32 state_offset,
+ const std::vector<u32>& arguments) {
+ u8* const state_offset_ptr = reinterpret_cast<u8*>(&state) + sizeof(u32) * state_offset;
+ std::memcpy(state_offset_ptr, arguments.data(), sizeof(u32) * arguments.size());
+}
+
+} // namespace Tegra
diff --git a/src/video_core/cdma_pusher.h b/src/video_core/cdma_pusher.h
new file mode 100644
index 000000000..e5f212c1a
--- /dev/null
+++ b/src/video_core/cdma_pusher.h
@@ -0,0 +1,136 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+#include <queue>
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "video_core/command_classes/sync_manager.h"
+
+namespace Tegra {
+
+class GPU;
+class Nvdec;
+class Vic;
+class Host1x;
+
+enum class ChSubmissionMode : u32 {
+ SetClass = 0,
+ Incrementing = 1,
+ NonIncrementing = 2,
+ Mask = 3,
+ Immediate = 4,
+ Restart = 5,
+ Gather = 6,
+};
+
+enum class ChClassId : u32 {
+ NoClass = 0x0,
+ Host1x = 0x1,
+ VideoEncodeMpeg = 0x20,
+ VideoEncodeNvEnc = 0x21,
+ VideoStreamingVi = 0x30,
+ VideoStreamingIsp = 0x32,
+ VideoStreamingIspB = 0x34,
+ VideoStreamingViI2c = 0x36,
+ GraphicsVic = 0x5d,
+ Graphics3D = 0x60,
+ GraphicsGpu = 0x61,
+ Tsec = 0xe0,
+ TsecB = 0xe1,
+ NvJpg = 0xc0,
+ NvDec = 0xf0
+};
+
+enum class ChMethod : u32 {
+ Empty = 0,
+ SetMethod = 0x10,
+ SetData = 0x11,
+};
+
+union ChCommandHeader {
+ u32 raw;
+ BitField<0, 16, u32> value;
+ BitField<16, 12, ChMethod> method_offset;
+ BitField<28, 4, ChSubmissionMode> submission_mode;
+};
+static_assert(sizeof(ChCommandHeader) == sizeof(u32), "ChCommand header is an invalid size");
+
+struct ChCommand {
+ ChClassId class_id{};
+ int method_offset{};
+ std::vector<u32> arguments;
+};
+
+using ChCommandHeaderList = std::vector<ChCommandHeader>;
+using ChCommandList = std::vector<ChCommand>;
+
+struct ThiRegisters {
+ u32_le increment_syncpt{};
+ INSERT_PADDING_WORDS(1);
+ u32_le increment_syncpt_error{};
+ u32_le ctx_switch_incremement_syncpt{};
+ INSERT_PADDING_WORDS(4);
+ u32_le ctx_switch{};
+ INSERT_PADDING_WORDS(1);
+ u32_le ctx_syncpt_eof{};
+ INSERT_PADDING_WORDS(5);
+ u32_le method_0{};
+ u32_le method_1{};
+ INSERT_PADDING_WORDS(12);
+ u32_le int_status{};
+ u32_le int_mask{};
+};
+
+enum class ThiMethod : u32 {
+ IncSyncpt = offsetof(ThiRegisters, increment_syncpt) / sizeof(u32),
+ SetMethod0 = offsetof(ThiRegisters, method_0) / sizeof(u32),
+ SetMethod1 = offsetof(ThiRegisters, method_1) / sizeof(u32),
+};
+
+class CDmaPusher {
+public:
+ explicit CDmaPusher(GPU& gpu_);
+ ~CDmaPusher();
+
+ /// Push NVDEC command buffer entries into queue
+ void Push(ChCommandHeaderList&& entries);
+
+ /// Process queued command buffer entries
+ void DispatchCalls();
+
+ /// Process one queue element
+ void Step();
+
+ /// Invoke command class devices to execute the command based on the current state
+ void ExecuteCommand(u32 state_offset, u32 data);
+
+private:
+ /// Write arguments value to the ThiRegisters member at the specified offset
+ void ThiStateWrite(ThiRegisters& state, u32 state_offset, const std::vector<u32>& arguments);
+
+ GPU& gpu;
+ std::shared_ptr<Tegra::Nvdec> nvdec_processor;
+ std::unique_ptr<Tegra::Vic> vic_processor;
+ std::unique_ptr<Tegra::Host1x> host1x_processor;
+ std::unique_ptr<SyncptIncrManager> sync_manager;
+ ChClassId current_class{};
+ ThiRegisters vic_thi_state{};
+ ThiRegisters nvdec_thi_state{};
+
+ s32 count{};
+ s32 offset{};
+ u32 mask{};
+ bool incrementing{};
+
+ // Queue of command lists to be processed
+ std::queue<ChCommandHeaderList> cdma_queue;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp
new file mode 100644
index 000000000..39bc923a5
--- /dev/null
+++ b/src/video_core/command_classes/codecs/codec.cpp
@@ -0,0 +1,129 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <fstream>
+#include <vector>
+#include "common/assert.h"
+#include "video_core/command_classes/codecs/codec.h"
+#include "video_core/command_classes/codecs/h264.h"
+#include "video_core/command_classes/codecs/vp9.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+extern "C" {
+#include <libavutil/opt.h>
+}
+
+namespace Tegra {
+
+void AVFrameDeleter(AVFrame* ptr) {
+ av_frame_unref(ptr);
+ av_free(ptr);
+}
+
+Codec::Codec(GPU& gpu_)
+ : gpu(gpu_), h264_decoder(std::make_unique<Decoder::H264>(gpu)),
+ vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {}
+
+Codec::~Codec() {
+ if (!initialized) {
+ return;
+ }
+ // Free libav memory
+ AVFrame* av_frame{nullptr};
+ avcodec_send_packet(av_codec_ctx, nullptr);
+ av_frame = av_frame_alloc();
+ avcodec_receive_frame(av_codec_ctx, av_frame);
+ avcodec_flush_buffers(av_codec_ctx);
+
+ av_frame_unref(av_frame);
+ av_free(av_frame);
+ avcodec_close(av_codec_ctx);
+}
+
+void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
+ LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", codec);
+ current_codec = codec;
+}
+
+void Codec::StateWrite(u32 offset, u64 arguments) {
+ u8* const state_offset = reinterpret_cast<u8*>(&state) + offset * sizeof(u64);
+ std::memcpy(state_offset, &arguments, sizeof(u64));
+}
+
+void Codec::Decode() {
+ bool is_first_frame = false;
+
+ if (!initialized) {
+ if (current_codec == NvdecCommon::VideoCodec::H264) {
+ av_codec = avcodec_find_decoder(AV_CODEC_ID_H264);
+ } else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
+ av_codec = avcodec_find_decoder(AV_CODEC_ID_VP9);
+ } else {
+ LOG_ERROR(Service_NVDRV, "Unknown video codec {}", current_codec);
+ return;
+ }
+
+ av_codec_ctx = avcodec_alloc_context3(av_codec);
+ av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
+
+ // TODO(ameerj): libavcodec gpu hw acceleration
+
+ const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
+ if (av_error < 0) {
+ LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
+ avcodec_close(av_codec_ctx);
+ return;
+ }
+ initialized = true;
+ is_first_frame = true;
+ }
+ bool vp9_hidden_frame = false;
+
+ AVPacket packet{};
+ av_init_packet(&packet);
+ std::vector<u8> frame_data;
+
+ if (current_codec == NvdecCommon::VideoCodec::H264) {
+ frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame);
+ } else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
+ frame_data = vp9_decoder->ComposeFrameHeader(state);
+ vp9_hidden_frame = vp9_decoder->WasFrameHidden();
+ }
+
+ packet.data = frame_data.data();
+ packet.size = static_cast<int>(frame_data.size());
+
+ avcodec_send_packet(av_codec_ctx, &packet);
+
+ if (!vp9_hidden_frame) {
+ // Only receive/store visible frames
+ AVFramePtr frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter};
+ avcodec_receive_frame(av_codec_ctx, frame.get());
+ av_frames.push(std::move(frame));
+ // Limit queue to 10 frames. Workaround for ZLA decode and queue spam
+ if (av_frames.size() > 10) {
+ av_frames.pop();
+ }
+ }
+}
+
+AVFramePtr Codec::GetCurrentFrame() {
+ // Sometimes VIC will request more frames than have been decoded.
+ // in this case, return a nullptr and don't overwrite previous frame data
+ if (av_frames.empty()) {
+ return AVFramePtr{nullptr, AVFrameDeleter};
+ }
+
+ AVFramePtr frame = std::move(av_frames.front());
+ av_frames.pop();
+ return frame;
+}
+
+NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
+ return current_codec;
+}
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h
new file mode 100644
index 000000000..8a2a6c360
--- /dev/null
+++ b/src/video_core/command_classes/codecs/codec.h
@@ -0,0 +1,70 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <queue>
+#include "common/common_types.h"
+#include "video_core/command_classes/nvdec_common.h"
+
+extern "C" {
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <libavcodec/avcodec.h>
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+}
+
+namespace Tegra {
+class GPU;
+struct VicRegisters;
+
+void AVFrameDeleter(AVFrame* ptr);
+using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
+
+namespace Decoder {
+class H264;
+class VP9;
+} // namespace Decoder
+
+class Codec {
+public:
+ explicit Codec(GPU& gpu);
+ ~Codec();
+
+ /// Sets NVDEC video stream codec
+ void SetTargetCodec(NvdecCommon::VideoCodec codec);
+
+ /// Populate NvdecRegisters state with argument value at the provided offset
+ void StateWrite(u32 offset, u64 arguments);
+
+ /// Call decoders to construct headers, decode AVFrame with ffmpeg
+ void Decode();
+
+ /// Returns next decoded frame
+ [[nodiscard]] AVFramePtr GetCurrentFrame();
+
+ /// Returns the value of current_codec
+ [[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const;
+
+private:
+ bool initialized{};
+ NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
+
+ AVCodec* av_codec{nullptr};
+ AVCodecContext* av_codec_ctx{nullptr};
+
+ GPU& gpu;
+ std::unique_ptr<Decoder::H264> h264_decoder;
+ std::unique_ptr<Decoder::VP9> vp9_decoder;
+
+ NvdecCommon::NvdecRegisters state{};
+ std::queue<AVFramePtr> av_frames{};
+};
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/h264.cpp b/src/video_core/command_classes/codecs/h264.cpp
new file mode 100644
index 000000000..fea6aed98
--- /dev/null
+++ b/src/video_core/command_classes/codecs/h264.cpp
@@ -0,0 +1,293 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include <array>
+#include <bit>
+#include "video_core/command_classes/codecs/h264.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra::Decoder {
+namespace {
+// ZigZag LUTs from libavcodec.
+constexpr std::array<u8, 64> zig_zag_direct{
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
+ 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
+ 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
+};
+
+constexpr std::array<u8, 16> zig_zag_scan{
+ 0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
+ 1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4,
+};
+} // Anonymous namespace
+
+H264::H264(GPU& gpu_) : gpu(gpu_) {}
+
+H264::~H264() = default;
+
+const std::vector<u8>& H264::ComposeFrameHeader(const NvdecCommon::NvdecRegisters& state,
+ bool is_first_frame) {
+ H264DecoderContext context{};
+ gpu.MemoryManager().ReadBlock(state.picture_info_offset, &context, sizeof(H264DecoderContext));
+
+ const s32 frame_number = static_cast<s32>((context.h264_parameter_set.flags >> 46) & 0x1ffff);
+ if (!is_first_frame && frame_number != 0) {
+ frame.resize(context.frame_data_size);
+
+ gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
+ } else {
+ /// Encode header
+ H264BitWriter writer{};
+ writer.WriteU(1, 24);
+ writer.WriteU(0, 1);
+ writer.WriteU(3, 2);
+ writer.WriteU(7, 5);
+ writer.WriteU(100, 8);
+ writer.WriteU(0, 8);
+ writer.WriteU(31, 8);
+ writer.WriteUe(0);
+ const auto chroma_format_idc =
+ static_cast<u32>((context.h264_parameter_set.flags >> 12) & 3);
+ writer.WriteUe(chroma_format_idc);
+ if (chroma_format_idc == 3) {
+ writer.WriteBit(false);
+ }
+
+ writer.WriteUe(0);
+ writer.WriteUe(0);
+ writer.WriteBit(false); // QpprimeYZeroTransformBypassFlag
+ writer.WriteBit(false); // Scaling matrix present flag
+
+ const auto order_cnt_type = static_cast<u32>((context.h264_parameter_set.flags >> 14) & 3);
+ writer.WriteUe(static_cast<u32>((context.h264_parameter_set.flags >> 8) & 0xf));
+ writer.WriteUe(order_cnt_type);
+ if (order_cnt_type == 0) {
+ writer.WriteUe(context.h264_parameter_set.log2_max_pic_order_cnt);
+ } else if (order_cnt_type == 1) {
+ writer.WriteBit(context.h264_parameter_set.delta_pic_order_always_zero_flag != 0);
+
+ writer.WriteSe(0);
+ writer.WriteSe(0);
+ writer.WriteUe(0);
+ }
+
+ const s32 pic_height = context.h264_parameter_set.pic_height_in_map_units /
+ (context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2);
+
+ writer.WriteUe(16);
+ writer.WriteBit(false);
+ writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1);
+ writer.WriteUe(pic_height - 1);
+ writer.WriteBit(context.h264_parameter_set.frame_mbs_only_flag != 0);
+
+ if (!context.h264_parameter_set.frame_mbs_only_flag) {
+ writer.WriteBit(((context.h264_parameter_set.flags >> 0) & 1) != 0);
+ }
+
+ writer.WriteBit(((context.h264_parameter_set.flags >> 1) & 1) != 0);
+ writer.WriteBit(false); // Frame cropping flag
+ writer.WriteBit(false); // VUI parameter present flag
+
+ writer.End();
+
+ // H264 PPS
+ writer.WriteU(1, 24);
+ writer.WriteU(0, 1);
+ writer.WriteU(3, 2);
+ writer.WriteU(8, 5);
+
+ writer.WriteUe(0);
+ writer.WriteUe(0);
+
+ writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag != 0);
+ writer.WriteBit(false);
+ writer.WriteUe(0);
+ writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active);
+ writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active);
+ writer.WriteBit(((context.h264_parameter_set.flags >> 2) & 1) != 0);
+ writer.WriteU(static_cast<s32>((context.h264_parameter_set.flags >> 32) & 0x3), 2);
+ s32 pic_init_qp = static_cast<s32>((context.h264_parameter_set.flags >> 16) & 0x3f);
+ pic_init_qp = (pic_init_qp << 26) >> 26;
+ writer.WriteSe(pic_init_qp);
+ writer.WriteSe(0);
+ s32 chroma_qp_index_offset =
+ static_cast<s32>((context.h264_parameter_set.flags >> 22) & 0x1f);
+ chroma_qp_index_offset = (chroma_qp_index_offset << 27) >> 27;
+
+ writer.WriteSe(chroma_qp_index_offset);
+ writer.WriteBit(context.h264_parameter_set.deblocking_filter_control_flag != 0);
+ writer.WriteBit(((context.h264_parameter_set.flags >> 3) & 1) != 0);
+ writer.WriteBit(context.h264_parameter_set.redundant_pic_count_flag != 0);
+ writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0);
+
+ writer.WriteBit(true);
+
+ for (s32 index = 0; index < 6; index++) {
+ writer.WriteBit(true);
+ const auto matrix_x4 =
+ std::vector<u8>(context.scaling_matrix_4.begin(), context.scaling_matrix_4.end());
+ writer.WriteScalingList(matrix_x4, index * 16, 16);
+ }
+
+ if (context.h264_parameter_set.transform_8x8_mode_flag) {
+ for (s32 index = 0; index < 2; index++) {
+ writer.WriteBit(true);
+ const auto matrix_x8 = std::vector<u8>(context.scaling_matrix_8.begin(),
+ context.scaling_matrix_8.end());
+
+ writer.WriteScalingList(matrix_x8, index * 64, 64);
+ }
+ }
+
+ s32 chroma_qp_index_offset2 =
+ static_cast<s32>((context.h264_parameter_set.flags >> 27) & 0x1f);
+ chroma_qp_index_offset2 = (chroma_qp_index_offset2 << 27) >> 27;
+
+ writer.WriteSe(chroma_qp_index_offset2);
+
+ writer.End();
+
+ const auto& encoded_header = writer.GetByteArray();
+ frame.resize(encoded_header.size() + context.frame_data_size);
+ std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
+
+ gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset,
+ frame.data() + encoded_header.size(),
+ context.frame_data_size);
+ }
+
+ return frame;
+}
+
+H264BitWriter::H264BitWriter() = default;
+
+H264BitWriter::~H264BitWriter() = default;
+
+void H264BitWriter::WriteU(s32 value, s32 value_sz) {
+ WriteBits(value, value_sz);
+}
+
+void H264BitWriter::WriteSe(s32 value) {
+ WriteExpGolombCodedInt(value);
+}
+
+void H264BitWriter::WriteUe(u32 value) {
+ WriteExpGolombCodedUInt(value);
+}
+
+void H264BitWriter::End() {
+ WriteBit(true);
+ Flush();
+}
+
+void H264BitWriter::WriteBit(bool state) {
+ WriteBits(state ? 1 : 0, 1);
+}
+
+void H264BitWriter::WriteScalingList(const std::vector<u8>& list, s32 start, s32 count) {
+ std::vector<u8> scan(count);
+ if (count == 16) {
+ std::memcpy(scan.data(), zig_zag_scan.data(), scan.size());
+ } else {
+ std::memcpy(scan.data(), zig_zag_direct.data(), scan.size());
+ }
+ u8 last_scale = 8;
+
+ for (s32 index = 0; index < count; index++) {
+ const u8 value = list[start + scan[index]];
+ const s32 delta_scale = static_cast<s32>(value - last_scale);
+
+ WriteSe(delta_scale);
+
+ last_scale = value;
+ }
+}
+
+std::vector<u8>& H264BitWriter::GetByteArray() {
+ return byte_array;
+}
+
+const std::vector<u8>& H264BitWriter::GetByteArray() const {
+ return byte_array;
+}
+
+void H264BitWriter::WriteBits(s32 value, s32 bit_count) {
+ s32 value_pos = 0;
+
+ s32 remaining = bit_count;
+
+ while (remaining > 0) {
+ s32 copy_size = remaining;
+
+ const s32 free_bits = GetFreeBufferBits();
+
+ if (copy_size > free_bits) {
+ copy_size = free_bits;
+ }
+
+ const s32 mask = (1 << copy_size) - 1;
+
+ const s32 src_shift = (bit_count - value_pos) - copy_size;
+ const s32 dst_shift = (buffer_size - buffer_pos) - copy_size;
+
+ buffer |= ((value >> src_shift) & mask) << dst_shift;
+
+ value_pos += copy_size;
+ buffer_pos += copy_size;
+ remaining -= copy_size;
+ }
+}
+
+void H264BitWriter::WriteExpGolombCodedInt(s32 value) {
+ const s32 sign = value <= 0 ? 0 : 1;
+ if (value < 0) {
+ value = -value;
+ }
+ value = (value << 1) - sign;
+ WriteExpGolombCodedUInt(value);
+}
+
+void H264BitWriter::WriteExpGolombCodedUInt(u32 value) {
+ const s32 size = 32 - std::countl_zero(value + 1);
+ WriteBits(1, size);
+
+ value -= (1U << (size - 1)) - 1;
+ WriteBits(static_cast<s32>(value), size - 1);
+}
+
+s32 H264BitWriter::GetFreeBufferBits() {
+ if (buffer_pos == buffer_size) {
+ Flush();
+ }
+
+ return buffer_size - buffer_pos;
+}
+
+void H264BitWriter::Flush() {
+ if (buffer_pos == 0) {
+ return;
+ }
+ byte_array.push_back(static_cast<u8>(buffer));
+
+ buffer = 0;
+ buffer_pos = 0;
+}
+} // namespace Tegra::Decoder
diff --git a/src/video_core/command_classes/codecs/h264.h b/src/video_core/command_classes/codecs/h264.h
new file mode 100644
index 000000000..0f3a1d9f3
--- /dev/null
+++ b/src/video_core/command_classes/codecs/h264.h
@@ -0,0 +1,118 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#pragma once
+
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/command_classes/nvdec_common.h"
+
+namespace Tegra {
+class GPU;
+namespace Decoder {
+
+class H264BitWriter {
+public:
+ H264BitWriter();
+ ~H264BitWriter();
+
+ /// The following Write methods are based on clause 9.1 in the H.264 specification.
+ /// WriteSe and WriteUe write in the Exp-Golomb-coded syntax
+ void WriteU(s32 value, s32 value_sz);
+ void WriteSe(s32 value);
+ void WriteUe(u32 value);
+
+ /// Finalize the bitstream
+ void End();
+
+ /// append a bit to the stream, equivalent value to the state parameter
+ void WriteBit(bool state);
+
+ /// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification
+ /// Writes the scaling matrices of the sream
+ void WriteScalingList(const std::vector<u8>& list, s32 start, s32 count);
+
+ /// Return the bitstream as a vector.
+ [[nodiscard]] std::vector<u8>& GetByteArray();
+ [[nodiscard]] const std::vector<u8>& GetByteArray() const;
+
+private:
+ void WriteBits(s32 value, s32 bit_count);
+ void WriteExpGolombCodedInt(s32 value);
+ void WriteExpGolombCodedUInt(u32 value);
+ [[nodiscard]] s32 GetFreeBufferBits();
+ void Flush();
+
+ s32 buffer_size{8};
+
+ s32 buffer{};
+ s32 buffer_pos{};
+ std::vector<u8> byte_array;
+};
+
+class H264 {
+public:
+ explicit H264(GPU& gpu);
+ ~H264();
+
+ /// Compose the H264 header of the frame for FFmpeg decoding
+ [[nodiscard]] const std::vector<u8>& ComposeFrameHeader(
+ const NvdecCommon::NvdecRegisters& state, bool is_first_frame = false);
+
+private:
+ struct H264ParameterSet {
+ u32 log2_max_pic_order_cnt{};
+ u32 delta_pic_order_always_zero_flag{};
+ u32 frame_mbs_only_flag{};
+ u32 pic_width_in_mbs{};
+ u32 pic_height_in_map_units{};
+ INSERT_PADDING_WORDS(1);
+ u32 entropy_coding_mode_flag{};
+ u32 bottom_field_pic_order_flag{};
+ u32 num_refidx_l0_default_active{};
+ u32 num_refidx_l1_default_active{};
+ u32 deblocking_filter_control_flag{};
+ u32 redundant_pic_count_flag{};
+ u32 transform_8x8_mode_flag{};
+ INSERT_PADDING_WORDS(9);
+ u64 flags{};
+ u32 frame_number{};
+ u32 frame_number2{};
+ };
+ static_assert(sizeof(H264ParameterSet) == 0x68, "H264ParameterSet is an invalid size");
+
+ struct H264DecoderContext {
+ INSERT_PADDING_BYTES(0x48);
+ u32 frame_data_size{};
+ INSERT_PADDING_BYTES(0xc);
+ H264ParameterSet h264_parameter_set{};
+ INSERT_PADDING_BYTES(0x100);
+ std::array<u8, 0x60> scaling_matrix_4;
+ std::array<u8, 0x80> scaling_matrix_8;
+ };
+ static_assert(sizeof(H264DecoderContext) == 0x2a0, "H264DecoderContext is an invalid size");
+
+ std::vector<u8> frame;
+ GPU& gpu;
+};
+
+} // namespace Decoder
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/vp9.cpp b/src/video_core/command_classes/codecs/vp9.cpp
new file mode 100644
index 000000000..59e586695
--- /dev/null
+++ b/src/video_core/command_classes/codecs/vp9.cpp
@@ -0,0 +1,989 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring> // for std::memcpy
+#include <numeric>
+#include "video_core/command_classes/codecs/vp9.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+
+namespace Tegra::Decoder {
+namespace {
+// Default compressed header probabilities once frame context resets
+constexpr Vp9EntropyProbs default_probs{
+ .y_mode_prob{
+ 65, 32, 18, 144, 162, 194, 41, 51, 98, 132, 68, 18, 165, 217, 196, 45, 40, 78,
+ 173, 80, 19, 176, 240, 193, 64, 35, 46, 221, 135, 38, 194, 248, 121, 96, 85, 29,
+ },
+ .partition_prob{
+ 199, 122, 141, 0, 147, 63, 159, 0, 148, 133, 118, 0, 121, 104, 114, 0,
+ 174, 73, 87, 0, 92, 41, 83, 0, 82, 99, 50, 0, 53, 39, 39, 0,
+ 177, 58, 59, 0, 68, 26, 63, 0, 52, 79, 25, 0, 17, 14, 12, 0,
+ 222, 34, 30, 0, 72, 16, 44, 0, 58, 32, 12, 0, 10, 7, 6, 0,
+ },
+ .coef_probs{
+ 195, 29, 183, 84, 49, 136, 8, 42, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 31, 107, 169, 35, 99, 159, 17, 82, 140, 8, 66, 114, 2, 44, 76, 1, 19, 32,
+ 40, 132, 201, 29, 114, 187, 13, 91, 157, 7, 75, 127, 3, 58, 95, 1, 28, 47,
+ 69, 142, 221, 42, 122, 201, 15, 91, 159, 6, 67, 121, 1, 42, 77, 1, 17, 31,
+ 102, 148, 228, 67, 117, 204, 17, 82, 154, 6, 59, 114, 2, 39, 75, 1, 15, 29,
+ 156, 57, 233, 119, 57, 212, 58, 48, 163, 29, 40, 124, 12, 30, 81, 3, 12, 31,
+ 191, 107, 226, 124, 117, 204, 25, 99, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 29, 148, 210, 37, 126, 194, 8, 93, 157, 2, 68, 118, 1, 39, 69, 1, 17, 33,
+ 41, 151, 213, 27, 123, 193, 3, 82, 144, 1, 58, 105, 1, 32, 60, 1, 13, 26,
+ 59, 159, 220, 23, 126, 198, 4, 88, 151, 1, 66, 114, 1, 38, 71, 1, 18, 34,
+ 114, 136, 232, 51, 114, 207, 11, 83, 155, 3, 56, 105, 1, 33, 65, 1, 17, 34,
+ 149, 65, 234, 121, 57, 215, 61, 49, 166, 28, 36, 114, 12, 25, 76, 3, 16, 42,
+ 214, 49, 220, 132, 63, 188, 42, 65, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 85, 137, 221, 104, 131, 216, 49, 111, 192, 21, 87, 155, 2, 49, 87, 1, 16, 28,
+ 89, 163, 230, 90, 137, 220, 29, 100, 183, 10, 70, 135, 2, 42, 81, 1, 17, 33,
+ 108, 167, 237, 55, 133, 222, 15, 97, 179, 4, 72, 135, 1, 45, 85, 1, 19, 38,
+ 124, 146, 240, 66, 124, 224, 17, 88, 175, 4, 58, 122, 1, 36, 75, 1, 18, 37,
+ 141, 79, 241, 126, 70, 227, 66, 58, 182, 30, 44, 136, 12, 34, 96, 2, 20, 47,
+ 229, 99, 249, 143, 111, 235, 46, 109, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 82, 158, 236, 94, 146, 224, 25, 117, 191, 9, 87, 149, 3, 56, 99, 1, 33, 57,
+ 83, 167, 237, 68, 145, 222, 10, 103, 177, 2, 72, 131, 1, 41, 79, 1, 20, 39,
+ 99, 167, 239, 47, 141, 224, 10, 104, 178, 2, 73, 133, 1, 44, 85, 1, 22, 47,
+ 127, 145, 243, 71, 129, 228, 17, 93, 177, 3, 61, 124, 1, 41, 84, 1, 21, 52,
+ 157, 78, 244, 140, 72, 231, 69, 58, 184, 31, 44, 137, 14, 38, 105, 8, 23, 61,
+ 125, 34, 187, 52, 41, 133, 6, 31, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 37, 109, 153, 51, 102, 147, 23, 87, 128, 8, 67, 101, 1, 41, 63, 1, 19, 29,
+ 31, 154, 185, 17, 127, 175, 6, 96, 145, 2, 73, 114, 1, 51, 82, 1, 28, 45,
+ 23, 163, 200, 10, 131, 185, 2, 93, 148, 1, 67, 111, 1, 41, 69, 1, 14, 24,
+ 29, 176, 217, 12, 145, 201, 3, 101, 156, 1, 69, 111, 1, 39, 63, 1, 14, 23,
+ 57, 192, 233, 25, 154, 215, 6, 109, 167, 3, 78, 118, 1, 48, 69, 1, 21, 29,
+ 202, 105, 245, 108, 106, 216, 18, 90, 144, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 33, 172, 219, 64, 149, 206, 14, 117, 177, 5, 90, 141, 2, 61, 95, 1, 37, 57,
+ 33, 179, 220, 11, 140, 198, 1, 89, 148, 1, 60, 104, 1, 33, 57, 1, 12, 21,
+ 30, 181, 221, 8, 141, 198, 1, 87, 145, 1, 58, 100, 1, 31, 55, 1, 12, 20,
+ 32, 186, 224, 7, 142, 198, 1, 86, 143, 1, 58, 100, 1, 31, 55, 1, 12, 22,
+ 57, 192, 227, 20, 143, 204, 3, 96, 154, 1, 68, 112, 1, 42, 69, 1, 19, 32,
+ 212, 35, 215, 113, 47, 169, 29, 48, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 74, 129, 203, 106, 120, 203, 49, 107, 178, 19, 84, 144, 4, 50, 84, 1, 15, 25,
+ 71, 172, 217, 44, 141, 209, 15, 102, 173, 6, 76, 133, 2, 51, 89, 1, 24, 42,
+ 64, 185, 231, 31, 148, 216, 8, 103, 175, 3, 74, 131, 1, 46, 81, 1, 18, 30,
+ 65, 196, 235, 25, 157, 221, 5, 105, 174, 1, 67, 120, 1, 38, 69, 1, 15, 30,
+ 65, 204, 238, 30, 156, 224, 7, 107, 177, 2, 70, 124, 1, 42, 73, 1, 18, 34,
+ 225, 86, 251, 144, 104, 235, 42, 99, 181, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 85, 175, 239, 112, 165, 229, 29, 136, 200, 12, 103, 162, 6, 77, 123, 2, 53, 84,
+ 75, 183, 239, 30, 155, 221, 3, 106, 171, 1, 74, 128, 1, 44, 76, 1, 17, 28,
+ 73, 185, 240, 27, 159, 222, 2, 107, 172, 1, 75, 127, 1, 42, 73, 1, 17, 29,
+ 62, 190, 238, 21, 159, 222, 2, 107, 172, 1, 72, 122, 1, 40, 71, 1, 18, 32,
+ 61, 199, 240, 27, 161, 226, 4, 113, 180, 1, 76, 129, 1, 46, 80, 1, 23, 41,
+ 7, 27, 153, 5, 30, 95, 1, 16, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 50, 75, 127, 57, 75, 124, 27, 67, 108, 10, 54, 86, 1, 33, 52, 1, 12, 18,
+ 43, 125, 151, 26, 108, 148, 7, 83, 122, 2, 59, 89, 1, 38, 60, 1, 17, 27,
+ 23, 144, 163, 13, 112, 154, 2, 75, 117, 1, 50, 81, 1, 31, 51, 1, 14, 23,
+ 18, 162, 185, 6, 123, 171, 1, 78, 125, 1, 51, 86, 1, 31, 54, 1, 14, 23,
+ 15, 199, 227, 3, 150, 204, 1, 91, 146, 1, 55, 95, 1, 30, 53, 1, 11, 20,
+ 19, 55, 240, 19, 59, 196, 3, 52, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 41, 166, 207, 104, 153, 199, 31, 123, 181, 14, 101, 152, 5, 72, 106, 1, 36, 52,
+ 35, 176, 211, 12, 131, 190, 2, 88, 144, 1, 60, 101, 1, 36, 60, 1, 16, 28,
+ 28, 183, 213, 8, 134, 191, 1, 86, 142, 1, 56, 96, 1, 30, 53, 1, 12, 20,
+ 20, 190, 215, 4, 135, 192, 1, 84, 139, 1, 53, 91, 1, 28, 49, 1, 11, 20,
+ 13, 196, 216, 2, 137, 192, 1, 86, 143, 1, 57, 99, 1, 32, 56, 1, 13, 24,
+ 211, 29, 217, 96, 47, 156, 22, 43, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 78, 120, 193, 111, 116, 186, 46, 102, 164, 15, 80, 128, 2, 49, 76, 1, 18, 28,
+ 71, 161, 203, 42, 132, 192, 10, 98, 150, 3, 69, 109, 1, 44, 70, 1, 18, 29,
+ 57, 186, 211, 30, 140, 196, 4, 93, 146, 1, 62, 102, 1, 38, 65, 1, 16, 27,
+ 47, 199, 217, 14, 145, 196, 1, 88, 142, 1, 57, 98, 1, 36, 62, 1, 15, 26,
+ 26, 219, 229, 5, 155, 207, 1, 94, 151, 1, 60, 104, 1, 36, 62, 1, 16, 28,
+ 233, 29, 248, 146, 47, 220, 43, 52, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 100, 163, 232, 179, 161, 222, 63, 142, 204, 37, 113, 174, 26, 89, 137, 18, 68, 97,
+ 85, 181, 230, 32, 146, 209, 7, 100, 164, 3, 71, 121, 1, 45, 77, 1, 18, 30,
+ 65, 187, 230, 20, 148, 207, 2, 97, 159, 1, 68, 116, 1, 40, 70, 1, 14, 29,
+ 40, 194, 227, 8, 147, 204, 1, 94, 155, 1, 65, 112, 1, 39, 66, 1, 14, 26,
+ 16, 208, 228, 3, 151, 207, 1, 98, 160, 1, 67, 117, 1, 41, 74, 1, 17, 31,
+ 17, 38, 140, 7, 34, 80, 1, 17, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 37, 75, 128, 41, 76, 128, 26, 66, 116, 12, 52, 94, 2, 32, 55, 1, 10, 16,
+ 50, 127, 154, 37, 109, 152, 16, 82, 121, 5, 59, 85, 1, 35, 54, 1, 13, 20,
+ 40, 142, 167, 17, 110, 157, 2, 71, 112, 1, 44, 72, 1, 27, 45, 1, 11, 17,
+ 30, 175, 188, 9, 124, 169, 1, 74, 116, 1, 48, 78, 1, 30, 49, 1, 11, 18,
+ 10, 222, 223, 2, 150, 194, 1, 83, 128, 1, 48, 79, 1, 27, 45, 1, 11, 17,
+ 36, 41, 235, 29, 36, 193, 10, 27, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 85, 165, 222, 177, 162, 215, 110, 135, 195, 57, 113, 168, 23, 83, 120, 10, 49, 61,
+ 85, 190, 223, 36, 139, 200, 5, 90, 146, 1, 60, 103, 1, 38, 65, 1, 18, 30,
+ 72, 202, 223, 23, 141, 199, 2, 86, 140, 1, 56, 97, 1, 36, 61, 1, 16, 27,
+ 55, 218, 225, 13, 145, 200, 1, 86, 141, 1, 57, 99, 1, 35, 61, 1, 13, 22,
+ 15, 235, 212, 1, 132, 184, 1, 84, 139, 1, 57, 97, 1, 34, 56, 1, 14, 23,
+ 181, 21, 201, 61, 37, 123, 10, 38, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 47, 106, 172, 95, 104, 173, 42, 93, 159, 18, 77, 131, 4, 50, 81, 1, 17, 23,
+ 62, 147, 199, 44, 130, 189, 28, 102, 154, 18, 75, 115, 2, 44, 65, 1, 12, 19,
+ 55, 153, 210, 24, 130, 194, 3, 93, 146, 1, 61, 97, 1, 31, 50, 1, 10, 16,
+ 49, 186, 223, 17, 148, 204, 1, 96, 142, 1, 53, 83, 1, 26, 44, 1, 11, 17,
+ 13, 217, 212, 2, 136, 180, 1, 78, 124, 1, 50, 83, 1, 29, 49, 1, 14, 23,
+ 197, 13, 247, 82, 17, 222, 25, 17, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 126, 186, 247, 234, 191, 243, 176, 177, 234, 104, 158, 220, 66, 128, 186, 55, 90, 137,
+ 111, 197, 242, 46, 158, 219, 9, 104, 171, 2, 65, 125, 1, 44, 80, 1, 17, 91,
+ 104, 208, 245, 39, 168, 224, 3, 109, 162, 1, 79, 124, 1, 50, 102, 1, 43, 102,
+ 84, 220, 246, 31, 177, 231, 2, 115, 180, 1, 79, 134, 1, 55, 77, 1, 60, 79,
+ 43, 243, 240, 8, 180, 217, 1, 115, 166, 1, 84, 121, 1, 51, 67, 1, 16, 6,
+ },
+ .switchable_interp_prob{235, 162, 36, 255, 34, 3, 149, 144},
+ .inter_mode_prob{
+ 2, 173, 34, 0, 7, 145, 85, 0, 7, 166, 63, 0, 7, 94,
+ 66, 0, 8, 64, 46, 0, 17, 81, 31, 0, 25, 29, 30, 0,
+ },
+ .intra_inter_prob{9, 102, 187, 225},
+ .comp_inter_prob{9, 102, 187, 225, 0},
+ .single_ref_prob{33, 16, 77, 74, 142, 142, 172, 170, 238, 247},
+ .comp_ref_prob{50, 126, 123, 221, 226},
+ .tx_32x32_prob{3, 136, 37, 5, 52, 13},
+ .tx_16x16_prob{20, 152, 15, 101},
+ .tx_8x8_prob{100, 66},
+ .skip_probs{192, 128, 64},
+ .joints{32, 64, 96},
+ .sign{128, 128},
+ .classes{
+ 224, 144, 192, 168, 192, 176, 192, 198, 198, 245,
+ 216, 128, 176, 160, 176, 176, 192, 198, 198, 208,
+ },
+ .class_0{216, 208},
+ .prob_bits{
+ 136, 140, 148, 160, 176, 192, 224, 234, 234, 240,
+ 136, 140, 148, 160, 176, 192, 224, 234, 234, 240,
+ },
+ .class_0_fr{128, 128, 64, 96, 112, 64, 128, 128, 64, 96, 112, 64},
+ .fr{64, 96, 64, 64, 96, 64},
+ .class_0_hp{160, 160},
+ .high_precision{128, 128},
+};
+
+constexpr std::array<s32, 256> norm_lut{
+ 0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+constexpr std::array<s32, 254> map_lut{
+ 20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
+ 73, 4, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 5, 86, 87, 88, 89,
+ 90, 91, 92, 93, 94, 95, 96, 97, 6, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
+ 108, 109, 7, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 8, 122, 123, 124,
+ 125, 126, 127, 128, 129, 130, 131, 132, 133, 9, 134, 135, 136, 137, 138, 139, 140, 141, 142,
+ 143, 144, 145, 10, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 11, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, 177,
+ 178, 179, 180, 181, 13, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 14, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, 208, 209, 210, 211, 212,
+ 213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 17,
+ 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 18, 242, 243, 244, 245, 246, 247,
+ 248, 249, 250, 251, 252, 253, 19,
+};
+
+// 6.2.14 Tile size calculation
+
+[[nodiscard]] s32 CalcMinLog2TileCols(s32 frame_width) {
+ const s32 sb64_cols = (frame_width + 63) / 64;
+ s32 min_log2 = 0;
+
+ while ((64 << min_log2) < sb64_cols) {
+ min_log2++;
+ }
+
+ return min_log2;
+}
+
+[[nodiscard]] s32 CalcMaxLog2TileCols(s32 frame_width) {
+ const s32 sb64_cols = (frame_width + 63) / 64;
+ s32 max_log2 = 1;
+
+ while ((sb64_cols >> max_log2) >= 4) {
+ max_log2++;
+ }
+
+ return max_log2 - 1;
+}
+
+// Recenters probability. Based on section 6.3.6 of VP9 Specification
+[[nodiscard]] s32 RecenterNonNeg(s32 new_prob, s32 old_prob) {
+ if (new_prob > old_prob * 2) {
+ return new_prob;
+ }
+
+ if (new_prob >= old_prob) {
+ return (new_prob - old_prob) * 2;
+ }
+
+ return (old_prob - new_prob) * 2 - 1;
+}
+
+// Adjusts old_prob depending on new_prob. Based on section 6.3.5 of VP9 Specification
+[[nodiscard]] s32 RemapProbability(s32 new_prob, s32 old_prob) {
+ new_prob--;
+ old_prob--;
+
+ std::size_t index{};
+
+ if (old_prob * 2 <= 0xff) {
+ index = static_cast<std::size_t>(std::max(0, RecenterNonNeg(new_prob, old_prob) - 1));
+ } else {
+ index = static_cast<std::size_t>(
+ std::max(0, RecenterNonNeg(0xff - 1 - new_prob, 0xff - 1 - old_prob) - 1));
+ }
+
+ return map_lut[index];
+}
+} // Anonymous namespace
+
+VP9::VP9(GPU& gpu_) : gpu{gpu_} {}
+
+VP9::~VP9() = default;
+
+void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const bool update = new_prob != old_prob;
+
+ writer.Write(update, diff_update_probability);
+
+ if (update) {
+ WriteProbabilityDelta(writer, new_prob, old_prob);
+ }
+}
+template <typename T, std::size_t N>
+void VP9::WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob) {
+ for (std::size_t offset = 0; offset < new_prob.size(); ++offset) {
+ WriteProbabilityUpdate(writer, new_prob[offset], old_prob[offset]);
+ }
+}
+
+template <typename T, std::size_t N>
+void VP9::WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob) {
+ for (std::size_t offset = 0; offset < new_prob.size(); offset += 4) {
+ WriteProbabilityUpdate(writer, new_prob[offset + 0], old_prob[offset + 0]);
+ WriteProbabilityUpdate(writer, new_prob[offset + 1], old_prob[offset + 1]);
+ WriteProbabilityUpdate(writer, new_prob[offset + 2], old_prob[offset + 2]);
+ }
+}
+
+void VP9::WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const int delta = RemapProbability(new_prob, old_prob);
+
+ EncodeTermSubExp(writer, delta);
+}
+
+void VP9::EncodeTermSubExp(VpxRangeEncoder& writer, s32 value) {
+ if (WriteLessThan(writer, value, 16)) {
+ writer.Write(value, 4);
+ } else if (WriteLessThan(writer, value, 32)) {
+ writer.Write(value - 16, 4);
+ } else if (WriteLessThan(writer, value, 64)) {
+ writer.Write(value - 32, 5);
+ } else {
+ value -= 64;
+
+ constexpr s32 size = 8;
+
+ const s32 mask = (1 << size) - 191;
+
+ const s32 delta = value - mask;
+
+ if (delta < 0) {
+ writer.Write(value, size - 1);
+ } else {
+ writer.Write(delta / 2 + mask, size - 1);
+ writer.Write(delta & 1, 1);
+ }
+ }
+}
+
+bool VP9::WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test) {
+ const bool is_lt = value < test;
+ writer.Write(!is_lt);
+ return is_lt;
+}
+
+void VP9::WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode,
+ const std::array<u8, 1728>& new_prob,
+ const std::array<u8, 1728>& old_prob) {
+ constexpr u32 block_bytes = 2 * 2 * 6 * 6 * 3;
+
+ const auto needs_update = [&](u32 base_index) {
+ return !std::equal(new_prob.begin() + base_index,
+ new_prob.begin() + base_index + block_bytes,
+ old_prob.begin() + base_index);
+ };
+
+ for (u32 block_index = 0; block_index < 4; block_index++) {
+ const u32 base_index = block_index * block_bytes;
+ const bool update = needs_update(base_index);
+ writer.Write(update);
+
+ if (update) {
+ u32 index = base_index;
+ for (s32 i = 0; i < 2; i++) {
+ for (s32 j = 0; j < 2; j++) {
+ for (s32 k = 0; k < 6; k++) {
+ for (s32 l = 0; l < 6; l++) {
+ if (k != 0 || l < 3) {
+ WriteProbabilityUpdate(writer, new_prob[index + 0],
+ old_prob[index + 0]);
+ WriteProbabilityUpdate(writer, new_prob[index + 1],
+ old_prob[index + 1]);
+ WriteProbabilityUpdate(writer, new_prob[index + 2],
+ old_prob[index + 2]);
+ }
+ index += 3;
+ }
+ }
+ }
+ }
+ }
+ if (block_index == static_cast<u32>(tx_mode)) {
+ break;
+ }
+ }
+}
+
+void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob) {
+ const bool update = new_prob != old_prob;
+ writer.Write(update, diff_update_probability);
+
+ if (update) {
+ writer.Write(new_prob >> 1, 7);
+ }
+}
+
+Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) {
+ PictureInfo picture_info{};
+ gpu.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
+ Vp9PictureInfo vp9_info = picture_info.Convert();
+
+ InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
+
+ // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following
+ // order: last, golden, altref, current. It may be worthwhile to track the updates done here
+ // to avoid buffering frame data needed for reference frame updating in the header composition.
+ std::memcpy(vp9_info.frame_offsets.data(), state.surface_luma_offset.data(), 4 * sizeof(u64));
+
+ return vp9_info;
+}
+
+void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
+ EntropyProbs entropy{};
+ gpu.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
+ entropy.Convert(dst);
+}
+
+Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state) {
+ Vp9FrameContainer current_frame{};
+ {
+ gpu.SyncGuestHost();
+ current_frame.info = GetVp9PictureInfo(state);
+ current_frame.bit_stream.resize(current_frame.info.bitstream_size);
+ gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(),
+ current_frame.info.bitstream_size);
+ }
+ // Buffer two frames, saving the last show frame info
+ if (!next_next_frame.bit_stream.empty()) {
+ Vp9FrameContainer temp{
+ .info = current_frame.info,
+ .bit_stream = std::move(current_frame.bit_stream),
+ };
+ next_next_frame.info.show_frame = current_frame.info.last_frame_shown;
+ current_frame.info = next_next_frame.info;
+ current_frame.bit_stream = std::move(next_next_frame.bit_stream);
+ next_next_frame = std::move(temp);
+
+ if (!next_frame.bit_stream.empty()) {
+ Vp9FrameContainer temp2{
+ .info = current_frame.info,
+ .bit_stream = std::move(current_frame.bit_stream),
+ };
+ next_frame.info.show_frame = current_frame.info.last_frame_shown;
+ current_frame.info = next_frame.info;
+ current_frame.bit_stream = std::move(next_frame.bit_stream);
+ next_frame = std::move(temp2);
+ } else {
+ next_frame.info = current_frame.info;
+ next_frame.bit_stream = std::move(current_frame.bit_stream);
+ }
+ } else {
+ next_next_frame.info = current_frame.info;
+ next_next_frame.bit_stream = std::move(current_frame.bit_stream);
+ }
+ return current_frame;
+}
+
+std::vector<u8> VP9::ComposeCompressedHeader() {
+ VpxRangeEncoder writer{};
+ const bool update_probs = current_frame_info.show_frame && !current_frame_info.is_key_frame;
+ if (!current_frame_info.lossless) {
+ if (static_cast<u32>(current_frame_info.transform_mode) >= 3) {
+ writer.Write(3, 2);
+ writer.Write(current_frame_info.transform_mode == 4);
+ } else {
+ writer.Write(current_frame_info.transform_mode, 2);
+ }
+ }
+
+ if (current_frame_info.transform_mode == 4) {
+ // tx_mode_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_8x8_prob,
+ prev_frame_probs.tx_8x8_prob);
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_16x16_prob,
+ prev_frame_probs.tx_16x16_prob);
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.tx_32x32_prob,
+ prev_frame_probs.tx_32x32_prob);
+ if (update_probs) {
+ prev_frame_probs.tx_8x8_prob = current_frame_info.entropy.tx_8x8_prob;
+ prev_frame_probs.tx_16x16_prob = current_frame_info.entropy.tx_16x16_prob;
+ prev_frame_probs.tx_32x32_prob = current_frame_info.entropy.tx_32x32_prob;
+ }
+ }
+ // read_coef_probs() in the spec
+ WriteCoefProbabilityUpdate(writer, current_frame_info.transform_mode,
+ current_frame_info.entropy.coef_probs, prev_frame_probs.coef_probs);
+ // read_skip_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.skip_probs,
+ prev_frame_probs.skip_probs);
+
+ if (update_probs) {
+ prev_frame_probs.coef_probs = current_frame_info.entropy.coef_probs;
+ prev_frame_probs.skip_probs = current_frame_info.entropy.skip_probs;
+ }
+
+ if (!current_frame_info.intra_only) {
+ // read_inter_probs() in the spec
+ WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.inter_mode_prob,
+ prev_frame_probs.inter_mode_prob);
+
+ if (current_frame_info.interp_filter == 4) {
+ // read_interp_filter_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.switchable_interp_prob,
+ prev_frame_probs.switchable_interp_prob);
+ if (update_probs) {
+ prev_frame_probs.switchable_interp_prob =
+ current_frame_info.entropy.switchable_interp_prob;
+ }
+ }
+
+ // read_is_inter_probs() in the spec
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.intra_inter_prob,
+ prev_frame_probs.intra_inter_prob);
+
+ // frame_reference_mode() in the spec
+ if ((current_frame_info.ref_frame_sign_bias[1] & 1) !=
+ (current_frame_info.ref_frame_sign_bias[2] & 1) ||
+ (current_frame_info.ref_frame_sign_bias[1] & 1) !=
+ (current_frame_info.ref_frame_sign_bias[3] & 1)) {
+ if (current_frame_info.reference_mode >= 1) {
+ writer.Write(1, 1);
+ writer.Write(current_frame_info.reference_mode == 2);
+ } else {
+ writer.Write(0, 1);
+ }
+ }
+
+ // frame_reference_mode_probs() in the spec
+ if (current_frame_info.reference_mode == 2) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_inter_prob,
+ prev_frame_probs.comp_inter_prob);
+ if (update_probs) {
+ prev_frame_probs.comp_inter_prob = current_frame_info.entropy.comp_inter_prob;
+ }
+ }
+
+ if (current_frame_info.reference_mode != 1) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.single_ref_prob,
+ prev_frame_probs.single_ref_prob);
+ if (update_probs) {
+ prev_frame_probs.single_ref_prob = current_frame_info.entropy.single_ref_prob;
+ }
+ }
+
+ if (current_frame_info.reference_mode != 0) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.comp_ref_prob,
+ prev_frame_probs.comp_ref_prob);
+ if (update_probs) {
+ prev_frame_probs.comp_ref_prob = current_frame_info.entropy.comp_ref_prob;
+ }
+ }
+
+ // read_y_mode_probs
+ for (std::size_t index = 0; index < current_frame_info.entropy.y_mode_prob.size();
+ ++index) {
+ WriteProbabilityUpdate(writer, current_frame_info.entropy.y_mode_prob[index],
+ prev_frame_probs.y_mode_prob[index]);
+ }
+
+ // read_partition_probs
+ WriteProbabilityUpdateAligned4(writer, current_frame_info.entropy.partition_prob,
+ prev_frame_probs.partition_prob);
+
+ // mv_probs
+ for (s32 i = 0; i < 3; i++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.joints[i],
+ prev_frame_probs.joints[i]);
+ }
+ if (update_probs) {
+ prev_frame_probs.inter_mode_prob = current_frame_info.entropy.inter_mode_prob;
+ prev_frame_probs.intra_inter_prob = current_frame_info.entropy.intra_inter_prob;
+ prev_frame_probs.y_mode_prob = current_frame_info.entropy.y_mode_prob;
+ prev_frame_probs.partition_prob = current_frame_info.entropy.partition_prob;
+ prev_frame_probs.joints = current_frame_info.entropy.joints;
+ }
+
+ for (s32 i = 0; i < 2; i++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.sign[i],
+ prev_frame_probs.sign[i]);
+ for (s32 j = 0; j < 10; j++) {
+ const int index = i * 10 + j;
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.classes[index],
+ prev_frame_probs.classes[index]);
+ }
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0[i],
+ prev_frame_probs.class_0[i]);
+
+ for (s32 j = 0; j < 10; j++) {
+ const int index = i * 10 + j;
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.prob_bits[index],
+ prev_frame_probs.prob_bits[index]);
+ }
+ }
+
+ for (s32 i = 0; i < 2; i++) {
+ for (s32 j = 0; j < 2; j++) {
+ for (s32 k = 0; k < 3; k++) {
+ const int index = i * 2 * 3 + j * 3 + k;
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_fr[index],
+ prev_frame_probs.class_0_fr[index]);
+ }
+ }
+
+ for (s32 j = 0; j < 3; j++) {
+ const int index = i * 3 + j;
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.fr[index],
+ prev_frame_probs.fr[index]);
+ }
+ }
+
+ if (current_frame_info.allow_high_precision_mv) {
+ for (s32 index = 0; index < 2; index++) {
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.class_0_hp[index],
+ prev_frame_probs.class_0_hp[index]);
+ WriteMvProbabilityUpdate(writer, current_frame_info.entropy.high_precision[index],
+ prev_frame_probs.high_precision[index]);
+ }
+ }
+
+ // save previous probs
+ if (update_probs) {
+ prev_frame_probs.sign = current_frame_info.entropy.sign;
+ prev_frame_probs.classes = current_frame_info.entropy.classes;
+ prev_frame_probs.class_0 = current_frame_info.entropy.class_0;
+ prev_frame_probs.prob_bits = current_frame_info.entropy.prob_bits;
+ prev_frame_probs.class_0_fr = current_frame_info.entropy.class_0_fr;
+ prev_frame_probs.fr = current_frame_info.entropy.fr;
+ prev_frame_probs.class_0_hp = current_frame_info.entropy.class_0_hp;
+ prev_frame_probs.high_precision = current_frame_info.entropy.high_precision;
+ }
+ }
+ writer.End();
+ return writer.GetBuffer();
+}
+
+VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
+ VpxBitStreamWriter uncomp_writer{};
+
+ uncomp_writer.WriteU(2, 2); // Frame marker.
+ uncomp_writer.WriteU(0, 2); // Profile.
+ uncomp_writer.WriteBit(false); // Show existing frame.
+ uncomp_writer.WriteBit(!current_frame_info.is_key_frame); // is key frame?
+ uncomp_writer.WriteBit(current_frame_info.show_frame); // show frame?
+ uncomp_writer.WriteBit(current_frame_info.error_resilient_mode); // error reslience
+
+ if (current_frame_info.is_key_frame) {
+ uncomp_writer.WriteU(frame_sync_code, 24);
+ uncomp_writer.WriteU(0, 3); // Color space.
+ uncomp_writer.WriteU(0, 1); // Color range.
+ uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
+ uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+
+ // Reset context
+ prev_frame_probs = default_probs;
+ swap_next_golden = false;
+ loop_filter_ref_deltas.fill(0);
+ loop_filter_mode_deltas.fill(0);
+
+ // allow frames offsets to stabilize before checking for golden frames
+ grace_period = 4;
+
+ // On key frames, all frame slots are set to the current frame,
+ // so the value of the selected slot doesn't really matter.
+ frame_ctxs.fill({current_frame_number, false, default_probs});
+
+ // intra only, meaning the frame can be recreated with no other references
+ current_frame_info.intra_only = true;
+
+ } else {
+
+ if (!current_frame_info.show_frame) {
+ uncomp_writer.WriteBit(current_frame_info.intra_only);
+ if (!current_frame_info.last_frame_was_key) {
+ swap_next_golden = !swap_next_golden;
+ }
+ } else {
+ current_frame_info.intra_only = false;
+ }
+ if (!current_frame_info.error_resilient_mode) {
+ uncomp_writer.WriteU(0, 2); // Reset frame context.
+ }
+
+ // Last, Golden, Altref frames
+ std::array<s32, 3> ref_frame_index{0, 1, 2};
+
+ // Set when next frame is hidden
+ // altref and golden references are swapped
+ if (swap_next_golden) {
+ ref_frame_index = std::array<s32, 3>{0, 2, 1};
+ }
+
+ // update Last Frame
+ u64 refresh_frame_flags = 1;
+
+ // golden frame may refresh, determined if the next golden frame offset is changed
+ bool golden_refresh = false;
+ if (grace_period <= 0) {
+ for (s32 index = 1; index < 3; ++index) {
+ if (current_frame_info.frame_offsets[index] !=
+ next_frame.info.frame_offsets[index]) {
+ current_frame_info.refresh_frame[index] = true;
+ golden_refresh = true;
+ grace_period = 3;
+ }
+ }
+ }
+
+ if (current_frame_info.show_frame &&
+ (!next_frame.info.show_frame || next_frame.info.is_key_frame)) {
+ // Update golden frame
+ refresh_frame_flags = swap_next_golden ? 2 : 4;
+ }
+
+ if (!current_frame_info.show_frame) {
+ // Update altref
+ refresh_frame_flags = swap_next_golden ? 2 : 4;
+ } else if (golden_refresh) {
+ refresh_frame_flags = 3;
+ }
+
+ if (current_frame_info.intra_only) {
+ uncomp_writer.WriteU(frame_sync_code, 24);
+ uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8);
+ uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
+ uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+ } else {
+ uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8);
+
+ for (s32 index = 1; index < 4; index++) {
+ uncomp_writer.WriteU(ref_frame_index[index - 1], 3);
+ uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1);
+ }
+
+ uncomp_writer.WriteBit(true); // Frame size with refs.
+ uncomp_writer.WriteBit(false); // Render and frame size different.
+ uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv);
+ uncomp_writer.WriteBit(current_frame_info.interp_filter == 4);
+
+ if (current_frame_info.interp_filter != 4) {
+ uncomp_writer.WriteU(current_frame_info.interp_filter, 2);
+ }
+ }
+ }
+
+ if (!current_frame_info.error_resilient_mode) {
+ uncomp_writer.WriteBit(true); // Refresh frame context. where do i get this info from?
+ uncomp_writer.WriteBit(true); // Frame parallel decoding mode.
+ }
+
+ int frame_ctx_idx = 0;
+ if (!current_frame_info.show_frame) {
+ frame_ctx_idx = 1;
+ }
+
+ uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index.
+ prev_frame_probs =
+ frame_ctxs[frame_ctx_idx].probs; // reference probabilities for compressed header
+ frame_ctxs[frame_ctx_idx] = {current_frame_number, false, current_frame_info.entropy};
+
+ uncomp_writer.WriteU(current_frame_info.first_level, 6);
+ uncomp_writer.WriteU(current_frame_info.sharpness_level, 3);
+ uncomp_writer.WriteBit(current_frame_info.mode_ref_delta_enabled);
+
+ if (current_frame_info.mode_ref_delta_enabled) {
+ // check if ref deltas are different, update accordingly
+ std::array<bool, 4> update_loop_filter_ref_deltas;
+ std::array<bool, 2> update_loop_filter_mode_deltas;
+
+ bool loop_filter_delta_update = false;
+
+ for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) {
+ const s8 old_deltas = loop_filter_ref_deltas[index];
+ const s8 new_deltas = current_frame_info.ref_deltas[index];
+ const bool differing_delta = old_deltas != new_deltas;
+
+ update_loop_filter_ref_deltas[index] = differing_delta;
+ loop_filter_delta_update |= differing_delta;
+ }
+
+ for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) {
+ const s8 old_deltas = loop_filter_mode_deltas[index];
+ const s8 new_deltas = current_frame_info.mode_deltas[index];
+ const bool differing_delta = old_deltas != new_deltas;
+
+ update_loop_filter_mode_deltas[index] = differing_delta;
+ loop_filter_delta_update |= differing_delta;
+ }
+
+ uncomp_writer.WriteBit(loop_filter_delta_update);
+
+ if (loop_filter_delta_update) {
+ for (std::size_t index = 0; index < current_frame_info.ref_deltas.size(); index++) {
+ uncomp_writer.WriteBit(update_loop_filter_ref_deltas[index]);
+
+ if (update_loop_filter_ref_deltas[index]) {
+ uncomp_writer.WriteS(current_frame_info.ref_deltas[index], 6);
+ }
+ }
+
+ for (std::size_t index = 0; index < current_frame_info.mode_deltas.size(); index++) {
+ uncomp_writer.WriteBit(update_loop_filter_mode_deltas[index]);
+
+ if (update_loop_filter_mode_deltas[index]) {
+ uncomp_writer.WriteS(current_frame_info.mode_deltas[index], 6);
+ }
+ }
+ // save new deltas
+ loop_filter_ref_deltas = current_frame_info.ref_deltas;
+ loop_filter_mode_deltas = current_frame_info.mode_deltas;
+ }
+ }
+
+ uncomp_writer.WriteU(current_frame_info.base_q_index, 8);
+
+ uncomp_writer.WriteDeltaQ(current_frame_info.y_dc_delta_q);
+ uncomp_writer.WriteDeltaQ(current_frame_info.uv_dc_delta_q);
+ uncomp_writer.WriteDeltaQ(current_frame_info.uv_ac_delta_q);
+
+ uncomp_writer.WriteBit(false); // Segmentation enabled (TODO).
+
+ const s32 min_tile_cols_log2 = CalcMinLog2TileCols(current_frame_info.frame_size.width);
+ const s32 max_tile_cols_log2 = CalcMaxLog2TileCols(current_frame_info.frame_size.width);
+
+ const s32 tile_cols_log2_diff = current_frame_info.log2_tile_cols - min_tile_cols_log2;
+ const s32 tile_cols_log2_inc_mask = (1 << tile_cols_log2_diff) - 1;
+
+ // If it's less than the maximum, we need to add an extra 0 on the bitstream
+ // to indicate that it should stop reading.
+ if (current_frame_info.log2_tile_cols < max_tile_cols_log2) {
+ uncomp_writer.WriteU(tile_cols_log2_inc_mask << 1, tile_cols_log2_diff + 1);
+ } else {
+ uncomp_writer.WriteU(tile_cols_log2_inc_mask, tile_cols_log2_diff);
+ }
+
+ const bool tile_rows_log2_is_nonzero = current_frame_info.log2_tile_rows != 0;
+
+ uncomp_writer.WriteBit(tile_rows_log2_is_nonzero);
+
+ if (tile_rows_log2_is_nonzero) {
+ uncomp_writer.WriteBit(current_frame_info.log2_tile_rows > 1);
+ }
+
+ return uncomp_writer;
+}
+
+const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters& state) {
+ std::vector<u8> bitstream;
+ {
+ Vp9FrameContainer curr_frame = GetCurrentFrame(state);
+ current_frame_info = curr_frame.info;
+ bitstream = std::move(curr_frame.bit_stream);
+ }
+
+ // The uncompressed header routine sets PrevProb parameters needed for the compressed header
+ auto uncomp_writer = ComposeUncompressedHeader();
+ std::vector<u8> compressed_header = ComposeCompressedHeader();
+
+ uncomp_writer.WriteU(static_cast<s32>(compressed_header.size()), 16);
+ uncomp_writer.Flush();
+ std::vector<u8> uncompressed_header = uncomp_writer.GetByteArray();
+
+ // Write headers and frame to buffer
+ frame.resize(uncompressed_header.size() + compressed_header.size() + bitstream.size());
+ std::memcpy(frame.data(), uncompressed_header.data(), uncompressed_header.size());
+ std::memcpy(frame.data() + uncompressed_header.size(), compressed_header.data(),
+ compressed_header.size());
+ std::memcpy(frame.data() + uncompressed_header.size() + compressed_header.size(),
+ bitstream.data(), bitstream.size());
+
+ // keep track of frame number
+ current_frame_number++;
+ grace_period--;
+
+ // don't display hidden frames
+ hidden = !current_frame_info.show_frame;
+ return frame;
+}
+
+VpxRangeEncoder::VpxRangeEncoder() {
+ Write(false);
+}
+
+VpxRangeEncoder::~VpxRangeEncoder() = default;
+
+void VpxRangeEncoder::Write(s32 value, s32 value_size) {
+ for (s32 bit = value_size - 1; bit >= 0; bit--) {
+ Write(((value >> bit) & 1) != 0);
+ }
+}
+
+void VpxRangeEncoder::Write(bool bit) {
+ Write(bit, half_probability);
+}
+
+void VpxRangeEncoder::Write(bool bit, s32 probability) {
+ u32 local_range = range;
+ const u32 split = 1 + (((local_range - 1) * static_cast<u32>(probability)) >> 8);
+ local_range = split;
+
+ if (bit) {
+ low_value += split;
+ local_range = range - split;
+ }
+
+ s32 shift = norm_lut[local_range];
+ local_range <<= shift;
+ count += shift;
+
+ if (count >= 0) {
+ const s32 offset = shift - count;
+
+ if (((low_value << (offset - 1)) >> 31) != 0) {
+ const s32 current_pos = static_cast<s32>(base_stream.GetPosition());
+ base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
+ while (PeekByte() == 0xff) {
+ base_stream.WriteByte(0);
+
+ base_stream.Seek(-2, Common::SeekOrigin::FromCurrentPos);
+ }
+ base_stream.WriteByte(static_cast<u8>((PeekByte() + 1)));
+ base_stream.Seek(current_pos, Common::SeekOrigin::SetOrigin);
+ }
+ base_stream.WriteByte(static_cast<u8>((low_value >> (24 - offset))));
+
+ low_value <<= offset;
+ shift = count;
+ low_value &= 0xffffff;
+ count -= 8;
+ }
+
+ low_value <<= shift;
+ range = local_range;
+}
+
+void VpxRangeEncoder::End() {
+ for (std::size_t index = 0; index < 32; ++index) {
+ Write(false);
+ }
+}
+
+u8 VpxRangeEncoder::PeekByte() {
+ const u8 value = base_stream.ReadByte();
+ base_stream.Seek(-1, Common::SeekOrigin::FromCurrentPos);
+
+ return value;
+}
+
+VpxBitStreamWriter::VpxBitStreamWriter() = default;
+
+VpxBitStreamWriter::~VpxBitStreamWriter() = default;
+
+void VpxBitStreamWriter::WriteU(u32 value, u32 value_size) {
+ WriteBits(value, value_size);
+}
+
+void VpxBitStreamWriter::WriteS(s32 value, u32 value_size) {
+ const bool sign = value < 0;
+ if (sign) {
+ value = -value;
+ }
+
+ WriteBits(static_cast<u32>(value << 1) | (sign ? 1 : 0), value_size + 1);
+}
+
+void VpxBitStreamWriter::WriteDeltaQ(u32 value) {
+ const bool delta_coded = value != 0;
+ WriteBit(delta_coded);
+
+ if (delta_coded) {
+ WriteBits(value, 4);
+ }
+}
+
+void VpxBitStreamWriter::WriteBits(u32 value, u32 bit_count) {
+ s32 value_pos = 0;
+ s32 remaining = bit_count;
+
+ while (remaining > 0) {
+ s32 copy_size = remaining;
+
+ const s32 free = GetFreeBufferBits();
+
+ if (copy_size > free) {
+ copy_size = free;
+ }
+
+ const s32 mask = (1 << copy_size) - 1;
+
+ const s32 src_shift = (bit_count - value_pos) - copy_size;
+ const s32 dst_shift = (buffer_size - buffer_pos) - copy_size;
+
+ buffer |= ((value >> src_shift) & mask) << dst_shift;
+
+ value_pos += copy_size;
+ buffer_pos += copy_size;
+ remaining -= copy_size;
+ }
+}
+
+void VpxBitStreamWriter::WriteBit(bool state) {
+ WriteBits(state ? 1 : 0, 1);
+}
+
+s32 VpxBitStreamWriter::GetFreeBufferBits() {
+ if (buffer_pos == buffer_size) {
+ Flush();
+ }
+
+ return buffer_size - buffer_pos;
+}
+
+void VpxBitStreamWriter::Flush() {
+ if (buffer_pos == 0) {
+ return;
+ }
+ byte_array.push_back(static_cast<u8>(buffer));
+ buffer = 0;
+ buffer_pos = 0;
+}
+
+std::vector<u8>& VpxBitStreamWriter::GetByteArray() {
+ return byte_array;
+}
+
+const std::vector<u8>& VpxBitStreamWriter::GetByteArray() const {
+ return byte_array;
+}
+
+} // namespace Tegra::Decoder
diff --git a/src/video_core/command_classes/codecs/vp9.h b/src/video_core/command_classes/codecs/vp9.h
new file mode 100644
index 000000000..8396c8105
--- /dev/null
+++ b/src/video_core/command_classes/codecs/vp9.h
@@ -0,0 +1,197 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/stream.h"
+#include "video_core/command_classes/codecs/vp9_types.h"
+#include "video_core/command_classes/nvdec_common.h"
+
+namespace Tegra {
+class GPU;
+enum class FrameType { KeyFrame = 0, InterFrame = 1 };
+namespace Decoder {
+
+/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
+/// VP9 header bitstreams.
+
+class VpxRangeEncoder {
+public:
+ VpxRangeEncoder();
+ ~VpxRangeEncoder();
+
+ VpxRangeEncoder(const VpxRangeEncoder&) = delete;
+ VpxRangeEncoder& operator=(const VpxRangeEncoder&) = delete;
+
+ VpxRangeEncoder(VpxRangeEncoder&&) = default;
+ VpxRangeEncoder& operator=(VpxRangeEncoder&&) = default;
+
+ /// Writes the rightmost value_size bits from value into the stream
+ void Write(s32 value, s32 value_size);
+
+ /// Writes a single bit with half probability
+ void Write(bool bit);
+
+ /// Writes a bit to the base_stream encoded with probability
+ void Write(bool bit, s32 probability);
+
+ /// Signal the end of the bitstream
+ void End();
+
+ [[nodiscard]] std::vector<u8>& GetBuffer() {
+ return base_stream.GetBuffer();
+ }
+
+ [[nodiscard]] const std::vector<u8>& GetBuffer() const {
+ return base_stream.GetBuffer();
+ }
+
+private:
+ u8 PeekByte();
+ Common::Stream base_stream{};
+ u32 low_value{};
+ u32 range{0xff};
+ s32 count{-24};
+ s32 half_probability{128};
+};
+
+class VpxBitStreamWriter {
+public:
+ VpxBitStreamWriter();
+ ~VpxBitStreamWriter();
+
+ VpxBitStreamWriter(const VpxBitStreamWriter&) = delete;
+ VpxBitStreamWriter& operator=(const VpxBitStreamWriter&) = delete;
+
+ VpxBitStreamWriter(VpxBitStreamWriter&&) = default;
+ VpxBitStreamWriter& operator=(VpxBitStreamWriter&&) = default;
+
+ /// Write an unsigned integer value
+ void WriteU(u32 value, u32 value_size);
+
+ /// Write a signed integer value
+ void WriteS(s32 value, u32 value_size);
+
+ /// Based on 6.2.10 of VP9 Spec, writes a delta coded value
+ void WriteDeltaQ(u32 value);
+
+ /// Write a single bit.
+ void WriteBit(bool state);
+
+ /// Pushes current buffer into buffer_array, resets buffer
+ void Flush();
+
+ /// Returns byte_array
+ [[nodiscard]] std::vector<u8>& GetByteArray();
+
+ /// Returns const byte_array
+ [[nodiscard]] const std::vector<u8>& GetByteArray() const;
+
+private:
+ /// Write bit_count bits from value into buffer
+ void WriteBits(u32 value, u32 bit_count);
+
+ /// Gets next available position in buffer, invokes Flush() if buffer is full
+ s32 GetFreeBufferBits();
+
+ s32 buffer_size{8};
+
+ s32 buffer{};
+ s32 buffer_pos{};
+ std::vector<u8> byte_array;
+};
+
+class VP9 {
+public:
+ explicit VP9(GPU& gpu_);
+ ~VP9();
+
+ VP9(const VP9&) = delete;
+ VP9& operator=(const VP9&) = delete;
+
+ VP9(VP9&&) = default;
+ VP9& operator=(VP9&&) = delete;
+
+ /// Composes the VP9 frame from the GPU state information. Based on the official VP9 spec
+ /// documentation
+ [[nodiscard]] const std::vector<u8>& ComposeFrameHeader(
+ const NvdecCommon::NvdecRegisters& state);
+
+ /// Returns true if the most recent frame was a hidden frame.
+ [[nodiscard]] bool WasFrameHidden() const {
+ return hidden;
+ }
+
+private:
+ /// Generates compressed header probability updates in the bitstream writer
+ template <typename T, std::size_t N>
+ void WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob);
+
+ /// Generates compressed header probability updates in the bitstream writer
+ /// If probs are not equal, WriteProbabilityDelta is invoked
+ void WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Generates compressed header probability deltas in the bitstream writer
+ void WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Inverse of 6.3.4 Decode term subexp
+ void EncodeTermSubExp(VpxRangeEncoder& writer, s32 value);
+
+ /// Writes if the value is less than the test value
+ bool WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test);
+
+ /// Writes probability updates for the Coef probabilities
+ void WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode,
+ const std::array<u8, 1728>& new_prob,
+ const std::array<u8, 1728>& old_prob);
+
+ /// Write probabilities for 4-byte aligned structures
+ template <typename T, std::size_t N>
+ void WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob,
+ const std::array<T, N>& old_prob);
+
+ /// Write motion vector probability updates. 6.3.17 in the spec
+ void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
+
+ /// Returns VP9 information from NVDEC provided offset and size
+ [[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state);
+
+ /// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct
+ void InsertEntropy(u64 offset, Vp9EntropyProbs& dst);
+
+ /// Returns frame to be decoded after buffering
+ [[nodiscard]] Vp9FrameContainer GetCurrentFrame(const NvdecCommon::NvdecRegisters& state);
+
+ /// Use NVDEC providied information to compose the headers for the current frame
+ [[nodiscard]] std::vector<u8> ComposeCompressedHeader();
+ [[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
+
+ GPU& gpu;
+ std::vector<u8> frame;
+
+ std::array<s8, 4> loop_filter_ref_deltas{};
+ std::array<s8, 2> loop_filter_mode_deltas{};
+
+ bool hidden = false;
+ s64 current_frame_number = -2; // since we buffer 2 frames
+ s32 grace_period = 6; // frame offsets need to stabilize
+ std::array<FrameContexts, 4> frame_ctxs{};
+ Vp9FrameContainer next_frame{};
+ Vp9FrameContainer next_next_frame{};
+ bool swap_next_golden{};
+
+ Vp9PictureInfo current_frame_info{};
+ Vp9EntropyProbs prev_frame_probs{};
+
+ s32 diff_update_probability = 252;
+ s32 frame_sync_code = 0x498342;
+};
+
+} // namespace Decoder
+} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/vp9_types.h b/src/video_core/command_classes/codecs/vp9_types.h
new file mode 100644
index 000000000..139501a1c
--- /dev/null
+++ b/src/video_core/command_classes/codecs/vp9_types.h
@@ -0,0 +1,302 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstring>
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+class GPU;
+
+namespace Decoder {
+struct Vp9FrameDimensions {
+ s16 width{};
+ s16 height{};
+ s16 luma_pitch{};
+ s16 chroma_pitch{};
+};
+static_assert(sizeof(Vp9FrameDimensions) == 0x8, "Vp9 Vp9FrameDimensions is an invalid size");
+
+enum FrameFlags : u32 {
+ IsKeyFrame = 1 << 0,
+ LastFrameIsKeyFrame = 1 << 1,
+ FrameSizeChanged = 1 << 2,
+ ErrorResilientMode = 1 << 3,
+ LastShowFrame = 1 << 4,
+ IntraOnly = 1 << 5,
+};
+
+enum class TxSize {
+ Tx4x4 = 0, // 4x4 transform
+ Tx8x8 = 1, // 8x8 transform
+ Tx16x16 = 2, // 16x16 transform
+ Tx32x32 = 3, // 32x32 transform
+ TxSizes = 4
+};
+
+enum class TxMode {
+ Only4X4 = 0, // Only 4x4 transform used
+ Allow8X8 = 1, // Allow block transform size up to 8x8
+ Allow16X16 = 2, // Allow block transform size up to 16x16
+ Allow32X32 = 3, // Allow block transform size up to 32x32
+ TxModeSelect = 4, // Transform specified for each block
+ TxModes = 5
+};
+
+struct Segmentation {
+ u8 enabled{};
+ u8 update_map{};
+ u8 temporal_update{};
+ u8 abs_delta{};
+ std::array<u32, 8> feature_mask{};
+ std::array<std::array<s16, 4>, 8> feature_data{};
+};
+static_assert(sizeof(Segmentation) == 0x64, "Segmentation is an invalid size");
+
+struct LoopFilter {
+ u8 mode_ref_delta_enabled{};
+ std::array<s8, 4> ref_deltas{};
+ std::array<s8, 2> mode_deltas{};
+};
+static_assert(sizeof(LoopFilter) == 0x7, "LoopFilter is an invalid size");
+
+struct Vp9EntropyProbs {
+ std::array<u8, 36> y_mode_prob{};
+ std::array<u8, 64> partition_prob{};
+ std::array<u8, 1728> coef_probs{};
+ std::array<u8, 8> switchable_interp_prob{};
+ std::array<u8, 28> inter_mode_prob{};
+ std::array<u8, 4> intra_inter_prob{};
+ std::array<u8, 5> comp_inter_prob{};
+ std::array<u8, 10> single_ref_prob{};
+ std::array<u8, 5> comp_ref_prob{};
+ std::array<u8, 6> tx_32x32_prob{};
+ std::array<u8, 4> tx_16x16_prob{};
+ std::array<u8, 2> tx_8x8_prob{};
+ std::array<u8, 3> skip_probs{};
+ std::array<u8, 3> joints{};
+ std::array<u8, 2> sign{};
+ std::array<u8, 20> classes{};
+ std::array<u8, 2> class_0{};
+ std::array<u8, 20> prob_bits{};
+ std::array<u8, 12> class_0_fr{};
+ std::array<u8, 6> fr{};
+ std::array<u8, 2> class_0_hp{};
+ std::array<u8, 2> high_precision{};
+};
+static_assert(sizeof(Vp9EntropyProbs) == 0x7B4, "Vp9EntropyProbs is an invalid size");
+
+struct Vp9PictureInfo {
+ bool is_key_frame{};
+ bool intra_only{};
+ bool last_frame_was_key{};
+ bool frame_size_changed{};
+ bool error_resilient_mode{};
+ bool last_frame_shown{};
+ bool show_frame{};
+ std::array<s8, 4> ref_frame_sign_bias{};
+ s32 base_q_index{};
+ s32 y_dc_delta_q{};
+ s32 uv_dc_delta_q{};
+ s32 uv_ac_delta_q{};
+ bool lossless{};
+ s32 transform_mode{};
+ bool allow_high_precision_mv{};
+ s32 interp_filter{};
+ s32 reference_mode{};
+ s8 comp_fixed_ref{};
+ std::array<s8, 2> comp_var_ref{};
+ s32 log2_tile_cols{};
+ s32 log2_tile_rows{};
+ bool segment_enabled{};
+ bool segment_map_update{};
+ bool segment_map_temporal_update{};
+ s32 segment_abs_delta{};
+ std::array<u32, 8> segment_feature_enable{};
+ std::array<std::array<s16, 4>, 8> segment_feature_data{};
+ bool mode_ref_delta_enabled{};
+ bool use_prev_in_find_mv_refs{};
+ std::array<s8, 4> ref_deltas{};
+ std::array<s8, 2> mode_deltas{};
+ Vp9EntropyProbs entropy{};
+ Vp9FrameDimensions frame_size{};
+ u8 first_level{};
+ u8 sharpness_level{};
+ u32 bitstream_size{};
+ std::array<u64, 4> frame_offsets{};
+ std::array<bool, 4> refresh_frame{};
+};
+
+struct Vp9FrameContainer {
+ Vp9PictureInfo info{};
+ std::vector<u8> bit_stream;
+};
+
+struct PictureInfo {
+ INSERT_PADDING_WORDS(12);
+ u32 bitstream_size{};
+ INSERT_PADDING_WORDS(5);
+ Vp9FrameDimensions last_frame_size{};
+ Vp9FrameDimensions golden_frame_size{};
+ Vp9FrameDimensions alt_frame_size{};
+ Vp9FrameDimensions current_frame_size{};
+ u32 vp9_flags{};
+ std::array<s8, 4> ref_frame_sign_bias{};
+ u8 first_level{};
+ u8 sharpness_level{};
+ u8 base_q_index{};
+ u8 y_dc_delta_q{};
+ u8 uv_ac_delta_q{};
+ u8 uv_dc_delta_q{};
+ u8 lossless{};
+ u8 tx_mode{};
+ u8 allow_high_precision_mv{};
+ u8 interp_filter{};
+ u8 reference_mode{};
+ s8 comp_fixed_ref{};
+ std::array<s8, 2> comp_var_ref{};
+ u8 log2_tile_cols{};
+ u8 log2_tile_rows{};
+ Segmentation segmentation{};
+ LoopFilter loop_filter{};
+ INSERT_PADDING_BYTES(5);
+ u32 surface_params{};
+ INSERT_PADDING_WORDS(3);
+
+ [[nodiscard]] Vp9PictureInfo Convert() const {
+ return {
+ .is_key_frame = (vp9_flags & FrameFlags::IsKeyFrame) != 0,
+ .intra_only = (vp9_flags & FrameFlags::IntraOnly) != 0,
+ .last_frame_was_key = (vp9_flags & FrameFlags::LastFrameIsKeyFrame) != 0,
+ .frame_size_changed = (vp9_flags & FrameFlags::FrameSizeChanged) != 0,
+ .error_resilient_mode = (vp9_flags & FrameFlags::ErrorResilientMode) != 0,
+ .last_frame_shown = (vp9_flags & FrameFlags::LastShowFrame) != 0,
+ .ref_frame_sign_bias = ref_frame_sign_bias,
+ .base_q_index = base_q_index,
+ .y_dc_delta_q = y_dc_delta_q,
+ .uv_dc_delta_q = uv_dc_delta_q,
+ .uv_ac_delta_q = uv_ac_delta_q,
+ .lossless = lossless != 0,
+ .transform_mode = tx_mode,
+ .allow_high_precision_mv = allow_high_precision_mv != 0,
+ .interp_filter = interp_filter,
+ .reference_mode = reference_mode,
+ .comp_fixed_ref = comp_fixed_ref,
+ .comp_var_ref = comp_var_ref,
+ .log2_tile_cols = log2_tile_cols,
+ .log2_tile_rows = log2_tile_rows,
+ .segment_enabled = segmentation.enabled != 0,
+ .segment_map_update = segmentation.update_map != 0,
+ .segment_map_temporal_update = segmentation.temporal_update != 0,
+ .segment_abs_delta = segmentation.abs_delta,
+ .segment_feature_enable = segmentation.feature_mask,
+ .segment_feature_data = segmentation.feature_data,
+ .mode_ref_delta_enabled = loop_filter.mode_ref_delta_enabled != 0,
+ .use_prev_in_find_mv_refs = !(vp9_flags == (FrameFlags::ErrorResilientMode)) &&
+ !(vp9_flags == (FrameFlags::FrameSizeChanged)) &&
+ !(vp9_flags == (FrameFlags::IntraOnly)) &&
+ (vp9_flags == (FrameFlags::LastShowFrame)) &&
+ !(vp9_flags == (FrameFlags::LastFrameIsKeyFrame)),
+ .ref_deltas = loop_filter.ref_deltas,
+ .mode_deltas = loop_filter.mode_deltas,
+ .frame_size = current_frame_size,
+ .first_level = first_level,
+ .sharpness_level = sharpness_level,
+ .bitstream_size = bitstream_size,
+ };
+ }
+};
+static_assert(sizeof(PictureInfo) == 0x100, "PictureInfo is an invalid size");
+
+struct EntropyProbs {
+ INSERT_PADDING_BYTES(1024);
+ std::array<u8, 28> inter_mode_prob{};
+ std::array<u8, 4> intra_inter_prob{};
+ INSERT_PADDING_BYTES(80);
+ std::array<u8, 2> tx_8x8_prob{};
+ std::array<u8, 4> tx_16x16_prob{};
+ std::array<u8, 6> tx_32x32_prob{};
+ std::array<u8, 4> y_mode_prob_e8{};
+ std::array<std::array<u8, 8>, 4> y_mode_prob_e0e7{};
+ INSERT_PADDING_BYTES(64);
+ std::array<u8, 64> partition_prob{};
+ INSERT_PADDING_BYTES(10);
+ std::array<u8, 8> switchable_interp_prob{};
+ std::array<u8, 5> comp_inter_prob{};
+ std::array<u8, 3> skip_probs{};
+ INSERT_PADDING_BYTES(1);
+ std::array<u8, 3> joints{};
+ std::array<u8, 2> sign{};
+ std::array<u8, 2> class_0{};
+ std::array<u8, 6> fr{};
+ std::array<u8, 2> class_0_hp{};
+ std::array<u8, 2> high_precision{};
+ std::array<u8, 20> classes{};
+ std::array<u8, 12> class_0_fr{};
+ std::array<u8, 20> pred_bits{};
+ std::array<u8, 10> single_ref_prob{};
+ std::array<u8, 5> comp_ref_prob{};
+ INSERT_PADDING_BYTES(17);
+ std::array<u8, 2304> coef_probs{};
+
+ void Convert(Vp9EntropyProbs& fc) {
+ fc.inter_mode_prob = inter_mode_prob;
+ fc.intra_inter_prob = intra_inter_prob;
+ fc.tx_8x8_prob = tx_8x8_prob;
+ fc.tx_16x16_prob = tx_16x16_prob;
+ fc.tx_32x32_prob = tx_32x32_prob;
+
+ for (std::size_t i = 0; i < 4; i++) {
+ for (std::size_t j = 0; j < 9; j++) {
+ fc.y_mode_prob[j + 9 * i] = j < 8 ? y_mode_prob_e0e7[i][j] : y_mode_prob_e8[i];
+ }
+ }
+
+ fc.partition_prob = partition_prob;
+ fc.switchable_interp_prob = switchable_interp_prob;
+ fc.comp_inter_prob = comp_inter_prob;
+ fc.skip_probs = skip_probs;
+ fc.joints = joints;
+ fc.sign = sign;
+ fc.class_0 = class_0;
+ fc.fr = fr;
+ fc.class_0_hp = class_0_hp;
+ fc.high_precision = high_precision;
+ fc.classes = classes;
+ fc.class_0_fr = class_0_fr;
+ fc.prob_bits = pred_bits;
+ fc.single_ref_prob = single_ref_prob;
+ fc.comp_ref_prob = comp_ref_prob;
+
+ // Skip the 4th element as it goes unused
+ for (std::size_t i = 0; i < coef_probs.size(); i += 4) {
+ const std::size_t j = i - i / 4;
+ fc.coef_probs[j] = coef_probs[i];
+ fc.coef_probs[j + 1] = coef_probs[i + 1];
+ fc.coef_probs[j + 2] = coef_probs[i + 2];
+ }
+ }
+};
+static_assert(sizeof(EntropyProbs) == 0xEA0, "EntropyProbs is an invalid size");
+
+enum class Ref { Last, Golden, AltRef };
+
+struct RefPoolElement {
+ s64 frame{};
+ Ref ref{};
+ bool refresh{};
+};
+
+struct FrameContexts {
+ s64 from{};
+ bool adapted{};
+ Vp9EntropyProbs probs{};
+};
+
+}; // namespace Decoder
+}; // namespace Tegra
diff --git a/src/video_core/command_classes/host1x.cpp b/src/video_core/command_classes/host1x.cpp
new file mode 100644
index 000000000..b12494528
--- /dev/null
+++ b/src/video_core/command_classes/host1x.cpp
@@ -0,0 +1,30 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "video_core/command_classes/host1x.h"
+#include "video_core/gpu.h"
+
+Tegra::Host1x::Host1x(GPU& gpu_) : gpu(gpu_) {}
+
+Tegra::Host1x::~Host1x() = default;
+
+void Tegra::Host1x::ProcessMethod(Method method, u32 argument) {
+ switch (method) {
+ case Method::LoadSyncptPayload32:
+ syncpoint_value = argument;
+ break;
+ case Method::WaitSyncpt:
+ case Method::WaitSyncpt32:
+ Execute(argument);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Host1x method 0x{:X}", static_cast<u32>(method));
+ break;
+ }
+}
+
+void Tegra::Host1x::Execute(u32 data) {
+ gpu.WaitFence(data, syncpoint_value);
+}
diff --git a/src/video_core/command_classes/host1x.h b/src/video_core/command_classes/host1x.h
new file mode 100644
index 000000000..7e94799dd
--- /dev/null
+++ b/src/video_core/command_classes/host1x.h
@@ -0,0 +1,37 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Tegra {
+class GPU;
+class Nvdec;
+
+class Host1x {
+public:
+ enum class Method : u32 {
+ WaitSyncpt = 0x8,
+ LoadSyncptPayload32 = 0x4e,
+ WaitSyncpt32 = 0x50,
+ };
+
+ explicit Host1x(GPU& gpu);
+ ~Host1x();
+
+ /// Writes the method into the state, Invoke Execute() if encountered
+ void ProcessMethod(Method method, u32 argument);
+
+private:
+ /// For Host1x, execute is waiting on a syncpoint previously written into the state
+ void Execute(u32 data);
+
+ u32 syncpoint_value{};
+ GPU& gpu;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec.cpp b/src/video_core/command_classes/nvdec.cpp
new file mode 100644
index 000000000..79e1f4e13
--- /dev/null
+++ b/src/video_core/command_classes/nvdec.cpp
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "video_core/command_classes/nvdec.h"
+#include "video_core/gpu.h"
+
+namespace Tegra {
+
+Nvdec::Nvdec(GPU& gpu_) : gpu(gpu_), codec(std::make_unique<Codec>(gpu)) {}
+
+Nvdec::~Nvdec() = default;
+
+void Nvdec::ProcessMethod(Method method, const std::vector<u32>& arguments) {
+ if (method == Method::SetVideoCodec) {
+ codec->StateWrite(static_cast<u32>(method), arguments[0]);
+ } else {
+ codec->StateWrite(static_cast<u32>(method), static_cast<u64>(arguments[0]) << 8);
+ }
+
+ switch (method) {
+ case Method::SetVideoCodec:
+ codec->SetTargetCodec(static_cast<NvdecCommon::VideoCodec>(arguments[0]));
+ break;
+ case Method::Execute:
+ Execute();
+ break;
+ }
+}
+
+AVFramePtr Nvdec::GetFrame() {
+ return codec->GetCurrentFrame();
+}
+
+void Nvdec::Execute() {
+ switch (codec->GetCurrentCodec()) {
+ case NvdecCommon::VideoCodec::H264:
+ case NvdecCommon::VideoCodec::Vp9:
+ codec->Decode();
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown codec {}", static_cast<u32>(codec->GetCurrentCodec()));
+ break;
+ }
+}
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec.h b/src/video_core/command_classes/nvdec.h
new file mode 100644
index 000000000..e4877c533
--- /dev/null
+++ b/src/video_core/command_classes/nvdec.h
@@ -0,0 +1,38 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "common/common_types.h"
+#include "video_core/command_classes/codecs/codec.h"
+
+namespace Tegra {
+class GPU;
+
+class Nvdec {
+public:
+ enum class Method : u32 {
+ SetVideoCodec = 0x80,
+ Execute = 0xc0,
+ };
+
+ explicit Nvdec(GPU& gpu);
+ ~Nvdec();
+
+ /// Writes the method into the state, Invoke Execute() if encountered
+ void ProcessMethod(Method method, const std::vector<u32>& arguments);
+
+ /// Return most recently decoded frame
+ [[nodiscard]] AVFramePtr GetFrame();
+
+private:
+ /// Invoke codec to decode a frame
+ void Execute();
+
+ GPU& gpu;
+ std::unique_ptr<Codec> codec;
+};
+} // namespace Tegra
diff --git a/src/video_core/command_classes/nvdec_common.h b/src/video_core/command_classes/nvdec_common.h
new file mode 100644
index 000000000..01b5e086d
--- /dev/null
+++ b/src/video_core/command_classes/nvdec_common.h
@@ -0,0 +1,48 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Tegra::NvdecCommon {
+
+struct NvdecRegisters {
+ INSERT_PADDING_WORDS(256);
+ u64 set_codec_id{};
+ INSERT_PADDING_WORDS(254);
+ u64 set_platform_id{};
+ u64 picture_info_offset{};
+ u64 frame_bitstream_offset{};
+ u64 frame_number{};
+ u64 h264_slice_data_offsets{};
+ u64 h264_mv_dump_offset{};
+ INSERT_PADDING_WORDS(6);
+ u64 frame_stats_offset{};
+ u64 h264_last_surface_luma_offset{};
+ u64 h264_last_surface_chroma_offset{};
+ std::array<u64, 17> surface_luma_offset{};
+ std::array<u64, 17> surface_chroma_offset{};
+ INSERT_PADDING_WORDS(132);
+ u64 vp9_entropy_probs_offset{};
+ u64 vp9_backward_updates_offset{};
+ u64 vp9_last_frame_segmap_offset{};
+ u64 vp9_curr_frame_segmap_offset{};
+ INSERT_PADDING_WORDS(2);
+ u64 vp9_last_frame_mvs_offset{};
+ u64 vp9_curr_frame_mvs_offset{};
+ INSERT_PADDING_WORDS(2);
+};
+static_assert(sizeof(NvdecRegisters) == (0xBC0), "NvdecRegisters is incorrect size");
+
+enum class VideoCodec : u32 {
+ None = 0x0,
+ H264 = 0x3,
+ Vp8 = 0x5,
+ H265 = 0x7,
+ Vp9 = 0x9,
+};
+
+} // namespace Tegra::NvdecCommon
diff --git a/src/video_core/command_classes/sync_manager.cpp b/src/video_core/command_classes/sync_manager.cpp
new file mode 100644
index 000000000..19dc9e0ab
--- /dev/null
+++ b/src/video_core/command_classes/sync_manager.cpp
@@ -0,0 +1,60 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include <algorithm>
+#include "sync_manager.h"
+#include "video_core/gpu.h"
+
+namespace Tegra {
+SyncptIncrManager::SyncptIncrManager(GPU& gpu_) : gpu(gpu_) {}
+SyncptIncrManager::~SyncptIncrManager() = default;
+
+void SyncptIncrManager::Increment(u32 id) {
+ increments.emplace_back(0, 0, id, true);
+ IncrementAllDone();
+}
+
+u32 SyncptIncrManager::IncrementWhenDone(u32 class_id, u32 id) {
+ const u32 handle = current_id++;
+ increments.emplace_back(handle, class_id, id);
+ return handle;
+}
+
+void SyncptIncrManager::SignalDone(u32 handle) {
+ const auto done_incr =
+ std::find_if(increments.begin(), increments.end(),
+ [handle](const SyncptIncr& incr) { return incr.id == handle; });
+ if (done_incr != increments.cend()) {
+ done_incr->complete = true;
+ }
+ IncrementAllDone();
+}
+
+void SyncptIncrManager::IncrementAllDone() {
+ std::size_t done_count = 0;
+ for (; done_count < increments.size(); ++done_count) {
+ if (!increments[done_count].complete) {
+ break;
+ }
+ gpu.IncrementSyncPoint(increments[done_count].syncpt_id);
+ }
+ increments.erase(increments.begin(), increments.begin() + done_count);
+}
+} // namespace Tegra
diff --git a/src/video_core/command_classes/sync_manager.h b/src/video_core/command_classes/sync_manager.h
new file mode 100644
index 000000000..2c321ec58
--- /dev/null
+++ b/src/video_core/command_classes/sync_manager.h
@@ -0,0 +1,64 @@
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#pragma once
+
+#include <mutex>
+#include <vector>
+#include "common/common_types.h"
+
+namespace Tegra {
+class GPU;
+struct SyncptIncr {
+ u32 id;
+ u32 class_id;
+ u32 syncpt_id;
+ bool complete;
+
+ SyncptIncr(u32 id_, u32 class_id_, u32 syncpt_id_, bool done = false)
+ : id(id_), class_id(class_id_), syncpt_id(syncpt_id_), complete(done) {}
+};
+
+class SyncptIncrManager {
+public:
+ explicit SyncptIncrManager(GPU& gpu);
+ ~SyncptIncrManager();
+
+ /// Add syncpoint id and increment all
+ void Increment(u32 id);
+
+ /// Returns a handle to increment later
+ u32 IncrementWhenDone(u32 class_id, u32 id);
+
+ /// IncrememntAllDone, including handle
+ void SignalDone(u32 handle);
+
+ /// Increment all sequential pending increments that are already done.
+ void IncrementAllDone();
+
+private:
+ std::vector<SyncptIncr> increments;
+ std::mutex increment_lock;
+ u32 current_id{};
+
+ GPU& gpu;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/vic.cpp b/src/video_core/command_classes/vic.cpp
new file mode 100644
index 000000000..2b7569335
--- /dev/null
+++ b/src/video_core/command_classes/vic.cpp
@@ -0,0 +1,172 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include "common/assert.h"
+#include "video_core/command_classes/nvdec.h"
+#include "video_core/command_classes/vic.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
+#include "video_core/textures/decoders.h"
+
+extern "C" {
+#include <libswscale/swscale.h>
+}
+
+namespace Tegra {
+
+Vic::Vic(GPU& gpu_, std::shared_ptr<Nvdec> nvdec_processor_)
+ : gpu(gpu_), nvdec_processor(std::move(nvdec_processor_)) {}
+Vic::~Vic() = default;
+
+void Vic::VicStateWrite(u32 offset, u32 arguments) {
+ u8* const state_offset = reinterpret_cast<u8*>(&vic_state) + offset * sizeof(u32);
+ std::memcpy(state_offset, &arguments, sizeof(u32));
+}
+
+void Vic::ProcessMethod(Method method, const std::vector<u32>& arguments) {
+ LOG_DEBUG(HW_GPU, "Vic method 0x{:X}", method);
+ VicStateWrite(static_cast<u32>(method), arguments[0]);
+ const u64 arg = static_cast<u64>(arguments[0]) << 8;
+ switch (method) {
+ case Method::Execute:
+ Execute();
+ break;
+ case Method::SetConfigStructOffset:
+ config_struct_address = arg;
+ break;
+ case Method::SetOutputSurfaceLumaOffset:
+ output_surface_luma_address = arg;
+ break;
+ case Method::SetOutputSurfaceChromaUOffset:
+ output_surface_chroma_u_address = arg;
+ break;
+ case Method::SetOutputSurfaceChromaVOffset:
+ output_surface_chroma_v_address = arg;
+ break;
+ default:
+ break;
+ }
+}
+
+void Vic::Execute() {
+ if (output_surface_luma_address == 0) {
+ LOG_ERROR(Service_NVDRV, "VIC Luma address not set. Received 0x{:X}",
+ vic_state.output_surface.luma_offset);
+ return;
+ }
+ const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)};
+ const AVFramePtr frame_ptr = nvdec_processor->GetFrame();
+ const auto* frame = frame_ptr.get();
+ if (!frame || frame->width == 0 || frame->height == 0) {
+ return;
+ }
+ const VideoPixelFormat pixel_format =
+ static_cast<VideoPixelFormat>(config.pixel_format.Value());
+ switch (pixel_format) {
+ case VideoPixelFormat::BGRA8:
+ case VideoPixelFormat::RGBA8: {
+ LOG_TRACE(Service_NVDRV, "Writing RGB Frame");
+
+ if (scaler_ctx == nullptr || frame->width != scaler_width ||
+ frame->height != scaler_height) {
+ const AVPixelFormat target_format =
+ (pixel_format == VideoPixelFormat::RGBA8) ? AV_PIX_FMT_RGBA : AV_PIX_FMT_BGRA;
+
+ sws_freeContext(scaler_ctx);
+ scaler_ctx = nullptr;
+
+ // FFmpeg returns all frames in YUV420, convert it into expected format
+ scaler_ctx =
+ sws_getContext(frame->width, frame->height, AV_PIX_FMT_YUV420P, frame->width,
+ frame->height, target_format, 0, nullptr, nullptr, nullptr);
+
+ scaler_width = frame->width;
+ scaler_height = frame->height;
+ }
+ // Get Converted frame
+ const std::size_t linear_size = frame->width * frame->height * 4;
+
+ using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>;
+ AVMallocPtr converted_frame_buffer{static_cast<u8*>(av_malloc(linear_size)), av_free};
+
+ const int converted_stride{frame->width * 4};
+ u8* const converted_frame_buf_addr{converted_frame_buffer.get()};
+
+ sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height,
+ &converted_frame_buf_addr, &converted_stride);
+
+ const u32 blk_kind = static_cast<u32>(config.block_linear_kind);
+ if (blk_kind != 0) {
+ // swizzle pitch linear to block linear
+ const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
+ const auto size = Tegra::Texture::CalculateSize(true, 4, frame->width, frame->height, 1,
+ block_height, 0);
+ std::vector<u8> swizzled_data(size);
+ Tegra::Texture::SwizzleSubrect(frame->width, frame->height, frame->width * 4,
+ frame->width, 4, swizzled_data.data(),
+ converted_frame_buffer.get(), block_height, 0, 0);
+
+ gpu.MemoryManager().WriteBlock(output_surface_luma_address, swizzled_data.data(), size);
+ } else {
+ // send pitch linear frame
+ gpu.MemoryManager().WriteBlock(output_surface_luma_address, converted_frame_buf_addr,
+ linear_size);
+ }
+ break;
+ }
+ case VideoPixelFormat::Yuv420: {
+ LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame");
+
+ const std::size_t surface_width = config.surface_width_minus1 + 1;
+ const std::size_t surface_height = config.surface_height_minus1 + 1;
+ const std::size_t half_width = surface_width / 2;
+ const std::size_t half_height = config.surface_height_minus1 / 2;
+ const std::size_t aligned_width = (surface_width + 0xff) & ~0xff;
+
+ const auto* luma_ptr = frame->data[0];
+ const auto* chroma_b_ptr = frame->data[1];
+ const auto* chroma_r_ptr = frame->data[2];
+ const auto stride = frame->linesize[0];
+ const auto half_stride = frame->linesize[1];
+
+ std::vector<u8> luma_buffer(aligned_width * surface_height);
+ std::vector<u8> chroma_buffer(aligned_width * half_height);
+
+ // Populate luma buffer
+ for (std::size_t y = 0; y < surface_height - 1; ++y) {
+ std::size_t src = y * stride;
+ std::size_t dst = y * aligned_width;
+
+ std::size_t size = surface_width;
+
+ for (std::size_t offset = 0; offset < size; ++offset) {
+ luma_buffer[dst + offset] = luma_ptr[src + offset];
+ }
+ }
+ gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(),
+ luma_buffer.size());
+
+ // Populate chroma buffer from both channels with interleaving.
+ for (std::size_t y = 0; y < half_height; ++y) {
+ std::size_t src = y * half_stride;
+ std::size_t dst = y * aligned_width;
+
+ for (std::size_t x = 0; x < half_width; ++x) {
+ chroma_buffer[dst + x * 2] = chroma_b_ptr[src + x];
+ chroma_buffer[dst + x * 2 + 1] = chroma_r_ptr[src + x];
+ }
+ }
+ gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(),
+ chroma_buffer.size());
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unknown video pixel format {}", config.pixel_format.Value());
+ break;
+ }
+}
+
+} // namespace Tegra
diff --git a/src/video_core/command_classes/vic.h b/src/video_core/command_classes/vic.h
new file mode 100644
index 000000000..8c4e284a1
--- /dev/null
+++ b/src/video_core/command_classes/vic.h
@@ -0,0 +1,110 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+struct SwsContext;
+
+namespace Tegra {
+class GPU;
+class Nvdec;
+
+struct PlaneOffsets {
+ u32 luma_offset{};
+ u32 chroma_u_offset{};
+ u32 chroma_v_offset{};
+};
+
+struct VicRegisters {
+ INSERT_PADDING_WORDS(64);
+ u32 nop{};
+ INSERT_PADDING_WORDS(15);
+ u32 pm_trigger{};
+ INSERT_PADDING_WORDS(47);
+ u32 set_application_id{};
+ u32 set_watchdog_timer{};
+ INSERT_PADDING_WORDS(17);
+ u32 context_save_area{};
+ u32 context_switch{};
+ INSERT_PADDING_WORDS(43);
+ u32 execute{};
+ INSERT_PADDING_WORDS(63);
+ std::array<std::array<PlaneOffsets, 8>, 8> surfacex_slots{};
+ u32 picture_index{};
+ u32 control_params{};
+ u32 config_struct_offset{};
+ u32 filter_struct_offset{};
+ u32 palette_offset{};
+ u32 hist_offset{};
+ u32 context_id{};
+ u32 fce_ucode_size{};
+ PlaneOffsets output_surface{};
+ u32 fce_ucode_offset{};
+ INSERT_PADDING_WORDS(4);
+ std::array<u32, 8> slot_context_id{};
+ INSERT_PADDING_WORDS(16);
+};
+static_assert(sizeof(VicRegisters) == 0x7A0, "VicRegisters is an invalid size");
+
+class Vic {
+public:
+ enum class Method : u32 {
+ Execute = 0xc0,
+ SetControlParams = 0x1c1,
+ SetConfigStructOffset = 0x1c2,
+ SetOutputSurfaceLumaOffset = 0x1c8,
+ SetOutputSurfaceChromaUOffset = 0x1c9,
+ SetOutputSurfaceChromaVOffset = 0x1ca
+ };
+
+ explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor);
+ ~Vic();
+
+ /// Write to the device state.
+ void ProcessMethod(Method method, const std::vector<u32>& arguments);
+
+private:
+ void Execute();
+
+ void VicStateWrite(u32 offset, u32 arguments);
+ VicRegisters vic_state{};
+
+ enum class VideoPixelFormat : u64_le {
+ RGBA8 = 0x1f,
+ BGRA8 = 0x20,
+ Yuv420 = 0x44,
+ };
+
+ union VicConfig {
+ u64_le raw{};
+ BitField<0, 7, u64_le> pixel_format;
+ BitField<7, 2, u64_le> chroma_loc_horiz;
+ BitField<9, 2, u64_le> chroma_loc_vert;
+ BitField<11, 4, u64_le> block_linear_kind;
+ BitField<15, 4, u64_le> block_linear_height_log2;
+ BitField<19, 3, u64_le> reserved0;
+ BitField<22, 10, u64_le> reserved1;
+ BitField<32, 14, u64_le> surface_width_minus1;
+ BitField<46, 14, u64_le> surface_height_minus1;
+ };
+
+ GPU& gpu;
+ std::shared_ptr<Tegra::Nvdec> nvdec_processor;
+
+ GPUVAddr config_struct_address{};
+ GPUVAddr output_surface_luma_address{};
+ GPUVAddr output_surface_chroma_u_address{};
+ GPUVAddr output_surface_chroma_v_address{};
+
+ SwsContext* scaler_ctx{};
+ s32 scaler_width{};
+ s32 scaler_height{};
+};
+
+} // namespace Tegra
diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp
index b06c32c84..acf2668dc 100644
--- a/src/video_core/compatible_formats.cpp
+++ b/src/video_core/compatible_formats.cpp
@@ -3,33 +3,33 @@
// Refer to the license.txt file included.
#include <array>
-#include <bitset>
#include <cstddef>
+#include "common/common_types.h"
#include "video_core/compatible_formats.h"
#include "video_core/surface.h"
namespace VideoCore::Surface {
-
namespace {
+using Table = std::array<std::array<u64, 2>, MaxPixelFormat>;
// Compatibility table taken from Table 3.X.2 in:
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_view.txt
-constexpr std::array VIEW_CLASS_128_BITS = {
+constexpr std::array VIEW_CLASS_128_BITS{
PixelFormat::R32G32B32A32_FLOAT,
PixelFormat::R32G32B32A32_UINT,
PixelFormat::R32G32B32A32_SINT,
};
-constexpr std::array VIEW_CLASS_96_BITS = {
+constexpr std::array VIEW_CLASS_96_BITS{
PixelFormat::R32G32B32_FLOAT,
};
// Missing formats:
// PixelFormat::RGB32UI,
// PixelFormat::RGB32I,
-constexpr std::array VIEW_CLASS_64_BITS = {
+constexpr std::array VIEW_CLASS_64_BITS{
PixelFormat::R32G32_FLOAT, PixelFormat::R32G32_UINT,
PixelFormat::R32G32_SINT, PixelFormat::R16G16B16A16_FLOAT,
PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM,
@@ -38,7 +38,7 @@ constexpr std::array VIEW_CLASS_64_BITS = {
// TODO: How should we handle 48 bits?
-constexpr std::array VIEW_CLASS_32_BITS = {
+constexpr std::array VIEW_CLASS_32_BITS{
PixelFormat::R16G16_FLOAT, PixelFormat::B10G11R11_FLOAT, PixelFormat::R32_FLOAT,
PixelFormat::A2B10G10R10_UNORM, PixelFormat::R16G16_UINT, PixelFormat::R32_UINT,
PixelFormat::R16G16_SINT, PixelFormat::R32_SINT, PixelFormat::A8B8G8R8_UNORM,
@@ -50,43 +50,105 @@ constexpr std::array VIEW_CLASS_32_BITS = {
// TODO: How should we handle 24 bits?
-constexpr std::array VIEW_CLASS_16_BITS = {
+constexpr std::array VIEW_CLASS_16_BITS{
PixelFormat::R16_FLOAT, PixelFormat::R8G8_UINT, PixelFormat::R16_UINT,
PixelFormat::R16_SINT, PixelFormat::R8G8_UNORM, PixelFormat::R16_UNORM,
PixelFormat::R8G8_SNORM, PixelFormat::R16_SNORM, PixelFormat::R8G8_SINT,
};
-constexpr std::array VIEW_CLASS_8_BITS = {
+constexpr std::array VIEW_CLASS_8_BITS{
PixelFormat::R8_UINT,
PixelFormat::R8_UNORM,
PixelFormat::R8_SINT,
PixelFormat::R8_SNORM,
};
-constexpr std::array VIEW_CLASS_RGTC1_RED = {
+constexpr std::array VIEW_CLASS_RGTC1_RED{
PixelFormat::BC4_UNORM,
PixelFormat::BC4_SNORM,
};
-constexpr std::array VIEW_CLASS_RGTC2_RG = {
+constexpr std::array VIEW_CLASS_RGTC2_RG{
PixelFormat::BC5_UNORM,
PixelFormat::BC5_SNORM,
};
-constexpr std::array VIEW_CLASS_BPTC_UNORM = {
+constexpr std::array VIEW_CLASS_BPTC_UNORM{
PixelFormat::BC7_UNORM,
PixelFormat::BC7_SRGB,
};
-constexpr std::array VIEW_CLASS_BPTC_FLOAT = {
+constexpr std::array VIEW_CLASS_BPTC_FLOAT{
PixelFormat::BC6H_SFLOAT,
PixelFormat::BC6H_UFLOAT,
};
+constexpr std::array VIEW_CLASS_ASTC_4x4_RGBA{
+ PixelFormat::ASTC_2D_4X4_UNORM,
+ PixelFormat::ASTC_2D_4X4_SRGB,
+};
+
+constexpr std::array VIEW_CLASS_ASTC_5x4_RGBA{
+ PixelFormat::ASTC_2D_5X4_UNORM,
+ PixelFormat::ASTC_2D_5X4_SRGB,
+};
+
+constexpr std::array VIEW_CLASS_ASTC_5x5_RGBA{
+ PixelFormat::ASTC_2D_5X5_UNORM,
+ PixelFormat::ASTC_2D_5X5_SRGB,
+};
+
+constexpr std::array VIEW_CLASS_ASTC_6x5_RGBA{
+ PixelFormat::ASTC_2D_6X5_UNORM,
+ PixelFormat::ASTC_2D_6X5_SRGB,
+};
+
+constexpr std::array VIEW_CLASS_ASTC_6x6_RGBA{
+ PixelFormat::ASTC_2D_6X6_UNORM,
+ PixelFormat::ASTC_2D_6X6_SRGB,
+};
+
+constexpr std::array VIEW_CLASS_ASTC_8x5_RGBA{
+ PixelFormat::ASTC_2D_8X5_UNORM,
+ PixelFormat::ASTC_2D_8X5_SRGB,
+};
+
+constexpr std::array VIEW_CLASS_ASTC_8x8_RGBA{
+ PixelFormat::ASTC_2D_8X8_UNORM,
+ PixelFormat::ASTC_2D_8X8_SRGB,
+};
+
+// Missing formats:
+// PixelFormat::ASTC_2D_10X5_UNORM
+// PixelFormat::ASTC_2D_10X5_SRGB
+
+// Missing formats:
+// PixelFormat::ASTC_2D_10X6_UNORM
+// PixelFormat::ASTC_2D_10X6_SRGB
+
+constexpr std::array VIEW_CLASS_ASTC_10x8_RGBA{
+ PixelFormat::ASTC_2D_10X8_UNORM,
+ PixelFormat::ASTC_2D_10X8_SRGB,
+};
+
+constexpr std::array VIEW_CLASS_ASTC_10x10_RGBA{
+ PixelFormat::ASTC_2D_10X10_UNORM,
+ PixelFormat::ASTC_2D_10X10_SRGB,
+};
+
+// Missing formats
+// ASTC_2D_12X10_UNORM,
+// ASTC_2D_12X10_SRGB,
+
+constexpr std::array VIEW_CLASS_ASTC_12x12_RGBA{
+ PixelFormat::ASTC_2D_12X12_UNORM,
+ PixelFormat::ASTC_2D_12X12_SRGB,
+};
+
// Compatibility table taken from Table 4.X.1 in:
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_copy_image.txt
-constexpr std::array COPY_CLASS_128_BITS = {
+constexpr std::array COPY_CLASS_128_BITS{
PixelFormat::R32G32B32A32_UINT, PixelFormat::R32G32B32A32_FLOAT, PixelFormat::R32G32B32A32_SINT,
PixelFormat::BC2_UNORM, PixelFormat::BC2_SRGB, PixelFormat::BC3_UNORM,
PixelFormat::BC3_SRGB, PixelFormat::BC5_UNORM, PixelFormat::BC5_SNORM,
@@ -97,7 +159,7 @@ constexpr std::array COPY_CLASS_128_BITS = {
// PixelFormat::RGBA32I
// COMPRESSED_RG_RGTC2
-constexpr std::array COPY_CLASS_64_BITS = {
+constexpr std::array COPY_CLASS_64_BITS{
PixelFormat::R16G16B16A16_FLOAT, PixelFormat::R16G16B16A16_UINT,
PixelFormat::R16G16B16A16_UNORM, PixelFormat::R16G16B16A16_SNORM,
PixelFormat::R16G16B16A16_SINT, PixelFormat::R32G32_UINT,
@@ -110,32 +172,36 @@ constexpr std::array COPY_CLASS_64_BITS = {
// COMPRESSED_RGBA_S3TC_DXT1_EXT
// COMPRESSED_SIGNED_RED_RGTC1
-void Enable(FormatCompatibility::Table& compatiblity, size_t format_a, size_t format_b) {
- compatiblity[format_a][format_b] = true;
- compatiblity[format_b][format_a] = true;
+constexpr void Enable(Table& table, size_t format_a, size_t format_b) {
+ table[format_a][format_b / 64] |= u64(1) << (format_b % 64);
+ table[format_b][format_a / 64] |= u64(1) << (format_a % 64);
}
-void Enable(FormatCompatibility::Table& compatibility, PixelFormat format_a, PixelFormat format_b) {
- Enable(compatibility, static_cast<size_t>(format_a), static_cast<size_t>(format_b));
+constexpr void Enable(Table& table, PixelFormat format_a, PixelFormat format_b) {
+ Enable(table, static_cast<size_t>(format_a), static_cast<size_t>(format_b));
}
template <typename Range>
-void EnableRange(FormatCompatibility::Table& compatibility, const Range& range) {
+constexpr void EnableRange(Table& table, const Range& range) {
for (auto it_a = range.begin(); it_a != range.end(); ++it_a) {
for (auto it_b = it_a; it_b != range.end(); ++it_b) {
- Enable(compatibility, *it_a, *it_b);
+ Enable(table, *it_a, *it_b);
}
}
}
-} // Anonymous namespace
+constexpr bool IsSupported(const Table& table, PixelFormat format_a, PixelFormat format_b) {
+ const size_t a = static_cast<size_t>(format_a);
+ const size_t b = static_cast<size_t>(format_b);
+ return ((table[a][b / 64] >> (b % 64)) & 1) != 0;
+}
-FormatCompatibility::FormatCompatibility() {
+constexpr Table MakeViewTable() {
+ Table view{};
for (size_t i = 0; i < MaxPixelFormat; ++i) {
// Identity is allowed
Enable(view, i, i);
}
-
EnableRange(view, VIEW_CLASS_128_BITS);
EnableRange(view, VIEW_CLASS_96_BITS);
EnableRange(view, VIEW_CLASS_64_BITS);
@@ -146,10 +212,39 @@ FormatCompatibility::FormatCompatibility() {
EnableRange(view, VIEW_CLASS_RGTC2_RG);
EnableRange(view, VIEW_CLASS_BPTC_UNORM);
EnableRange(view, VIEW_CLASS_BPTC_FLOAT);
+ EnableRange(view, VIEW_CLASS_ASTC_4x4_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_5x4_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_5x5_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_6x5_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_6x6_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_8x5_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_8x8_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_10x8_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA);
+ return view;
+}
- copy = view;
+constexpr Table MakeCopyTable() {
+ Table copy = MakeViewTable();
EnableRange(copy, COPY_CLASS_128_BITS);
EnableRange(copy, COPY_CLASS_64_BITS);
+ return copy;
+}
+} // Anonymous namespace
+
+bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_views) {
+ if (broken_views) {
+ // If format views are broken, only accept formats that are identical.
+ return format_a == format_b;
+ }
+ static constexpr Table TABLE = MakeViewTable();
+ return IsSupported(TABLE, format_a, format_b);
+}
+
+bool IsCopyCompatible(PixelFormat format_a, PixelFormat format_b) {
+ static constexpr Table TABLE = MakeCopyTable();
+ return IsSupported(TABLE, format_a, format_b);
}
} // namespace VideoCore::Surface
diff --git a/src/video_core/compatible_formats.h b/src/video_core/compatible_formats.h
index 51766349b..9a0522988 100644
--- a/src/video_core/compatible_formats.h
+++ b/src/video_core/compatible_formats.h
@@ -4,31 +4,12 @@
#pragma once
-#include <array>
-#include <bitset>
-#include <cstddef>
-
#include "video_core/surface.h"
namespace VideoCore::Surface {
-class FormatCompatibility {
-public:
- using Table = std::array<std::bitset<MaxPixelFormat>, MaxPixelFormat>;
-
- explicit FormatCompatibility();
-
- bool TestView(PixelFormat format_a, PixelFormat format_b) const noexcept {
- return view[static_cast<size_t>(format_a)][static_cast<size_t>(format_b)];
- }
-
- bool TestCopy(PixelFormat format_a, PixelFormat format_b) const noexcept {
- return copy[static_cast<size_t>(format_a)][static_cast<size_t>(format_b)];
- }
+bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_views);
-private:
- Table view;
- Table copy;
-};
+bool IsCopyCompatible(PixelFormat format_a, PixelFormat format_b);
} // namespace VideoCore::Surface
diff --git a/src/video_core/delayed_destruction_ring.h b/src/video_core/delayed_destruction_ring.h
new file mode 100644
index 000000000..4f1d29c04
--- /dev/null
+++ b/src/video_core/delayed_destruction_ring.h
@@ -0,0 +1,32 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <utility>
+#include <vector>
+
+namespace VideoCommon {
+
+/// Container to push objects to be destroyed a few ticks in the future
+template <typename T, size_t TICKS_TO_DESTROY>
+class DelayedDestructionRing {
+public:
+ void Tick() {
+ index = (index + 1) % TICKS_TO_DESTROY;
+ elements[index].clear();
+ }
+
+ void Push(T&& object) {
+ elements[index].push_back(std::move(object));
+ }
+
+private:
+ size_t index = 0;
+ std::array<std::vector<T>, TICKS_TO_DESTROY> elements;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/dirty_flags.cpp b/src/video_core/dirty_flags.cpp
index e16075993..7149af290 100644
--- a/src/video_core/dirty_flags.cpp
+++ b/src/video_core/dirty_flags.cpp
@@ -9,13 +9,33 @@
#include "video_core/dirty_flags.h"
#define OFF(field_name) MAXWELL3D_REG_INDEX(field_name)
-#define NUM(field_name) (sizeof(::Tegra::Engines::Maxwell3D::Regs::field_name) / sizeof(u32))
+#define NUM(field_name) (sizeof(::Tegra::Engines::Maxwell3D::Regs::field_name) / (sizeof(u32)))
namespace VideoCommon::Dirty {
-
+namespace {
using Tegra::Engines::Maxwell3D;
-void SetupDirtyRenderTargets(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables) {
+void SetupDirtyVertexBuffers(Maxwell3D::DirtyState::Tables& tables) {
+ static constexpr std::size_t num_array = 3;
+ for (std::size_t i = 0; i < Maxwell3D::Regs::NumVertexArrays; ++i) {
+ const std::size_t array_offset = OFF(vertex_array) + i * NUM(vertex_array[0]);
+ const std::size_t limit_offset = OFF(vertex_array_limit) + i * NUM(vertex_array_limit[0]);
+
+ FillBlock(tables, array_offset, num_array, VertexBuffer0 + i, VertexBuffers);
+ FillBlock(tables, limit_offset, NUM(vertex_array_limit), VertexBuffer0 + i, VertexBuffers);
+ }
+}
+
+void SetupIndexBuffer(Maxwell3D::DirtyState::Tables& tables) {
+ FillBlock(tables[0], OFF(index_array), NUM(index_array), IndexBuffer);
+}
+
+void SetupDirtyDescriptors(Maxwell3D::DirtyState::Tables& tables) {
+ FillBlock(tables[0], OFF(tic), NUM(tic), Descriptors);
+ FillBlock(tables[0], OFF(tsc), NUM(tsc), Descriptors);
+}
+
+void SetupDirtyRenderTargets(Maxwell3D::DirtyState::Tables& tables) {
static constexpr std::size_t num_per_rt = NUM(rt[0]);
static constexpr std::size_t begin = OFF(rt);
static constexpr std::size_t num = num_per_rt * Maxwell3D::Regs::NumRenderTargets;
@@ -23,6 +43,10 @@ void SetupDirtyRenderTargets(Tegra::Engines::Maxwell3D::DirtyState::Tables& tabl
FillBlock(tables[0], begin + rt * num_per_rt, num_per_rt, ColorBuffer0 + rt);
}
FillBlock(tables[1], begin, num, RenderTargets);
+ FillBlock(tables[0], OFF(render_area), NUM(render_area), RenderTargets);
+
+ tables[0][OFF(rt_control)] = RenderTargets;
+ tables[1][OFF(rt_control)] = RenderTargetControl;
static constexpr std::array zeta_flags{ZetaBuffer, RenderTargets};
for (std::size_t i = 0; i < std::size(zeta_flags); ++i) {
@@ -34,5 +58,13 @@ void SetupDirtyRenderTargets(Tegra::Engines::Maxwell3D::DirtyState::Tables& tabl
FillBlock(table, OFF(zeta), NUM(zeta), flag);
}
}
+} // Anonymous namespace
+
+void SetupDirtyFlags(Maxwell3D::DirtyState::Tables& tables) {
+ SetupDirtyVertexBuffers(tables);
+ SetupIndexBuffer(tables);
+ SetupDirtyDescriptors(tables);
+ SetupDirtyRenderTargets(tables);
+}
} // namespace VideoCommon::Dirty
diff --git a/src/video_core/dirty_flags.h b/src/video_core/dirty_flags.h
index 3f6c1d83a..702688ace 100644
--- a/src/video_core/dirty_flags.h
+++ b/src/video_core/dirty_flags.h
@@ -16,7 +16,10 @@ namespace VideoCommon::Dirty {
enum : u8 {
NullEntry = 0,
+ Descriptors,
+
RenderTargets,
+ RenderTargetControl,
ColorBuffer0,
ColorBuffer1,
ColorBuffer2,
@@ -27,6 +30,12 @@ enum : u8 {
ColorBuffer7,
ZetaBuffer,
+ VertexBuffers,
+ VertexBuffer0,
+ VertexBuffer31 = VertexBuffer0 + 31,
+
+ IndexBuffer,
+
LastCommonEntry,
};
@@ -44,6 +53,6 @@ void FillBlock(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables, std::size_
FillBlock(tables[1], begin, num, index_b);
}
-void SetupDirtyRenderTargets(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables);
+void SetupDirtyFlags(Tegra::Engines::Maxwell3D::DirtyState::Tables& tables);
} // namespace VideoCommon::Dirty
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index f2f96ac33..8b33c04ab 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/cityhash.h"
#include "common/microprofile.h"
#include "core/core.h"
#include "core/memory.h"
@@ -12,7 +13,7 @@
namespace Tegra {
-DmaPusher::DmaPusher(Core::System& system, GPU& gpu) : gpu{gpu}, system{system} {}
+DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_) : gpu{gpu_}, system{system_} {}
DmaPusher::~DmaPusher() = default;
@@ -22,8 +23,6 @@ void DmaPusher::DispatchCalls() {
MICROPROFILE_SCOPE(DispatchCalls);
gpu.SyncGuestHost();
- // On entering GPU code, assume all memory may be touched by the ARM core.
- gpu.Maxwell3D().OnMemoryWrite();
dma_pushbuffer_subindex = 0;
@@ -45,32 +44,41 @@ bool DmaPusher::Step() {
return false;
}
- const CommandList& command_list{dma_pushbuffer.front()};
- ASSERT_OR_EXECUTE(!command_list.empty(), {
- // Somehow the command_list is empty, in order to avoid a crash
- // We ignore it and assume its size is 0.
- dma_pushbuffer.pop();
- dma_pushbuffer_subindex = 0;
- return true;
- });
- const CommandListHeader command_list_header{command_list[dma_pushbuffer_subindex++]};
- const GPUVAddr dma_get = command_list_header.addr;
-
- if (dma_pushbuffer_subindex >= command_list.size()) {
- // We've gone through the current list, remove it from the queue
- dma_pushbuffer.pop();
- dma_pushbuffer_subindex = 0;
- }
+ CommandList& command_list{dma_pushbuffer.front()};
- if (command_list_header.size == 0) {
- return true;
- }
+ ASSERT_OR_EXECUTE(
+ command_list.command_lists.size() || command_list.prefetch_command_list.size(), {
+ // Somehow the command_list is empty, in order to avoid a crash
+ // We ignore it and assume its size is 0.
+ dma_pushbuffer.pop();
+ dma_pushbuffer_subindex = 0;
+ return true;
+ });
+
+ if (command_list.prefetch_command_list.size()) {
+ // Prefetched command list from nvdrv, used for things like synchronization
+ command_headers = std::move(command_list.prefetch_command_list);
+ dma_pushbuffer.pop();
+ } else {
+ const CommandListHeader command_list_header{
+ command_list.command_lists[dma_pushbuffer_subindex++]};
+ const GPUVAddr dma_get = command_list_header.addr;
+
+ if (dma_pushbuffer_subindex >= command_list.command_lists.size()) {
+ // We've gone through the current list, remove it from the queue
+ dma_pushbuffer.pop();
+ dma_pushbuffer_subindex = 0;
+ }
- // Push buffer non-empty, read a word
- command_headers.resize(command_list_header.size);
- gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
- command_list_header.size * sizeof(u32));
+ if (command_list_header.size == 0) {
+ return true;
+ }
+ // Push buffer non-empty, read a word
+ command_headers.resize(command_list_header.size);
+ gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
+ command_list_header.size * sizeof(u32));
+ }
for (std::size_t index = 0; index < command_headers.size();) {
const CommandHeader& command_header = command_headers[index];
@@ -142,7 +150,12 @@ void DmaPusher::SetState(const CommandHeader& command_header) {
void DmaPusher::CallMethod(u32 argument) const {
if (dma_state.method < non_puller_methods) {
- gpu.CallMethod({dma_state.method, argument, dma_state.subchannel, dma_state.method_count});
+ gpu.CallMethod(GPU::MethodCall{
+ dma_state.method,
+ argument,
+ dma_state.subchannel,
+ dma_state.method_count,
+ });
} else {
subchannels[dma_state.subchannel]->CallMethod(dma_state.method, argument,
dma_state.is_last_call);
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index efa90d170..19f286fa7 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -18,6 +18,8 @@ class System;
namespace Tegra {
+class GPU;
+
enum class SubmissionMode : u32 {
IncreasingOld = 0,
Increasing = 1,
@@ -27,6 +29,31 @@ enum class SubmissionMode : u32 {
IncreaseOnce = 5
};
+// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
+// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4.
+// So the values you see in docs might be multiplied by 4.
+enum class BufferMethods : u32 {
+ BindObject = 0x0,
+ Nop = 0x2,
+ SemaphoreAddressHigh = 0x4,
+ SemaphoreAddressLow = 0x5,
+ SemaphoreSequence = 0x6,
+ SemaphoreTrigger = 0x7,
+ NotifyIntr = 0x8,
+ WrcacheFlush = 0x9,
+ Unk28 = 0xA,
+ UnkCacheFlush = 0xB,
+ RefCnt = 0x14,
+ SemaphoreAcquire = 0x1A,
+ SemaphoreRelease = 0x1B,
+ FenceValue = 0x1C,
+ FenceAction = 0x1D,
+ WaitForInterrupt = 0x1E,
+ Unk7c = 0x1F,
+ Yield = 0x20,
+ NonPullerMethods = 0x40,
+};
+
struct CommandListHeader {
union {
u64 raw;
@@ -49,9 +76,23 @@ union CommandHeader {
static_assert(std::is_standard_layout_v<CommandHeader>, "CommandHeader is not standard layout");
static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!");
-class GPU;
+inline CommandHeader BuildCommandHeader(BufferMethods method, u32 arg_count, SubmissionMode mode) {
+ CommandHeader result{};
+ result.method.Assign(static_cast<u32>(method));
+ result.arg_count.Assign(arg_count);
+ result.mode.Assign(mode);
+ return result;
+}
-using CommandList = std::vector<Tegra::CommandListHeader>;
+struct CommandList final {
+ CommandList() = default;
+ explicit CommandList(std::size_t size) : command_lists(size) {}
+ explicit CommandList(std::vector<CommandHeader>&& prefetch_command_list_)
+ : prefetch_command_list{std::move(prefetch_command_list_)} {}
+
+ std::vector<CommandListHeader> command_lists;
+ std::vector<CommandHeader> prefetch_command_list;
+};
/**
* The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the
@@ -60,9 +101,9 @@ using CommandList = std::vector<Tegra::CommandListHeader>;
* See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for
* details on this implementation.
*/
-class DmaPusher {
+class DmaPusher final {
public:
- explicit DmaPusher(Core::System& system, GPU& gpu);
+ explicit DmaPusher(Core::System& system_, GPU& gpu_);
~DmaPusher();
void Push(CommandList&& entries) {
@@ -71,7 +112,7 @@ public:
void DispatchCalls();
- void BindSubchannel(Tegra::Engines::EngineInterface* engine, u32 subchannel_id) {
+ void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id) {
subchannels[subchannel_id] = engine;
}
@@ -104,7 +145,7 @@ private:
bool ib_enable{true}; ///< IB mode enabled
- std::array<Tegra::Engines::EngineInterface*, max_subchannels> subchannels{};
+ std::array<Engines::EngineInterface*, max_subchannels> subchannels{};
GPU& gpu;
Core::System& system;
diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp
index d44ad0cd8..71d7e1473 100644
--- a/src/video_core/engines/engine_upload.cpp
+++ b/src/video_core/engines/engine_upload.cpp
@@ -11,16 +11,16 @@
namespace Tegra::Engines::Upload {
-State::State(MemoryManager& memory_manager, Registers& regs)
- : regs{regs}, memory_manager{memory_manager} {}
+State::State(MemoryManager& memory_manager_, Registers& regs_)
+ : regs{regs_}, memory_manager{memory_manager_} {}
State::~State() = default;
-void State::ProcessExec(const bool is_linear) {
+void State::ProcessExec(const bool is_linear_) {
write_offset = 0;
copy_size = regs.line_length_in * regs.line_count;
inner_buffer.resize(copy_size);
- this->is_linear = is_linear;
+ is_linear = is_linear_;
}
void State::ProcessData(const u32 data, const bool is_last_call) {
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h
index 462da419e..1c7f1effa 100644
--- a/src/video_core/engines/engine_upload.h
+++ b/src/video_core/engines/engine_upload.h
@@ -54,10 +54,10 @@ struct Registers {
class State {
public:
- State(MemoryManager& memory_manager, Registers& regs);
+ explicit State(MemoryManager& memory_manager_, Registers& regs_);
~State();
- void ProcessExec(bool is_linear);
+ void ProcessExec(bool is_linear_);
void ProcessData(u32 data, bool is_last_call);
private:
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 6e50661a3..0f640fdae 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -10,90 +10,58 @@
namespace Tegra::Engines {
-Fermi2D::Fermi2D() = default;
+Fermi2D::Fermi2D() {
+ // Nvidia's OpenGL driver seems to assume these values
+ regs.src.depth = 1;
+ regs.dst.depth = 1;
+}
Fermi2D::~Fermi2D() = default;
-void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
- rasterizer = &rasterizer_;
+void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
+ rasterizer = rasterizer_;
}
void Fermi2D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Fermi2D register, increase the size of the Regs structure");
-
regs.reg_array[method] = method_argument;
- switch (method) {
- // Trigger the surface copy on the last register write. This is blit_src_y, but this is 64-bit,
- // so trigger on the second 32-bit write.
- case FERMI2D_REG_INDEX(blit_src_y) + 1: {
- HandleSurfaceCopy();
- break;
- }
+ if (method == FERMI2D_REG_INDEX(pixels_from_memory.src_y0) + 1) {
+ Blit();
}
}
void Fermi2D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) {
- for (std::size_t i = 0; i < amount; i++) {
- CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
+ for (u32 i = 0; i < amount; ++i) {
+ CallMethod(method, base_start[i], methods_pending - i <= 1);
}
}
-static std::pair<u32, u32> DelimitLine(u32 src_1, u32 src_2, u32 dst_1, u32 dst_2, u32 src_line) {
- const u32 line_a = src_2 - src_1;
- const u32 line_b = dst_2 - dst_1;
- const u32 excess = std::max<s32>(0, line_a - src_line + src_1);
- return {line_b - (excess * line_b) / line_a, excess};
-}
+void Fermi2D::Blit() {
+ LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}",
+ regs.src.Address(), regs.dst.Address());
-void Fermi2D::HandleSurfaceCopy() {
- LOG_DEBUG(HW_GPU, "Requested a surface copy with operation {}",
- static_cast<u32>(regs.operation));
-
- // TODO(Subv): Only raw copies are implemented.
- ASSERT(regs.operation == Operation::SrcCopy);
-
- const u32 src_blit_x1{static_cast<u32>(regs.blit_src_x >> 32)};
- const u32 src_blit_y1{static_cast<u32>(regs.blit_src_y >> 32)};
- u32 src_blit_x2, src_blit_y2;
- if (regs.blit_control.origin == Origin::Corner) {
- src_blit_x2 =
- static_cast<u32>((regs.blit_src_x + (regs.blit_du_dx * regs.blit_dst_width)) >> 32);
- src_blit_y2 =
- static_cast<u32>((regs.blit_src_y + (regs.blit_dv_dy * regs.blit_dst_height)) >> 32);
- } else {
- src_blit_x2 = static_cast<u32>((regs.blit_src_x >> 32) + regs.blit_dst_width);
- src_blit_y2 = static_cast<u32>((regs.blit_src_y >> 32) + regs.blit_dst_height);
- }
- u32 dst_blit_x2 = regs.blit_dst_x + regs.blit_dst_width;
- u32 dst_blit_y2 = regs.blit_dst_y + regs.blit_dst_height;
- const auto [new_dst_w, src_excess_x] =
- DelimitLine(src_blit_x1, src_blit_x2, regs.blit_dst_x, dst_blit_x2, regs.src.width);
- const auto [new_dst_h, src_excess_y] =
- DelimitLine(src_blit_y1, src_blit_y2, regs.blit_dst_y, dst_blit_y2, regs.src.height);
- dst_blit_x2 = new_dst_w + regs.blit_dst_x;
- src_blit_x2 = src_blit_x2 - src_excess_x;
- dst_blit_y2 = new_dst_h + regs.blit_dst_y;
- src_blit_y2 = src_blit_y2 - src_excess_y;
- const auto [new_src_w, dst_excess_x] =
- DelimitLine(regs.blit_dst_x, dst_blit_x2, src_blit_x1, src_blit_x2, regs.dst.width);
- const auto [new_src_h, dst_excess_y] =
- DelimitLine(regs.blit_dst_y, dst_blit_y2, src_blit_y1, src_blit_y2, regs.dst.height);
- src_blit_x2 = new_src_w + src_blit_x1;
- dst_blit_x2 = dst_blit_x2 - dst_excess_x;
- src_blit_y2 = new_src_h + src_blit_y1;
- dst_blit_y2 = dst_blit_y2 - dst_excess_y;
- const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2};
- const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y, dst_blit_x2,
- dst_blit_y2};
- Config copy_config;
- copy_config.operation = regs.operation;
- copy_config.filter = regs.blit_control.filter;
- copy_config.src_rect = src_rect;
- copy_config.dst_rect = dst_rect;
+ UNIMPLEMENTED_IF_MSG(regs.operation != Operation::SrcCopy, "Operation is not copy");
+ UNIMPLEMENTED_IF_MSG(regs.src.layer != 0, "Source layer is not zero");
+ UNIMPLEMENTED_IF_MSG(regs.dst.layer != 0, "Destination layer is not zero");
+ UNIMPLEMENTED_IF_MSG(regs.src.depth != 1, "Source depth is not one");
+ UNIMPLEMENTED_IF_MSG(regs.clip_enable != 0, "Clipped blit enabled");
- if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) {
+ const auto& args = regs.pixels_from_memory;
+ const Config config{
+ .operation = regs.operation,
+ .filter = args.sample_mode.filter,
+ .dst_x0 = args.dst_x0,
+ .dst_y0 = args.dst_y0,
+ .dst_x1 = args.dst_x0 + args.dst_width,
+ .dst_y1 = args.dst_y0 + args.dst_height,
+ .src_x0 = static_cast<s32>(args.src_x0 >> 32),
+ .src_y0 = static_cast<s32>(args.src_y0 >> 32),
+ .src_x1 = static_cast<s32>((args.du_dx * args.dst_width + args.src_x0) >> 32),
+ .src_y1 = static_cast<s32>((args.dv_dy * args.dst_height + args.src_y0) >> 32),
+ };
+ if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, config)) {
UNIMPLEMENTED();
}
}
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 213abfaae..c808a577d 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -38,7 +38,7 @@ public:
~Fermi2D();
/// Binds a rasterizer to this engine.
- void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
+ void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
@@ -53,8 +53,8 @@ public:
};
enum class Filter : u32 {
- PointSample = 0, // Nearest
- Linear = 1,
+ Point = 0,
+ Bilinear = 1,
};
enum class Operation : u32 {
@@ -67,88 +67,235 @@ public:
BlendPremult = 6,
};
- struct Regs {
- static constexpr std::size_t NUM_REGS = 0x258;
+ enum class MemoryLayout : u32 {
+ BlockLinear = 0,
+ Pitch = 1,
+ };
- struct Surface {
- RenderTargetFormat format;
- BitField<0, 1, u32> linear;
- union {
- BitField<0, 4, u32> block_width;
- BitField<4, 4, u32> block_height;
- BitField<8, 4, u32> block_depth;
- };
- u32 depth;
- u32 layer;
- u32 pitch;
- u32 width;
- u32 height;
- u32 address_high;
- u32 address_low;
-
- GPUVAddr Address() const {
- return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
- address_low);
- }
-
- u32 BlockWidth() const {
- return block_width.Value();
- }
-
- u32 BlockHeight() const {
- return block_height.Value();
- }
-
- u32 BlockDepth() const {
- return block_depth.Value();
- }
- };
- static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size");
+ enum class CpuIndexWrap : u32 {
+ Wrap = 0,
+ NoWrap = 1,
+ };
+ struct Surface {
+ RenderTargetFormat format;
+ MemoryLayout linear;
union {
- struct {
- INSERT_UNION_PADDING_WORDS(0x80);
+ BitField<0, 4, u32> block_width;
+ BitField<4, 4, u32> block_height;
+ BitField<8, 4, u32> block_depth;
+ };
+ u32 depth;
+ u32 layer;
+ u32 pitch;
+ u32 width;
+ u32 height;
+ u32 addr_upper;
+ u32 addr_lower;
+
+ [[nodiscard]] constexpr GPUVAddr Address() const noexcept {
+ return (static_cast<GPUVAddr>(addr_upper) << 32) | static_cast<GPUVAddr>(addr_lower);
+ }
+ };
+ static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size");
- Surface dst;
+ enum class SectorPromotion : u32 {
+ NoPromotion = 0,
+ PromoteTo2V = 1,
+ PromoteTo2H = 2,
+ PromoteTo4 = 3,
+ };
- INSERT_UNION_PADDING_WORDS(2);
+ enum class NumTpcs : u32 {
+ All = 0,
+ One = 1,
+ };
- Surface src;
+ enum class RenderEnableMode : u32 {
+ False = 0,
+ True = 1,
+ Conditional = 2,
+ RenderIfEqual = 3,
+ RenderIfNotEqual = 4,
+ };
- INSERT_UNION_PADDING_WORDS(0x15);
+ enum class ColorKeyFormat : u32 {
+ A16R56G6B5 = 0,
+ A1R5G55B5 = 1,
+ A8R8G8B8 = 2,
+ A2R10G10B10 = 3,
+ Y8 = 4,
+ Y16 = 5,
+ Y32 = 6,
+ };
- Operation operation;
+ union Beta4 {
+ BitField<0, 8, u32> b;
+ BitField<8, 8, u32> g;
+ BitField<16, 8, u32> r;
+ BitField<24, 8, u32> a;
+ };
- INSERT_UNION_PADDING_WORDS(0x177);
+ struct Point {
+ u32 x;
+ u32 y;
+ };
+ enum class PatternSelect : u32 {
+ MonoChrome8x8 = 0,
+ MonoChrome64x1 = 1,
+ MonoChrome1x64 = 2,
+ Color = 3,
+ };
+
+ enum class NotifyType : u32 {
+ WriteOnly = 0,
+ WriteThenAwaken = 1,
+ };
+
+ enum class MonochromePatternColorFormat : u32 {
+ A8X8R8G6B5 = 0,
+ A1R5G5B5 = 1,
+ A8R8G8B8 = 2,
+ A8Y8 = 3,
+ A8X8Y16 = 4,
+ Y32 = 5,
+ };
+
+ enum class MonochromePatternFormat : u32 {
+ CGA6_M1 = 0,
+ LE_M1 = 1,
+ };
+
+ union Regs {
+ static constexpr std::size_t NUM_REGS = 0x258;
+ struct {
+ u32 object;
+ INSERT_PADDING_WORDS_NOINIT(0x3F);
+ u32 no_operation;
+ NotifyType notify;
+ INSERT_PADDING_WORDS_NOINIT(0x2);
+ u32 wait_for_idle;
+ INSERT_PADDING_WORDS_NOINIT(0xB);
+ u32 pm_trigger;
+ INSERT_PADDING_WORDS_NOINIT(0xF);
+ u32 context_dma_notify;
+ u32 dst_context_dma;
+ u32 src_context_dma;
+ u32 semaphore_context_dma;
+ INSERT_PADDING_WORDS_NOINIT(0x1C);
+ Surface dst;
+ CpuIndexWrap pixels_from_cpu_index_wrap;
+ u32 kind2d_check_enable;
+ Surface src;
+ SectorPromotion pixels_from_memory_sector_promotion;
+ INSERT_PADDING_WORDS_NOINIT(0x1);
+ NumTpcs num_tpcs;
+ u32 render_enable_addr_upper;
+ u32 render_enable_addr_lower;
+ RenderEnableMode render_enable_mode;
+ INSERT_PADDING_WORDS_NOINIT(0x4);
+ u32 clip_x0;
+ u32 clip_y0;
+ u32 clip_width;
+ u32 clip_height;
+ BitField<0, 1, u32> clip_enable;
+ BitField<0, 3, ColorKeyFormat> color_key_format;
+ u32 color_key;
+ BitField<0, 1, u32> color_key_enable;
+ BitField<0, 8, u32> rop;
+ u32 beta1;
+ Beta4 beta4;
+ Operation operation;
+ union {
+ BitField<0, 6, u32> x;
+ BitField<8, 6, u32> y;
+ } pattern_offset;
+ BitField<0, 2, PatternSelect> pattern_select;
+ INSERT_PADDING_WORDS_NOINIT(0xC);
+ struct {
+ BitField<0, 3, MonochromePatternColorFormat> color_format;
+ BitField<0, 1, MonochromePatternFormat> format;
+ u32 color0;
+ u32 color1;
+ u32 pattern0;
+ u32 pattern1;
+ } monochrome_pattern;
+ struct {
+ std::array<u32, 0x40> X8R8G8B8;
+ std::array<u32, 0x20> R5G6B5;
+ std::array<u32, 0x20> X1R5G5B5;
+ std::array<u32, 0x10> Y8;
+ } color_pattern;
+ INSERT_PADDING_WORDS_NOINIT(0x10);
+ struct {
+ u32 prim_mode;
+ u32 prim_color_format;
+ u32 prim_color;
+ u32 line_tie_break_bits;
+ INSERT_PADDING_WORDS_NOINIT(0x14);
+ u32 prim_point_xy;
+ INSERT_PADDING_WORDS_NOINIT(0x7);
+ std::array<Point, 0x40> prim_point;
+ } render_solid;
+ struct {
+ u32 data_type;
+ u32 color_format;
+ u32 index_format;
+ u32 mono_format;
+ u32 wrap;
+ u32 color0;
+ u32 color1;
+ u32 mono_opacity;
+ INSERT_PADDING_WORDS_NOINIT(0x6);
+ u32 src_width;
+ u32 src_height;
+ u32 dx_du_frac;
+ u32 dx_du_int;
+ u32 dx_dv_frac;
+ u32 dy_dv_int;
+ u32 dst_x0_frac;
+ u32 dst_x0_int;
+ u32 dst_y0_frac;
+ u32 dst_y0_int;
+ u32 data;
+ } pixels_from_cpu;
+ INSERT_PADDING_WORDS_NOINIT(0x3);
+ u32 big_endian_control;
+ INSERT_PADDING_WORDS_NOINIT(0x3);
+ struct {
+ BitField<0, 3, u32> block_shape;
+ BitField<0, 5, u32> corral_size;
+ BitField<0, 1, u32> safe_overlap;
union {
- u32 raw;
BitField<0, 1, Origin> origin;
BitField<4, 1, Filter> filter;
- } blit_control;
-
- INSERT_UNION_PADDING_WORDS(0x8);
-
- u32 blit_dst_x;
- u32 blit_dst_y;
- u32 blit_dst_width;
- u32 blit_dst_height;
- u64 blit_du_dx;
- u64 blit_dv_dy;
- u64 blit_src_x;
- u64 blit_src_y;
-
- INSERT_UNION_PADDING_WORDS(0x21);
- };
- std::array<u32, NUM_REGS> reg_array;
+ } sample_mode;
+ INSERT_PADDING_WORDS_NOINIT(0x8);
+ s32 dst_x0;
+ s32 dst_y0;
+ s32 dst_width;
+ s32 dst_height;
+ s64 du_dx;
+ s64 dv_dy;
+ s64 src_x0;
+ s64 src_y0;
+ } pixels_from_memory;
};
+ std::array<u32, NUM_REGS> reg_array;
} regs{};
struct Config {
Operation operation;
Filter filter;
- Common::Rectangle<u32> src_rect;
- Common::Rectangle<u32> dst_rect;
+ s32 dst_x0;
+ s32 dst_y0;
+ s32 dst_x1;
+ s32 dst_y1;
+ s32 src_x0;
+ s32 src_y0;
+ s32 src_x1;
+ s32 src_y1;
};
private:
@@ -156,25 +303,49 @@ private:
/// Performs the copy from the source surface to the destination surface as configured in the
/// registers.
- void HandleSurfaceCopy();
+ void Blit();
};
#define ASSERT_REG_POSITION(field_name, position) \
- static_assert(offsetof(Fermi2D::Regs, field_name) == position * 4, \
+ static_assert(offsetof(Fermi2D::Regs, field_name) == position, \
"Field " #field_name " has invalid position")
-ASSERT_REG_POSITION(dst, 0x80);
-ASSERT_REG_POSITION(src, 0x8C);
-ASSERT_REG_POSITION(operation, 0xAB);
-ASSERT_REG_POSITION(blit_control, 0x223);
-ASSERT_REG_POSITION(blit_dst_x, 0x22c);
-ASSERT_REG_POSITION(blit_dst_y, 0x22d);
-ASSERT_REG_POSITION(blit_dst_width, 0x22e);
-ASSERT_REG_POSITION(blit_dst_height, 0x22f);
-ASSERT_REG_POSITION(blit_du_dx, 0x230);
-ASSERT_REG_POSITION(blit_dv_dy, 0x232);
-ASSERT_REG_POSITION(blit_src_x, 0x234);
-ASSERT_REG_POSITION(blit_src_y, 0x236);
+ASSERT_REG_POSITION(object, 0x0);
+ASSERT_REG_POSITION(no_operation, 0x100);
+ASSERT_REG_POSITION(notify, 0x104);
+ASSERT_REG_POSITION(wait_for_idle, 0x110);
+ASSERT_REG_POSITION(pm_trigger, 0x140);
+ASSERT_REG_POSITION(context_dma_notify, 0x180);
+ASSERT_REG_POSITION(dst_context_dma, 0x184);
+ASSERT_REG_POSITION(src_context_dma, 0x188);
+ASSERT_REG_POSITION(semaphore_context_dma, 0x18C);
+ASSERT_REG_POSITION(dst, 0x200);
+ASSERT_REG_POSITION(pixels_from_cpu_index_wrap, 0x228);
+ASSERT_REG_POSITION(kind2d_check_enable, 0x22C);
+ASSERT_REG_POSITION(src, 0x230);
+ASSERT_REG_POSITION(pixels_from_memory_sector_promotion, 0x258);
+ASSERT_REG_POSITION(num_tpcs, 0x260);
+ASSERT_REG_POSITION(render_enable_addr_upper, 0x264);
+ASSERT_REG_POSITION(render_enable_addr_lower, 0x268);
+ASSERT_REG_POSITION(clip_x0, 0x280);
+ASSERT_REG_POSITION(clip_y0, 0x284);
+ASSERT_REG_POSITION(clip_width, 0x288);
+ASSERT_REG_POSITION(clip_height, 0x28c);
+ASSERT_REG_POSITION(clip_enable, 0x290);
+ASSERT_REG_POSITION(color_key_format, 0x294);
+ASSERT_REG_POSITION(color_key, 0x298);
+ASSERT_REG_POSITION(rop, 0x2A0);
+ASSERT_REG_POSITION(beta1, 0x2A4);
+ASSERT_REG_POSITION(beta4, 0x2A8);
+ASSERT_REG_POSITION(operation, 0x2AC);
+ASSERT_REG_POSITION(pattern_offset, 0x2B0);
+ASSERT_REG_POSITION(pattern_select, 0x2B4);
+ASSERT_REG_POSITION(monochrome_pattern, 0x2E8);
+ASSERT_REG_POSITION(color_pattern, 0x300);
+ASSERT_REG_POSITION(render_solid, 0x580);
+ASSERT_REG_POSITION(pixels_from_cpu, 0x800);
+ASSERT_REG_POSITION(big_endian_control, 0x870);
+ASSERT_REG_POSITION(pixels_from_memory, 0x880);
#undef ASSERT_REG_POSITION
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 898370739..a9b75091e 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -21,8 +21,8 @@ KeplerCompute::KeplerCompute(Core::System& system_, MemoryManager& memory_manage
KeplerCompute::~KeplerCompute() = default;
-void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
- rasterizer = &rasterizer_;
+void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
+ rasterizer = rasterizer_;
}
void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
@@ -39,7 +39,6 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
upload_state.ProcessData(method_argument, is_last_call);
if (is_last_call) {
- system.GPU().Maxwell3D().OnMemoryWrite();
}
break;
}
@@ -58,24 +57,6 @@ void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amoun
}
}
-Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const {
- const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value();
- ASSERT(cbuf_mask[regs.tex_cb_index]);
-
- const auto& texinfo = launch_description.const_buffer_config[regs.tex_cb_index];
- ASSERT(texinfo.Address() != 0);
-
- const GPUVAddr address = texinfo.Address() + offset * sizeof(Texture::TextureHandle);
- ASSERT(address < texinfo.Address() + texinfo.size);
-
- const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(address)};
- return GetTextureInfo(tex_handle);
-}
-
-Texture::FullTextureInfo KeplerCompute::GetTextureInfo(Texture::TextureHandle tex_handle) const {
- return Texture::FullTextureInfo{GetTICEntry(tex_handle.tic_id), GetTSCEntry(tex_handle.tsc_id)};
-}
-
u32 KeplerCompute::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const {
ASSERT(stage == ShaderType::Compute);
const auto& buffer = launch_description.const_buffer_config[const_buffer];
@@ -98,9 +79,11 @@ SamplerDescriptor KeplerCompute::AccessBindlessSampler(ShaderType stage, u64 con
SamplerDescriptor KeplerCompute::AccessSampler(u32 handle) const {
const Texture::TextureHandle tex_handle{handle};
- const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle);
- SamplerDescriptor result = SamplerDescriptor::FromTIC(tex_info.tic);
- result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value());
+ const Texture::TICEntry tic = GetTICEntry(tex_handle.tic_id);
+ const Texture::TSCEntry tsc = GetTSCEntry(tex_handle.tsc_id);
+
+ SamplerDescriptor result = SamplerDescriptor::FromTIC(tic);
+ result.is_shadow.Assign(tsc.depth_compare_enabled.Value());
return result;
}
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 7f2500aab..7c40cba38 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -46,7 +46,7 @@ public:
~KeplerCompute();
/// Binds a rasterizer to this engine.
- void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
+ void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
static constexpr std::size_t NumConstBuffers = 8;
@@ -55,7 +55,7 @@ public:
union {
struct {
- INSERT_UNION_PADDING_WORDS(0x60);
+ INSERT_PADDING_WORDS_NOINIT(0x60);
Upload::Registers upload;
@@ -67,7 +67,7 @@ public:
u32 data_upload;
- INSERT_UNION_PADDING_WORDS(0x3F);
+ INSERT_PADDING_WORDS_NOINIT(0x3F);
struct {
u32 address;
@@ -76,11 +76,11 @@ public:
}
} launch_desc_loc;
- INSERT_UNION_PADDING_WORDS(0x1);
+ INSERT_PADDING_WORDS_NOINIT(0x1);
u32 launch;
- INSERT_UNION_PADDING_WORDS(0x4A7);
+ INSERT_PADDING_WORDS_NOINIT(0x4A7);
struct {
u32 address_high;
@@ -92,7 +92,7 @@ public:
}
} tsc;
- INSERT_UNION_PADDING_WORDS(0x3);
+ INSERT_PADDING_WORDS_NOINIT(0x3);
struct {
u32 address_high;
@@ -104,7 +104,7 @@ public:
}
} tic;
- INSERT_UNION_PADDING_WORDS(0x22);
+ INSERT_PADDING_WORDS_NOINIT(0x22);
struct {
u32 address_high;
@@ -115,11 +115,11 @@ public:
}
} code_loc;
- INSERT_UNION_PADDING_WORDS(0x3FE);
+ INSERT_PADDING_WORDS_NOINIT(0x3FE);
u32 tex_cb_index;
- INSERT_UNION_PADDING_WORDS(0x374);
+ INSERT_PADDING_WORDS_NOINIT(0x374);
};
std::array<u32, NUM_REGS> reg_array;
};
@@ -209,11 +209,6 @@ public:
void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) override;
- Texture::FullTextureInfo GetTexture(std::size_t offset) const;
-
- /// Given a texture handle, returns the TSC and TIC entries.
- Texture::FullTextureInfo GetTextureInfo(Texture::TextureHandle tex_handle) const;
-
u32 AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const override;
SamplerDescriptor AccessBoundSampler(ShaderType stage, u64 offset) const override;
diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp
index dc71b2eec..560551157 100644
--- a/src/video_core/engines/kepler_memory.cpp
+++ b/src/video_core/engines/kepler_memory.cpp
@@ -14,8 +14,8 @@
namespace Tegra::Engines {
-KeplerMemory::KeplerMemory(Core::System& system, MemoryManager& memory_manager)
- : system{system}, upload_state{memory_manager, regs.upload} {}
+KeplerMemory::KeplerMemory(Core::System& system_, MemoryManager& memory_manager)
+ : system{system_}, upload_state{memory_manager, regs.upload} {}
KeplerMemory::~KeplerMemory() = default;
@@ -33,7 +33,6 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call
case KEPLERMEMORY_REG_INDEX(data): {
upload_state.ProcessData(method_argument, is_last_call);
if (is_last_call) {
- system.GPU().Maxwell3D().OnMemoryWrite();
}
break;
}
diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h
index 5b7f71a00..19808a5c6 100644
--- a/src/video_core/engines/kepler_memory.h
+++ b/src/video_core/engines/kepler_memory.h
@@ -35,7 +35,7 @@ namespace Tegra::Engines {
class KeplerMemory final : public EngineInterface {
public:
- KeplerMemory(Core::System& system, MemoryManager& memory_manager);
+ explicit KeplerMemory(Core::System& system_, MemoryManager& memory_manager);
~KeplerMemory();
/// Write the value to the register identified by method.
@@ -50,7 +50,7 @@ public:
union {
struct {
- INSERT_UNION_PADDING_WORDS(0x60);
+ INSERT_PADDING_WORDS_NOINIT(0x60);
Upload::Registers upload;
@@ -62,7 +62,7 @@ public:
u32 data;
- INSERT_UNION_PADDING_WORDS(0x11);
+ INSERT_PADDING_WORDS_NOINIT(0x11);
};
std::array<u32, NUM_REGS> reg_array;
};
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 33854445f..75517a4f7 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <cinttypes>
#include <cstring>
#include <optional>
#include "common/assert.h"
@@ -31,8 +30,8 @@ Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
Maxwell3D::~Maxwell3D() = default;
-void Maxwell3D::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
- rasterizer = &rasterizer_;
+void Maxwell3D::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
+ rasterizer = rasterizer_;
}
void Maxwell3D::InitializeRegisterDefaults() {
@@ -124,6 +123,115 @@ void Maxwell3D::InitializeRegisterDefaults() {
mme_inline[MAXWELL3D_REG_INDEX(index_array.count)] = true;
}
+void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call) {
+ if (executing_macro == 0) {
+ // A macro call must begin by writing the macro method's register, not its argument.
+ ASSERT_MSG((method % 2) == 0,
+ "Can't start macro execution by writing to the ARGS register");
+ executing_macro = method;
+ }
+
+ macro_params.insert(macro_params.end(), base_start, base_start + amount);
+
+ // Call the macro when there are no more parameters in the command buffer
+ if (is_last_call) {
+ CallMacroMethod(executing_macro, macro_params);
+ macro_params.clear();
+ }
+}
+
+u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) {
+ // Keep track of the register value in shadow_state when requested.
+ const auto control = shadow_state.shadow_ram_control;
+ if (control == Regs::ShadowRamControl::Track ||
+ control == Regs::ShadowRamControl::TrackWithFilter) {
+ shadow_state.reg_array[method] = argument;
+ return argument;
+ }
+ if (control == Regs::ShadowRamControl::Replay) {
+ return shadow_state.reg_array[method];
+ }
+ return argument;
+}
+
+void Maxwell3D::ProcessDirtyRegisters(u32 method, u32 argument) {
+ if (regs.reg_array[method] == argument) {
+ return;
+ }
+ regs.reg_array[method] = argument;
+
+ for (const auto& table : dirty.tables) {
+ dirty.flags[table[method]] = true;
+ }
+}
+
+void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument,
+ bool is_last_call) {
+ switch (method) {
+ case MAXWELL3D_REG_INDEX(wait_for_idle):
+ return rasterizer->WaitForIdle();
+ case MAXWELL3D_REG_INDEX(shadow_ram_control):
+ shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument);
+ return;
+ case MAXWELL3D_REG_INDEX(macros.data):
+ return macro_engine->AddCode(regs.macros.upload_address, argument);
+ case MAXWELL3D_REG_INDEX(macros.bind):
+ return ProcessMacroBind(argument);
+ case MAXWELL3D_REG_INDEX(firmware[4]):
+ return ProcessFirmwareCall4();
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 1:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 2:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 3:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 4:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 5:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 6:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 7:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 8:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 9:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 10:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 11:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 12:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 13:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 14:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15:
+ return StartCBData(method);
+ case MAXWELL3D_REG_INDEX(cb_bind[0]):
+ return ProcessCBBind(0);
+ case MAXWELL3D_REG_INDEX(cb_bind[1]):
+ return ProcessCBBind(1);
+ case MAXWELL3D_REG_INDEX(cb_bind[2]):
+ return ProcessCBBind(2);
+ case MAXWELL3D_REG_INDEX(cb_bind[3]):
+ return ProcessCBBind(3);
+ case MAXWELL3D_REG_INDEX(cb_bind[4]):
+ return ProcessCBBind(4);
+ case MAXWELL3D_REG_INDEX(draw.vertex_end_gl):
+ return DrawArrays();
+ case MAXWELL3D_REG_INDEX(clear_buffers):
+ return ProcessClearBuffers();
+ case MAXWELL3D_REG_INDEX(query.query_get):
+ return ProcessQueryGet();
+ case MAXWELL3D_REG_INDEX(condition.mode):
+ return ProcessQueryCondition();
+ case MAXWELL3D_REG_INDEX(counter_reset):
+ return ProcessCounterReset();
+ case MAXWELL3D_REG_INDEX(sync_info):
+ return ProcessSyncPoint();
+ case MAXWELL3D_REG_INDEX(exec_upload):
+ return upload_state.ProcessExec(regs.exec_upload.linear != 0);
+ case MAXWELL3D_REG_INDEX(data_upload):
+ upload_state.ProcessData(argument, is_last_call);
+ if (is_last_call) {
+ }
+ return;
+ case MAXWELL3D_REG_INDEX(fragment_barrier):
+ return rasterizer->FragmentBarrier();
+ case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
+ return rasterizer->TiledCacheBarrier();
+ }
+}
+
void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters) {
// Reset the current macro.
executing_macro = 0;
@@ -157,142 +265,16 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
// Methods after 0xE00 are special, they're actually triggers for some microcode that was
// uploaded to the GPU during initialization.
if (method >= MacroRegistersStart) {
- // We're trying to execute a macro
- if (executing_macro == 0) {
- // A macro call must begin by writing the macro method's register, not its argument.
- ASSERT_MSG((method % 2) == 0,
- "Can't start macro execution by writing to the ARGS register");
- executing_macro = method;
- }
-
- macro_params.push_back(method_argument);
-
- // Call the macro when there are no more parameters in the command buffer
- if (is_last_call) {
- CallMacroMethod(executing_macro, macro_params);
- macro_params.clear();
- }
+ ProcessMacro(method, &method_argument, 1, is_last_call);
return;
}
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Maxwell3D register, increase the size of the Regs structure");
- u32 arg = method_argument;
- // Keep track of the register value in shadow_state when requested.
- if (shadow_state.shadow_ram_control == Regs::ShadowRamControl::Track ||
- shadow_state.shadow_ram_control == Regs::ShadowRamControl::TrackWithFilter) {
- shadow_state.reg_array[method] = arg;
- } else if (shadow_state.shadow_ram_control == Regs::ShadowRamControl::Replay) {
- arg = shadow_state.reg_array[method];
- }
-
- if (regs.reg_array[method] != arg) {
- regs.reg_array[method] = arg;
-
- for (const auto& table : dirty.tables) {
- dirty.flags[table[method]] = true;
- }
- }
-
- switch (method) {
- case MAXWELL3D_REG_INDEX(wait_for_idle): {
- rasterizer->WaitForIdle();
- break;
- }
- case MAXWELL3D_REG_INDEX(shadow_ram_control): {
- shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(method_argument);
- break;
- }
- case MAXWELL3D_REG_INDEX(macros.data): {
- macro_engine->AddCode(regs.macros.upload_address, arg);
- break;
- }
- case MAXWELL3D_REG_INDEX(macros.bind): {
- ProcessMacroBind(arg);
- break;
- }
- case MAXWELL3D_REG_INDEX(firmware[4]): {
- ProcessFirmwareCall4();
- break;
- }
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): {
- StartCBData(method);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[0]): {
- ProcessCBBind(0);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[1]): {
- ProcessCBBind(1);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[2]): {
- ProcessCBBind(2);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[3]): {
- ProcessCBBind(3);
- break;
- }
- case MAXWELL3D_REG_INDEX(cb_bind[4]): {
- ProcessCBBind(4);
- break;
- }
- case MAXWELL3D_REG_INDEX(draw.vertex_end_gl): {
- DrawArrays();
- break;
- }
- case MAXWELL3D_REG_INDEX(clear_buffers): {
- ProcessClearBuffers();
- break;
- }
- case MAXWELL3D_REG_INDEX(query.query_get): {
- ProcessQueryGet();
- break;
- }
- case MAXWELL3D_REG_INDEX(condition.mode): {
- ProcessQueryCondition();
- break;
- }
- case MAXWELL3D_REG_INDEX(counter_reset): {
- ProcessCounterReset();
- break;
- }
- case MAXWELL3D_REG_INDEX(sync_info): {
- ProcessSyncPoint();
- break;
- }
- case MAXWELL3D_REG_INDEX(exec_upload): {
- upload_state.ProcessExec(regs.exec_upload.linear != 0);
- break;
- }
- case MAXWELL3D_REG_INDEX(data_upload): {
- upload_state.ProcessData(arg, is_last_call);
- if (is_last_call) {
- OnMemoryWrite();
- }
- break;
- }
- default:
- break;
- }
+ const u32 argument = ProcessShadowRam(method, method_argument);
+ ProcessDirtyRegisters(method, argument);
+ ProcessMethodCall(method, argument, method_argument, is_last_call);
}
void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
@@ -300,50 +282,33 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
// Methods after 0xE00 are special, they're actually triggers for some microcode that was
// uploaded to the GPU during initialization.
if (method >= MacroRegistersStart) {
- // We're trying to execute a macro
- if (executing_macro == 0) {
- // A macro call must begin by writing the macro method's register, not its argument.
- ASSERT_MSG((method % 2) == 0,
- "Can't start macro execution by writing to the ARGS register");
- executing_macro = method;
- }
-
- for (std::size_t i = 0; i < amount; i++) {
- macro_params.push_back(base_start[i]);
- }
-
- // Call the macro when there are no more parameters in the command buffer
- if (amount == methods_pending) {
- CallMacroMethod(executing_macro, macro_params);
- macro_params.clear();
- }
+ ProcessMacro(method, base_start, amount, amount == methods_pending);
return;
}
switch (method) {
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[3]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[4]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[5]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[6]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[7]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[8]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[9]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[10]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[11]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[12]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]):
- case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): {
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data):
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 1:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 2:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 3:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 4:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 5:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 6:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 7:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 8:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 9:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 10:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 11:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 12:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 13:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 14:
+ case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15:
ProcessCBMultiData(method, base_start, amount);
break;
- }
- default: {
+ default:
for (std::size_t i = 0; i < amount; i++) {
CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
}
- }
+ break;
}
}
@@ -396,7 +361,7 @@ void Maxwell3D::CallMethodFromMME(u32 method, u32 method_argument) {
}
void Maxwell3D::FlushMMEInlineDraw() {
- LOG_TRACE(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()),
+ LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(),
regs.vertex_buffer.count);
ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?");
ASSERT(mme_draw.instance_count == mme_draw.gl_end_count);
@@ -541,8 +506,7 @@ void Maxwell3D::ProcessCounterReset() {
rasterizer->ResetCounter(QueryType::SamplesPassed);
break;
default:
- LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}",
- static_cast<int>(regs.counter_reset));
+ LOG_DEBUG(Render_OpenGL, "Unimplemented counter reset={}", regs.counter_reset);
break;
}
}
@@ -557,7 +521,7 @@ void Maxwell3D::ProcessSyncPoint() {
}
void Maxwell3D::DrawArrays() {
- LOG_TRACE(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()),
+ LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(),
regs.vertex_buffer.count);
ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?");
@@ -595,27 +559,28 @@ std::optional<u64> Maxwell3D::GetQueryResult() {
return 0;
case Regs::QuerySelect::SamplesPassed:
// Deferred.
- rasterizer->Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed,
+ rasterizer->Query(regs.query.QueryAddress(), QueryType::SamplesPassed,
system.GPU().GetTicks());
- return {};
+ return std::nullopt;
default:
LOG_DEBUG(HW_GPU, "Unimplemented query select type {}",
- static_cast<u32>(regs.query.query_get.select.Value()));
+ regs.query.query_get.select.Value());
return 1;
}
}
-void Maxwell3D::ProcessCBBind(std::size_t stage_index) {
+void Maxwell3D::ProcessCBBind(size_t stage_index) {
// Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage.
- auto& shader = state.shader_stages[stage_index];
- auto& bind_data = regs.cb_bind[stage_index];
-
- ASSERT(bind_data.index < Regs::MaxConstBuffers);
- auto& buffer = shader.const_buffers[bind_data.index];
-
+ const auto& bind_data = regs.cb_bind[stage_index];
+ auto& buffer = state.shader_stages[stage_index].const_buffers[bind_data.index];
buffer.enabled = bind_data.valid.Value() != 0;
buffer.address = regs.const_buffer.BufferAddress();
buffer.size = regs.const_buffer.cb_size;
+
+ const bool is_enabled = bind_data.valid.Value() != 0;
+ const GPUVAddr gpu_addr = is_enabled ? regs.const_buffer.BufferAddress() : 0;
+ const u32 size = is_enabled ? regs.const_buffer.cb_size : 0;
+ rasterizer->BindGraphicsUniformBuffer(stage_index, bind_data.index, gpu_addr, size);
}
void Maxwell3D::ProcessCBData(u32 value) {
@@ -627,7 +592,7 @@ void Maxwell3D::ProcessCBData(u32 value) {
}
void Maxwell3D::StartCBData(u32 method) {
- constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]);
+ constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data);
cb_data_state.start_pos = regs.const_buffer.cb_pos;
cb_data_state.id = method - first_cb_data;
cb_data_state.current = method;
@@ -640,7 +605,7 @@ void Maxwell3D::ProcessCBMultiData(u32 method, const u32* start_base, u32 amount
if (cb_data_state.current != null_cb_data) {
FinishCBData();
}
- constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]);
+ constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data);
cb_data_state.start_pos = regs.const_buffer.cb_pos;
cb_data_state.id = method - first_cb_data;
cb_data_state.current = method;
@@ -670,14 +635,13 @@ void Maxwell3D::FinishCBData() {
const u32 id = cb_data_state.id;
memory_manager.WriteBlock(address, cb_data_state.buffer[id].data(), size);
- OnMemoryWrite();
cb_data_state.id = null_cb_data;
cb_data_state.current = null_cb_data;
}
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
- const GPUVAddr tic_address_gpu{regs.tic.TICAddress() + tic_index * sizeof(Texture::TICEntry)};
+ const GPUVAddr tic_address_gpu{regs.tic.Address() + tic_index * sizeof(Texture::TICEntry)};
Texture::TICEntry tic_entry;
memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
@@ -686,43 +650,19 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
}
Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
- const GPUVAddr tsc_address_gpu{regs.tsc.TSCAddress() + tsc_index * sizeof(Texture::TSCEntry)};
+ const GPUVAddr tsc_address_gpu{regs.tsc.Address() + tsc_index * sizeof(Texture::TSCEntry)};
Texture::TSCEntry tsc_entry;
memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
return tsc_entry;
}
-Texture::FullTextureInfo Maxwell3D::GetTextureInfo(Texture::TextureHandle tex_handle) const {
- return Texture::FullTextureInfo{GetTICEntry(tex_handle.tic_id), GetTSCEntry(tex_handle.tsc_id)};
-}
-
-Texture::FullTextureInfo Maxwell3D::GetStageTexture(ShaderType stage, std::size_t offset) const {
- const auto stage_index = static_cast<std::size_t>(stage);
- const auto& shader = state.shader_stages[stage_index];
- const auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index];
- ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
-
- const GPUVAddr tex_info_address =
- tex_info_buffer.address + offset * sizeof(Texture::TextureHandle);
-
- ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
-
- const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
-
- return GetTextureInfo(tex_handle);
-}
-
u32 Maxwell3D::GetRegisterValue(u32 method) const {
ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Maxwell3D register");
return regs.reg_array[method];
}
void Maxwell3D::ProcessClearBuffers() {
- ASSERT(regs.clear_buffers.R == regs.clear_buffers.G &&
- regs.clear_buffers.R == regs.clear_buffers.B &&
- regs.clear_buffers.R == regs.clear_buffers.A);
-
rasterizer->Clear();
}
@@ -730,9 +670,7 @@ u32 Maxwell3D::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offse
ASSERT(stage != ShaderType::Compute);
const auto& shader_stage = state.shader_stages[static_cast<std::size_t>(stage)];
const auto& buffer = shader_stage.const_buffers[const_buffer];
- u32 result;
- std::memcpy(&result, memory_manager.GetPointer(buffer.address + offset), sizeof(u32));
- return result;
+ return memory_manager.Read<u32>(buffer.address + offset);
}
SamplerDescriptor Maxwell3D::AccessBoundSampler(ShaderType stage, u64 offset) const {
@@ -750,9 +688,11 @@ SamplerDescriptor Maxwell3D::AccessBindlessSampler(ShaderType stage, u64 const_b
SamplerDescriptor Maxwell3D::AccessSampler(u32 handle) const {
const Texture::TextureHandle tex_handle{handle};
- const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle);
- SamplerDescriptor result = SamplerDescriptor::FromTIC(tex_info.tic);
- result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value());
+ const Texture::TICEntry tic = GetTICEntry(tex_handle.tic_id);
+ const Texture::TSCEntry tsc = GetTSCEntry(tex_handle.tsc_id);
+
+ SamplerDescriptor result = SamplerDescriptor::FromTIC(tic);
+ result.is_shadow.Assign(tsc.depth_compare_enabled.Value());
return result;
}
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index bc289c55d..ffed42a29 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -55,7 +55,7 @@ public:
~Maxwell3D();
/// Binds a rasterizer to this engine.
- void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
+ void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
/// Register structure of the Maxwell3D engine.
/// TODO(Subv): This structure will need to be made bigger as more registers are discovered.
@@ -438,16 +438,6 @@ public:
DecrWrapOGL = 0x8508,
};
- enum class MemoryLayout : u32 {
- Linear = 0,
- BlockLinear = 1,
- };
-
- enum class InvMemoryLayout : u32 {
- BlockLinear = 0,
- Linear = 1,
- };
-
enum class CounterReset : u32 {
SampleCnt = 0x01,
Unk02 = 0x02,
@@ -546,7 +536,7 @@ public:
Equation equation_a;
Factor factor_source_a;
Factor factor_dest_a;
- INSERT_UNION_PADDING_WORDS(1);
+ INSERT_PADDING_WORDS_NOINIT(1);
};
enum class TessellationPrimitive : u32 {
@@ -589,26 +579,36 @@ public:
NegativeW = 7,
};
+ enum class SamplerIndex : u32 {
+ Independently = 0,
+ ViaHeaderIndex = 1,
+ };
+
+ struct TileMode {
+ union {
+ BitField<0, 4, u32> block_width;
+ BitField<4, 4, u32> block_height;
+ BitField<8, 4, u32> block_depth;
+ BitField<12, 1, u32> is_pitch_linear;
+ BitField<16, 1, u32> is_3d;
+ };
+ };
+ static_assert(sizeof(TileMode) == 4);
+
struct RenderTargetConfig {
u32 address_high;
u32 address_low;
u32 width;
u32 height;
Tegra::RenderTargetFormat format;
+ TileMode tile_mode;
union {
- BitField<0, 3, u32> block_width;
- BitField<4, 3, u32> block_height;
- BitField<8, 3, u32> block_depth;
- BitField<12, 1, InvMemoryLayout> type;
- BitField<16, 1, u32> is_3d;
- } memory_layout;
- union {
- BitField<0, 16, u32> layers;
+ BitField<0, 16, u32> depth;
BitField<16, 1, u32> volume;
};
u32 layer_stride;
u32 base_layer;
- INSERT_UNION_PADDING_WORDS(7);
+ INSERT_PADDING_WORDS_NOINIT(7);
GPUVAddr Address() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
@@ -640,7 +640,7 @@ public:
BitField<8, 3, ViewportSwizzle> z;
BitField<12, 3, ViewportSwizzle> w;
} swizzle;
- INSERT_UNION_PADDING_WORDS(1);
+ INSERT_PADDING_WORDS_NOINIT(1);
Common::Rectangle<f32> GetRect() const {
return {
@@ -700,7 +700,7 @@ public:
u32 address_low;
s32 buffer_size;
s32 buffer_offset;
- INSERT_UNION_PADDING_WORDS(3);
+ INSERT_PADDING_WORDS_NOINIT(3);
GPUVAddr Address() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
@@ -713,7 +713,7 @@ public:
u32 stream;
u32 varying_count;
u32 stride;
- INSERT_UNION_PADDING_WORDS(1);
+ INSERT_PADDING_WORDS_NOINIT(1);
};
static_assert(sizeof(TransformFeedbackLayout) == 16);
@@ -731,7 +731,7 @@ public:
union {
struct {
- INSERT_UNION_PADDING_WORDS(0x44);
+ INSERT_PADDING_WORDS_NOINIT(0x44);
u32 wait_for_idle;
@@ -744,7 +744,7 @@ public:
ShadowRamControl shadow_ram_control;
- INSERT_UNION_PADDING_WORDS(0x16);
+ INSERT_PADDING_WORDS_NOINIT(0x16);
Upload::Registers upload;
struct {
@@ -755,7 +755,11 @@ public:
u32 data_upload;
- INSERT_UNION_PADDING_WORDS(0x44);
+ INSERT_PADDING_WORDS_NOINIT(0x16);
+
+ u32 force_early_fragment_tests;
+
+ INSERT_PADDING_WORDS_NOINIT(0x2D);
struct {
union {
@@ -765,7 +769,7 @@ public:
};
} sync_info;
- INSERT_UNION_PADDING_WORDS(0x15);
+ INSERT_PADDING_WORDS_NOINIT(0x15);
union {
BitField<0, 2, TessellationPrimitive> prim;
@@ -777,21 +781,21 @@ public:
std::array<f32, 4> tess_level_outer;
std::array<f32, 2> tess_level_inner;
- INSERT_UNION_PADDING_WORDS(0x10);
+ INSERT_PADDING_WORDS_NOINIT(0x10);
u32 rasterize_enable;
std::array<TransformFeedbackBinding, NumTransformFeedbackBuffers> tfb_bindings;
- INSERT_UNION_PADDING_WORDS(0xC0);
+ INSERT_PADDING_WORDS_NOINIT(0xC0);
std::array<TransformFeedbackLayout, NumTransformFeedbackBuffers> tfb_layouts;
- INSERT_UNION_PADDING_WORDS(0x1);
+ INSERT_PADDING_WORDS_NOINIT(0x1);
u32 tfb_enabled;
- INSERT_UNION_PADDING_WORDS(0x2E);
+ INSERT_PADDING_WORDS_NOINIT(0x2E);
std::array<RenderTargetConfig, NumRenderTargets> rt;
@@ -799,7 +803,7 @@ public:
std::array<ViewPort, NumViewports> viewports;
- INSERT_UNION_PADDING_WORDS(0x1D);
+ INSERT_PADDING_WORDS_NOINIT(0x1D);
struct {
u32 first;
@@ -811,16 +815,16 @@ public:
float clear_color[4];
float clear_depth;
- INSERT_UNION_PADDING_WORDS(0x3);
+ INSERT_PADDING_WORDS_NOINIT(0x3);
s32 clear_stencil;
- INSERT_UNION_PADDING_WORDS(0x2);
+ INSERT_PADDING_WORDS_NOINIT(0x2);
PolygonMode polygon_mode_front;
PolygonMode polygon_mode_back;
- INSERT_UNION_PADDING_WORDS(0x3);
+ INSERT_PADDING_WORDS_NOINIT(0x3);
u32 polygon_offset_point_enable;
u32 polygon_offset_line_enable;
@@ -828,46 +832,53 @@ public:
u32 patch_vertices;
- INSERT_UNION_PADDING_WORDS(0xC);
+ INSERT_PADDING_WORDS_NOINIT(0x4);
+
+ u32 fragment_barrier;
+
+ INSERT_PADDING_WORDS_NOINIT(0x7);
std::array<ScissorTest, NumViewports> scissor_test;
- INSERT_UNION_PADDING_WORDS(0x15);
+ INSERT_PADDING_WORDS_NOINIT(0x15);
s32 stencil_back_func_ref;
u32 stencil_back_mask;
u32 stencil_back_func_mask;
- INSERT_UNION_PADDING_WORDS(0xC);
+ INSERT_PADDING_WORDS_NOINIT(0x5);
+
+ u32 invalidate_texture_data_cache;
+
+ INSERT_PADDING_WORDS_NOINIT(0x1);
+
+ u32 tiled_cache_barrier;
+
+ INSERT_PADDING_WORDS_NOINIT(0x4);
u32 color_mask_common;
- INSERT_UNION_PADDING_WORDS(0x2);
+ INSERT_PADDING_WORDS_NOINIT(0x2);
f32 depth_bounds[2];
- INSERT_UNION_PADDING_WORDS(0x2);
+ INSERT_PADDING_WORDS_NOINIT(0x2);
u32 rt_separate_frag_data;
- INSERT_UNION_PADDING_WORDS(0x1);
+ INSERT_PADDING_WORDS_NOINIT(0x1);
u32 multisample_raster_enable;
u32 multisample_raster_samples;
std::array<u32, 4> multisample_sample_mask;
- INSERT_UNION_PADDING_WORDS(0x5);
+ INSERT_PADDING_WORDS_NOINIT(0x5);
struct {
u32 address_high;
u32 address_low;
Tegra::DepthFormat format;
- union {
- BitField<0, 4, u32> block_width;
- BitField<4, 4, u32> block_height;
- BitField<8, 4, u32> block_depth;
- BitField<20, 1, InvMemoryLayout> type;
- } memory_layout;
+ TileMode tile_mode;
u32 layer_stride;
GPUVAddr Address() const {
@@ -876,7 +887,18 @@ public:
}
} zeta;
- INSERT_UNION_PADDING_WORDS(0x41);
+ struct {
+ union {
+ BitField<0, 16, u32> x;
+ BitField<16, 16, u32> width;
+ };
+ union {
+ BitField<0, 16, u32> y;
+ BitField<16, 16, u32> height;
+ };
+ } render_area;
+
+ INSERT_PADDING_WORDS_NOINIT(0x3F);
union {
BitField<0, 4, u32> stencil;
@@ -885,24 +907,24 @@ public:
BitField<12, 4, u32> viewport;
} clear_flags;
- INSERT_UNION_PADDING_WORDS(0x10);
+ INSERT_PADDING_WORDS_NOINIT(0x10);
u32 fill_rectangle;
- INSERT_UNION_PADDING_WORDS(0x8);
+ INSERT_PADDING_WORDS_NOINIT(0x8);
std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format;
std::array<MsaaSampleLocation, 4> multisample_sample_locations;
- INSERT_UNION_PADDING_WORDS(0x2);
+ INSERT_PADDING_WORDS_NOINIT(0x2);
union {
BitField<0, 1, u32> enable;
BitField<4, 3, u32> target;
} multisample_coverage_to_color;
- INSERT_UNION_PADDING_WORDS(0x8);
+ INSERT_PADDING_WORDS_NOINIT(0x8);
struct {
union {
@@ -917,7 +939,7 @@ public:
BitField<25, 3, u32> map_7;
};
- u32 GetMap(std::size_t index) const {
+ u32 Map(std::size_t index) const {
const std::array<u32, NumRenderTargets> maps{map_0, map_1, map_2, map_3,
map_4, map_5, map_6, map_7};
ASSERT(index < maps.size());
@@ -925,20 +947,22 @@ public:
}
} rt_control;
- INSERT_UNION_PADDING_WORDS(0x2);
+ INSERT_PADDING_WORDS_NOINIT(0x2);
u32 zeta_width;
u32 zeta_height;
union {
- BitField<0, 16, u32> zeta_layers;
+ BitField<0, 16, u32> zeta_depth;
BitField<16, 1, u32> zeta_volume;
};
- INSERT_UNION_PADDING_WORDS(0x26);
+ SamplerIndex sampler_index;
+
+ INSERT_PADDING_WORDS_NOINIT(0x25);
u32 depth_test_enable;
- INSERT_UNION_PADDING_WORDS(0x5);
+ INSERT_PADDING_WORDS_NOINIT(0x5);
u32 independent_blend_enable;
@@ -946,7 +970,7 @@ public:
u32 alpha_test_enabled;
- INSERT_UNION_PADDING_WORDS(0x6);
+ INSERT_PADDING_WORDS_NOINIT(0x6);
u32 d3d_cull_mode;
@@ -960,7 +984,8 @@ public:
float b;
float a;
} blend_color;
- INSERT_UNION_PADDING_WORDS(0x4);
+
+ INSERT_PADDING_WORDS_NOINIT(0x4);
struct {
u32 separate_alpha;
@@ -969,7 +994,7 @@ public:
Blend::Factor factor_dest_rgb;
Blend::Equation equation_a;
Blend::Factor factor_source_a;
- INSERT_UNION_PADDING_WORDS(1);
+ INSERT_PADDING_WORDS_NOINIT(1);
Blend::Factor factor_dest_a;
u32 enable_common;
@@ -985,7 +1010,7 @@ public:
u32 stencil_front_func_mask;
u32 stencil_front_mask;
- INSERT_UNION_PADDING_WORDS(0x2);
+ INSERT_PADDING_WORDS_NOINIT(0x2);
u32 frag_color_clamp;
@@ -997,12 +1022,17 @@ public:
float line_width_smooth;
float line_width_aliased;
- INSERT_UNION_PADDING_WORDS(0x1F);
+ INSERT_PADDING_WORDS_NOINIT(0x1B);
+
+ u32 invalidate_sampler_cache_no_wfi;
+ u32 invalidate_texture_header_cache_no_wfi;
+
+ INSERT_PADDING_WORDS_NOINIT(0x2);
u32 vb_element_base;
u32 vb_base_instance;
- INSERT_UNION_PADDING_WORDS(0x35);
+ INSERT_PADDING_WORDS_NOINIT(0x35);
u32 clip_distance_enabled;
@@ -1010,11 +1040,11 @@ public:
float point_size;
- INSERT_UNION_PADDING_WORDS(0x1);
+ INSERT_PADDING_WORDS_NOINIT(0x1);
u32 point_sprite_enable;
- INSERT_UNION_PADDING_WORDS(0x3);
+ INSERT_PADDING_WORDS_NOINIT(0x3);
CounterReset counter_reset;
@@ -1027,7 +1057,7 @@ public:
BitField<4, 1, u32> alpha_to_one;
} multisample_control;
- INSERT_UNION_PADDING_WORDS(0x4);
+ INSERT_PADDING_WORDS_NOINIT(0x4);
struct {
u32 address_high;
@@ -1041,34 +1071,34 @@ public:
} condition;
struct {
- u32 tsc_address_high;
- u32 tsc_address_low;
- u32 tsc_limit;
+ u32 address_high;
+ u32 address_low;
+ u32 limit;
- GPUVAddr TSCAddress() const {
- return static_cast<GPUVAddr>(
- (static_cast<GPUVAddr>(tsc_address_high) << 32) | tsc_address_low);
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
}
} tsc;
- INSERT_UNION_PADDING_WORDS(0x1);
+ INSERT_PADDING_WORDS_NOINIT(0x1);
float polygon_offset_factor;
u32 line_smooth_enable;
struct {
- u32 tic_address_high;
- u32 tic_address_low;
- u32 tic_limit;
+ u32 address_high;
+ u32 address_low;
+ u32 limit;
- GPUVAddr TICAddress() const {
- return static_cast<GPUVAddr>(
- (static_cast<GPUVAddr>(tic_address_high) << 32) | tic_address_low);
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
}
} tic;
- INSERT_UNION_PADDING_WORDS(0x5);
+ INSERT_PADDING_WORDS_NOINIT(0x5);
u32 stencil_two_side_enable;
StencilOp stencil_back_op_fail;
@@ -1076,17 +1106,17 @@ public:
StencilOp stencil_back_op_zpass;
ComparisonOp stencil_back_func_func;
- INSERT_UNION_PADDING_WORDS(0x4);
+ INSERT_PADDING_WORDS_NOINIT(0x4);
u32 framebuffer_srgb;
float polygon_offset_units;
- INSERT_UNION_PADDING_WORDS(0x4);
+ INSERT_PADDING_WORDS_NOINIT(0x4);
Tegra::Texture::MsaaMode multisample_mode;
- INSERT_UNION_PADDING_WORDS(0xC);
+ INSERT_PADDING_WORDS_NOINIT(0xC);
union {
BitField<2, 1, u32> coord_origin;
@@ -1102,7 +1132,7 @@ public:
(static_cast<GPUVAddr>(code_address_high) << 32) | code_address_low);
}
} code_address;
- INSERT_UNION_PADDING_WORDS(1);
+ INSERT_PADDING_WORDS_NOINIT(1);
struct {
u32 vertex_end_gl;
@@ -1114,14 +1144,14 @@ public:
};
} draw;
- INSERT_UNION_PADDING_WORDS(0xA);
+ INSERT_PADDING_WORDS_NOINIT(0xA);
struct {
u32 enabled;
u32 index;
} primitive_restart;
- INSERT_UNION_PADDING_WORDS(0x5F);
+ INSERT_PADDING_WORDS_NOINIT(0x5F);
struct {
u32 start_addr_high;
@@ -1162,9 +1192,9 @@ public:
}
} index_array;
- INSERT_UNION_PADDING_WORDS(0x7);
+ INSERT_PADDING_WORDS_NOINIT(0x7);
- INSERT_UNION_PADDING_WORDS(0x1F);
+ INSERT_PADDING_WORDS_NOINIT(0x1F);
float polygon_offset_clamp;
@@ -1178,14 +1208,14 @@ public:
}
} instanced_arrays;
- INSERT_UNION_PADDING_WORDS(0x4);
+ INSERT_PADDING_WORDS_NOINIT(0x4);
union {
BitField<0, 1, u32> enable;
BitField<4, 8, u32> unk4;
} vp_point_size;
- INSERT_UNION_PADDING_WORDS(1);
+ INSERT_PADDING_WORDS_NOINIT(1);
u32 cull_test_enabled;
FrontFace front_face;
@@ -1193,11 +1223,11 @@ public:
u32 pixel_center_integer;
- INSERT_UNION_PADDING_WORDS(0x1);
+ INSERT_PADDING_WORDS_NOINIT(0x1);
u32 viewport_transform_enabled;
- INSERT_UNION_PADDING_WORDS(0x3);
+ INSERT_PADDING_WORDS_NOINIT(0x3);
union {
BitField<0, 1, u32> depth_range_0_1;
@@ -1206,18 +1236,18 @@ public:
BitField<11, 1, u32> depth_clamp_disabled;
} view_volume_clip_control;
- INSERT_UNION_PADDING_WORDS(0x1F);
+ INSERT_PADDING_WORDS_NOINIT(0x1F);
u32 depth_bounds_enable;
- INSERT_UNION_PADDING_WORDS(1);
+ INSERT_PADDING_WORDS_NOINIT(1);
struct {
u32 enable;
LogicOperation operation;
} logic_op;
- INSERT_UNION_PADDING_WORDS(0x1);
+ INSERT_PADDING_WORDS_NOINIT(0x1);
union {
u32 raw;
@@ -1230,9 +1260,9 @@ public:
BitField<6, 4, u32> RT;
BitField<10, 11, u32> layer;
} clear_buffers;
- INSERT_UNION_PADDING_WORDS(0xB);
+ INSERT_PADDING_WORDS_NOINIT(0xB);
std::array<ColorMask, NumRenderTargets> color_mask;
- INSERT_UNION_PADDING_WORDS(0x38);
+ INSERT_PADDING_WORDS_NOINIT(0x38);
struct {
u32 query_address_high;
@@ -1254,7 +1284,7 @@ public:
}
} query;
- INSERT_UNION_PADDING_WORDS(0x3C);
+ INSERT_PADDING_WORDS_NOINIT(0x3C);
struct {
union {
@@ -1284,8 +1314,7 @@ public:
GPUVAddr LimitAddress() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(limit_high) << 32) |
- limit_low) +
- 1;
+ limit_low);
}
} vertex_array_limit[NumVertexArrays];
@@ -1295,10 +1324,10 @@ public:
BitField<4, 4, ShaderProgram> program;
};
u32 offset;
- INSERT_UNION_PADDING_WORDS(14);
+ INSERT_PADDING_WORDS_NOINIT(14);
} shader_config[MaxShaderProgram];
- INSERT_UNION_PADDING_WORDS(0x60);
+ INSERT_PADDING_WORDS_NOINIT(0x60);
u32 firmware[0x20];
@@ -1307,7 +1336,7 @@ public:
u32 cb_address_high;
u32 cb_address_low;
u32 cb_pos;
- u32 cb_data[NumCBData];
+ std::array<u32, NumCBData> cb_data;
GPUVAddr BufferAddress() const {
return static_cast<GPUVAddr>(
@@ -1315,7 +1344,7 @@ public:
}
} const_buffer;
- INSERT_UNION_PADDING_WORDS(0x10);
+ INSERT_PADDING_WORDS_NOINIT(0x10);
struct {
union {
@@ -1323,18 +1352,18 @@ public:
BitField<0, 1, u32> valid;
BitField<4, 5, u32> index;
};
- INSERT_UNION_PADDING_WORDS(7);
+ INSERT_PADDING_WORDS_NOINIT(7);
} cb_bind[MaxShaderStage];
- INSERT_UNION_PADDING_WORDS(0x56);
+ INSERT_PADDING_WORDS_NOINIT(0x56);
u32 tex_cb_index;
- INSERT_UNION_PADDING_WORDS(0x7D);
+ INSERT_PADDING_WORDS_NOINIT(0x7D);
std::array<std::array<u8, 128>, NumTransformFeedbackBuffers> tfb_varying_locs;
- INSERT_UNION_PADDING_WORDS(0x298);
+ INSERT_PADDING_WORDS_NOINIT(0x298);
struct {
/// Compressed address of a buffer that holds information about bound SSBOs.
@@ -1346,14 +1375,14 @@ public:
}
} ssbo_info;
- INSERT_UNION_PADDING_WORDS(0x11);
+ INSERT_PADDING_WORDS_NOINIT(0x11);
struct {
u32 address[MaxShaderStage];
u32 size[MaxShaderStage];
} tex_info_buffers;
- INSERT_UNION_PADDING_WORDS(0xCC);
+ INSERT_PADDING_WORDS_NOINIT(0xCC);
};
std::array<u32, NUM_REGS> reg_array;
};
@@ -1373,6 +1402,7 @@ public:
};
std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages;
+
u32 current_instance = 0; ///< Current instance to be used to simulate instanced rendering.
};
@@ -1393,12 +1423,6 @@ public:
void FlushMMEInlineDraw();
- /// Given a texture handle, returns the TSC and TIC entries.
- Texture::FullTextureInfo GetTextureInfo(Texture::TextureHandle tex_handle) const;
-
- /// Returns the texture information for a specific texture in a specific shader stage.
- Texture::FullTextureInfo GetStageTexture(ShaderType stage, std::size_t offset) const;
-
u32 AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const override;
SamplerDescriptor AccessBoundSampler(ShaderType stage, u64 offset) const override;
@@ -1428,11 +1452,6 @@ public:
return *rasterizer;
}
- /// Notify a memory write has happened.
- void OnMemoryWrite() {
- dirty.flags |= dirty.on_write_stores;
- }
-
enum class MMEDrawMode : u32 {
Undefined,
Array,
@@ -1454,45 +1473,19 @@ public:
using Tables = std::array<Table, 2>;
Flags flags;
- Flags on_write_stores;
Tables tables{};
} dirty;
private:
void InitializeRegisterDefaults();
- Core::System& system;
- MemoryManager& memory_manager;
+ void ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call);
- VideoCore::RasterizerInterface* rasterizer = nullptr;
+ u32 ProcessShadowRam(u32 method, u32 argument);
- /// Start offsets of each macro in macro_memory
- std::array<u32, 0x80> macro_positions = {};
-
- std::array<bool, Regs::NUM_REGS> mme_inline{};
-
- /// Macro method that is currently being executed / being fed parameters.
- u32 executing_macro = 0;
- /// Parameters that have been submitted to the macro call so far.
- std::vector<u32> macro_params;
-
- /// Interpreter for the macro codes uploaded to the GPU.
- std::unique_ptr<MacroEngine> macro_engine;
-
- static constexpr u32 null_cb_data = 0xFFFFFFFF;
- struct {
- std::array<std::array<u32, 0x4000>, 16> buffer;
- u32 current{null_cb_data};
- u32 id{null_cb_data};
- u32 start_pos{};
- u32 counter{};
- } cb_data_state;
+ void ProcessDirtyRegisters(u32 method, u32 argument);
- Upload::State upload_state;
-
- bool execute_on{true};
-
- std::array<u8, Regs::NUM_REGS> dirty_pointers{};
+ void ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, bool is_last_call);
/// Retrieves information about a specific TIC entry from the TIC buffer.
Texture::TICEntry GetTICEntry(u32 tic_index) const;
@@ -1502,8 +1495,8 @@ private:
/**
* Call a macro on this engine.
+ *
* @param method Method to call
- * @param num_parameters Number of arguments
* @param parameters Arguments to the method call
*/
void CallMacroMethod(u32 method, const std::vector<u32>& parameters);
@@ -1542,7 +1535,7 @@ private:
void FinishCBData();
/// Handles a write to the CB_BIND register.
- void ProcessCBBind(std::size_t stage_index);
+ void ProcessCBBind(size_t stage_index);
/// Handles a write to the VERTEX_END_GL register, triggering a draw.
void DrawArrays();
@@ -1552,6 +1545,38 @@ private:
/// Returns a query's value or an empty object if the value will be deferred through a cache.
std::optional<u64> GetQueryResult();
+
+ Core::System& system;
+ MemoryManager& memory_manager;
+
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
+
+ /// Start offsets of each macro in macro_memory
+ std::array<u32, 0x80> macro_positions{};
+
+ std::array<bool, Regs::NUM_REGS> mme_inline{};
+
+ /// Macro method that is currently being executed / being fed parameters.
+ u32 executing_macro = 0;
+ /// Parameters that have been submitted to the macro call so far.
+ std::vector<u32> macro_params;
+
+ /// Interpreter for the macro codes uploaded to the GPU.
+ std::unique_ptr<MacroEngine> macro_engine;
+
+ static constexpr u32 null_cb_data = 0xFFFFFFFF;
+ struct CBDataState {
+ std::array<std::array<u32, 0x4000>, 16> buffer;
+ u32 current{null_cb_data};
+ u32 id{null_cb_data};
+ u32 start_pos{};
+ u32 counter{};
+ };
+ CBDataState cb_data_state;
+
+ Upload::State upload_state;
+
+ bool execute_on{true};
};
#define ASSERT_REG_POSITION(field_name, position) \
@@ -1564,6 +1589,7 @@ ASSERT_REG_POSITION(shadow_ram_control, 0x49);
ASSERT_REG_POSITION(upload, 0x60);
ASSERT_REG_POSITION(exec_upload, 0x6C);
ASSERT_REG_POSITION(data_upload, 0x6D);
+ASSERT_REG_POSITION(force_early_fragment_tests, 0x84);
ASSERT_REG_POSITION(sync_info, 0xB2);
ASSERT_REG_POSITION(tess_mode, 0xC8);
ASSERT_REG_POSITION(tess_level_outer, 0xC9);
@@ -1586,10 +1612,13 @@ ASSERT_REG_POSITION(polygon_offset_point_enable, 0x370);
ASSERT_REG_POSITION(polygon_offset_line_enable, 0x371);
ASSERT_REG_POSITION(polygon_offset_fill_enable, 0x372);
ASSERT_REG_POSITION(patch_vertices, 0x373);
+ASSERT_REG_POSITION(fragment_barrier, 0x378);
ASSERT_REG_POSITION(scissor_test, 0x380);
ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
+ASSERT_REG_POSITION(invalidate_texture_data_cache, 0x3DD);
+ASSERT_REG_POSITION(tiled_cache_barrier, 0x3DF);
ASSERT_REG_POSITION(color_mask_common, 0x3E4);
ASSERT_REG_POSITION(depth_bounds, 0x3E7);
ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB);
@@ -1597,6 +1626,7 @@ ASSERT_REG_POSITION(multisample_raster_enable, 0x3ED);
ASSERT_REG_POSITION(multisample_raster_samples, 0x3EE);
ASSERT_REG_POSITION(multisample_sample_mask, 0x3EF);
ASSERT_REG_POSITION(zeta, 0x3F8);
+ASSERT_REG_POSITION(render_area, 0x3FD);
ASSERT_REG_POSITION(clear_flags, 0x43E);
ASSERT_REG_POSITION(fill_rectangle, 0x44F);
ASSERT_REG_POSITION(vertex_attrib_format, 0x458);
@@ -1605,7 +1635,8 @@ ASSERT_REG_POSITION(multisample_coverage_to_color, 0x47E);
ASSERT_REG_POSITION(rt_control, 0x487);
ASSERT_REG_POSITION(zeta_width, 0x48a);
ASSERT_REG_POSITION(zeta_height, 0x48b);
-ASSERT_REG_POSITION(zeta_layers, 0x48c);
+ASSERT_REG_POSITION(zeta_depth, 0x48c);
+ASSERT_REG_POSITION(sampler_index, 0x48D);
ASSERT_REG_POSITION(depth_test_enable, 0x4B3);
ASSERT_REG_POSITION(independent_blend_enable, 0x4B9);
ASSERT_REG_POSITION(depth_write_enabled, 0x4BA);
@@ -1629,6 +1660,8 @@ ASSERT_REG_POSITION(frag_color_clamp, 0x4EA);
ASSERT_REG_POSITION(screen_y_control, 0x4EB);
ASSERT_REG_POSITION(line_width_smooth, 0x4EC);
ASSERT_REG_POSITION(line_width_aliased, 0x4ED);
+ASSERT_REG_POSITION(invalidate_sampler_cache_no_wfi, 0x509);
+ASSERT_REG_POSITION(invalidate_texture_header_cache_no_wfi, 0x50A);
ASSERT_REG_POSITION(vb_element_base, 0x50D);
ASSERT_REG_POSITION(vb_base_instance, 0x50E);
ASSERT_REG_POSITION(clip_distance_enabled, 0x544);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index e88290754..a2f19559f 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -16,8 +16,10 @@ namespace Tegra::Engines {
using namespace Texture;
-MaxwellDMA::MaxwellDMA(Core::System& system, MemoryManager& memory_manager)
- : system{system}, memory_manager{memory_manager} {}
+MaxwellDMA::MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_)
+ : system{system_}, memory_manager{memory_manager_} {}
+
+MaxwellDMA::~MaxwellDMA() = default;
void MaxwellDMA::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < NUM_REGS, "Invalid MaxwellDMA register");
@@ -58,9 +60,6 @@ void MaxwellDMA::Launch() {
return;
}
- // All copies here update the main memory, so mark all rasterizer states as invalid.
- system.GPU().Maxwell3D().OnMemoryWrite();
-
if (is_src_pitch && is_dst_pitch) {
CopyPitchToPitch();
} else {
@@ -94,6 +93,7 @@ void MaxwellDMA::CopyPitchToPitch() {
}
void MaxwellDMA::CopyBlockLinearToPitch() {
+ UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0);
UNIMPLEMENTED_IF(regs.src_params.block_size.depth != 0);
UNIMPLEMENTED_IF(regs.src_params.layer != 0);
@@ -114,8 +114,6 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
const u32 block_depth = src_params.block_size.depth;
const size_t src_size =
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
- const size_t src_layer_size =
- CalculateSize(true, bytes_per_pixel, width, height, 1, block_height, block_depth);
if (read_buffer.size() < src_size) {
read_buffer.resize(src_size);
@@ -135,6 +133,8 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
}
void MaxwellDMA::CopyPitchToBlockLinear() {
+ UNIMPLEMENTED_IF_MSG(regs.dst_params.block_size.width != 0, "Block width is not one");
+
const auto& dst_params = regs.dst_params;
const u32 bytes_per_pixel = regs.pitch_in / regs.line_length_in;
const u32 width = dst_params.width;
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index 50f445efc..3c59eeb13 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -72,11 +72,13 @@ public:
struct RenderEnable {
enum class Mode : u32 {
- FALSE = 0,
- TRUE = 1,
- CONDITIONAL = 2,
- RENDER_IF_EQUAL = 3,
- RENDER_IF_NOT_EQUAL = 4,
+ // Note: This uses Pascal case in order to avoid the identifiers
+ // FALSE and TRUE, which are reserved on Darwin.
+ False = 0,
+ True = 1,
+ Conditional = 2,
+ RenderIfEqual = 3,
+ RenderIfNotEqual = 4,
};
PackedGPUVAddr address;
@@ -185,8 +187,8 @@ public:
};
static_assert(sizeof(RemapConst) == 12);
- explicit MaxwellDMA(Core::System& system, MemoryManager& memory_manager);
- ~MaxwellDMA() = default;
+ explicit MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_);
+ ~MaxwellDMA();
/// Write the value to the register identified by method.
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override;
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index d374b73cf..8b45f1b62 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -32,31 +32,31 @@ struct Register {
constexpr Register() = default;
- constexpr Register(u64 value) : value(value) {}
+ constexpr Register(u64 value_) : value(value_) {}
- constexpr operator u64() const {
+ [[nodiscard]] constexpr operator u64() const {
return value;
}
template <typename T>
- constexpr u64 operator-(const T& oth) const {
+ [[nodiscard]] constexpr u64 operator-(const T& oth) const {
return value - oth;
}
template <typename T>
- constexpr u64 operator&(const T& oth) const {
+ [[nodiscard]] constexpr u64 operator&(const T& oth) const {
return value & oth;
}
- constexpr u64 operator&(const Register& oth) const {
+ [[nodiscard]] constexpr u64 operator&(const Register& oth) const {
return value & oth.value;
}
- constexpr u64 operator~() const {
+ [[nodiscard]] constexpr u64 operator~() const {
return ~value;
}
- u64 GetSwizzledIndex(u64 elem) const {
+ [[nodiscard]] u64 GetSwizzledIndex(u64 elem) const {
elem = (value + elem) & 3;
return (value & ~3) + elem;
}
@@ -75,7 +75,7 @@ enum class AttributeSize : u64 {
union Attribute {
Attribute() = default;
- constexpr explicit Attribute(u64 value) : value(value) {}
+ constexpr explicit Attribute(u64 value_) : value(value_) {}
enum class Index : u64 {
LayerViewportPointSize = 6,
@@ -107,7 +107,7 @@ union Attribute {
BitField<31, 1, u64> patch;
BitField<47, 3, AttributeSize> size;
- bool IsPhysical() const {
+ [[nodiscard]] bool IsPhysical() const {
return patch == 0 && element == 0 && static_cast<u64>(index.Value()) == 0;
}
} fmt20;
@@ -124,7 +124,7 @@ union Attribute {
union Sampler {
Sampler() = default;
- constexpr explicit Sampler(u64 value) : value(value) {}
+ constexpr explicit Sampler(u64 value_) : value(value_) {}
enum class Index : u64 {
Sampler_0 = 8,
@@ -137,7 +137,7 @@ union Sampler {
union Image {
Image() = default;
- constexpr explicit Image(u64 value) : value{value} {}
+ constexpr explicit Image(u64 value_) : value{value_} {}
BitField<36, 13, u64> index;
u64 value;
@@ -505,14 +505,14 @@ struct IpaMode {
IpaInterpMode interpolation_mode;
IpaSampleMode sampling_mode;
- bool operator==(const IpaMode& a) const {
+ [[nodiscard]] bool operator==(const IpaMode& a) const {
return std::tie(interpolation_mode, sampling_mode) ==
std::tie(a.interpolation_mode, a.sampling_mode);
}
- bool operator!=(const IpaMode& a) const {
+ [[nodiscard]] bool operator!=(const IpaMode& a) const {
return !operator==(a);
}
- bool operator<(const IpaMode& a) const {
+ [[nodiscard]] bool operator<(const IpaMode& a) const {
return std::tie(interpolation_mode, sampling_mode) <
std::tie(a.interpolation_mode, a.sampling_mode);
}
@@ -658,10 +658,10 @@ union Instruction {
return *this;
}
- constexpr Instruction(u64 value) : value{value} {}
+ constexpr Instruction(u64 value_) : value{value_} {}
constexpr Instruction(const Instruction& instr) : value(instr.value) {}
- constexpr bool Bit(u64 offset) const {
+ [[nodiscard]] constexpr bool Bit(u64 offset) const {
return ((value >> offset) & 1) != 0;
}
@@ -746,34 +746,34 @@ union Instruction {
BitField<28, 8, u64> imm_lut28;
BitField<48, 8, u64> imm_lut48;
- u32 GetImmLut28() const {
+ [[nodiscard]] u32 GetImmLut28() const {
return static_cast<u32>(imm_lut28);
}
- u32 GetImmLut48() const {
+ [[nodiscard]] u32 GetImmLut48() const {
return static_cast<u32>(imm_lut48);
}
} lop3;
- u16 GetImm20_16() const {
+ [[nodiscard]] u16 GetImm20_16() const {
return static_cast<u16>(imm20_16);
}
- u32 GetImm20_19() const {
+ [[nodiscard]] u32 GetImm20_19() const {
u32 imm{static_cast<u32>(imm20_19)};
imm <<= 12;
imm |= negate_imm ? 0x80000000 : 0;
return imm;
}
- u32 GetImm20_32() const {
+ [[nodiscard]] u32 GetImm20_32() const {
return static_cast<u32>(imm20_32);
}
- s32 GetSignedImm20_20() const {
- u32 immediate = static_cast<u32>(imm20_19 | (negate_imm << 19));
+ [[nodiscard]] s32 GetSignedImm20_20() const {
+ const auto immediate = static_cast<u32>(imm20_19 | (negate_imm << 19));
// Sign extend the 20-bit value.
- u32 mask = 1U << (20 - 1);
+ const auto mask = 1U << (20 - 1);
return static_cast<s32>((immediate ^ mask) - mask);
}
} alu;
@@ -857,7 +857,7 @@ union Instruction {
BitField<56, 1, u64> second_negate;
BitField<30, 9, u64> second;
- u32 PackImmediates() const {
+ [[nodiscard]] u32 PackImmediates() const {
// Immediates are half floats shifted.
constexpr u32 imm_shift = 6;
return static_cast<u32>((first << imm_shift) | (second << (16 + imm_shift)));
@@ -1033,7 +1033,7 @@ union Instruction {
BitField<28, 2, AtomicType> type;
BitField<30, 22, s64> offset;
- s32 GetImmediateOffset() const {
+ [[nodiscard]] s32 GetImmediateOffset() const {
return static_cast<s32>(offset << 2);
}
} atoms;
@@ -1215,7 +1215,7 @@ union Instruction {
BitField<39, 4, u64> rounding;
// H0, H1 extract for F16 missing
BitField<41, 1, u64> selector; // Guessed as some games set it, TODO: reverse this value
- F2fRoundingOp GetRoundingMode() const {
+ [[nodiscard]] F2fRoundingOp GetRoundingMode() const {
constexpr u64 rounding_mask = 0x0B;
return static_cast<F2fRoundingOp>(rounding.Value() & rounding_mask);
}
@@ -1239,15 +1239,15 @@ union Instruction {
BitField<54, 1, u64> aoffi_flag;
BitField<55, 3, TextureProcessMode> process_mode;
- bool IsComponentEnabled(std::size_t component) const {
- return ((1ull << component) & component_mask) != 0;
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
+ return ((1ULL << component) & component_mask) != 0;
}
- TextureProcessMode GetTextureProcessMode() const {
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
return process_mode;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::DC:
return dc_flag != 0;
@@ -1271,15 +1271,15 @@ union Instruction {
BitField<36, 1, u64> aoffi_flag;
BitField<37, 3, TextureProcessMode> process_mode;
- bool IsComponentEnabled(std::size_t component) const {
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
return ((1ULL << component) & component_mask) != 0;
}
- TextureProcessMode GetTextureProcessMode() const {
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
return process_mode;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::DC:
return dc_flag != 0;
@@ -1299,7 +1299,7 @@ union Instruction {
BitField<31, 4, u64> component_mask;
BitField<49, 1, u64> nodep_flag;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::NODEP:
return nodep_flag != 0;
@@ -1309,7 +1309,7 @@ union Instruction {
return false;
}
- bool IsComponentEnabled(std::size_t component) const {
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
return ((1ULL << component) & component_mask) != 0;
}
} txq;
@@ -1321,11 +1321,11 @@ union Instruction {
BitField<35, 1, u64> ndv_flag;
BitField<49, 1, u64> nodep_flag;
- bool IsComponentEnabled(std::size_t component) const {
- return ((1ull << component) & component_mask) != 0;
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
+ return ((1ULL << component) & component_mask) != 0;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::NDV:
return (ndv_flag != 0);
@@ -1347,7 +1347,7 @@ union Instruction {
BitField<54, 2, u64> offset_mode;
BitField<56, 2, u64> component;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::NDV:
return ndv_flag != 0;
@@ -1373,7 +1373,7 @@ union Instruction {
BitField<33, 2, u64> offset_mode;
BitField<37, 2, u64> component;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::NDV:
return ndv_flag != 0;
@@ -1399,7 +1399,7 @@ union Instruction {
BitField<52, 2, u64> component;
BitField<55, 1, u64> fp16_flag;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::DC:
return dc_flag != 0;
@@ -1422,24 +1422,27 @@ union Instruction {
BitField<53, 4, u64> texture_info;
BitField<59, 1, u64> fp32_flag;
- TextureType GetTextureType() const {
+ [[nodiscard]] TextureType GetTextureType() const {
// The TEXS instruction has a weird encoding for the texture type.
- if (texture_info == 0)
+ if (texture_info == 0) {
return TextureType::Texture1D;
- if (texture_info >= 1 && texture_info <= 9)
+ }
+ if (texture_info >= 1 && texture_info <= 9) {
return TextureType::Texture2D;
- if (texture_info >= 10 && texture_info <= 11)
+ }
+ if (texture_info >= 10 && texture_info <= 11) {
return TextureType::Texture3D;
- if (texture_info >= 12 && texture_info <= 13)
+ }
+ if (texture_info >= 12 && texture_info <= 13) {
return TextureType::TextureCube;
+ }
- LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
- static_cast<u32>(texture_info.Value()));
+ LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}", texture_info.Value());
UNREACHABLE();
return TextureType::Texture1D;
}
- TextureProcessMode GetTextureProcessMode() const {
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
switch (texture_info) {
case 0:
case 2:
@@ -1458,7 +1461,7 @@ union Instruction {
return TextureProcessMode::None;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::DC:
return (texture_info >= 4 && texture_info <= 6) || texture_info == 9;
@@ -1470,16 +1473,16 @@ union Instruction {
return false;
}
- bool IsArrayTexture() const {
+ [[nodiscard]] bool IsArrayTexture() const {
// TEXS only supports Texture2D arrays.
return texture_info >= 7 && texture_info <= 9;
}
- bool HasTwoDestinations() const {
+ [[nodiscard]] bool HasTwoDestinations() const {
return gpr28.Value() != Register::ZeroIndex;
}
- bool IsComponentEnabled(std::size_t component) const {
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
static constexpr std::array<std::array<u32, 8>, 4> mask_lut{{
{},
{0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc},
@@ -1506,7 +1509,7 @@ union Instruction {
BitField<54, 1, u64> cl;
BitField<55, 1, u64> process_mode;
- TextureProcessMode GetTextureProcessMode() const {
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
return process_mode == 0 ? TextureProcessMode::LZ : TextureProcessMode::LL;
}
} tld;
@@ -1516,7 +1519,7 @@ union Instruction {
BitField<53, 4, u64> texture_info;
BitField<59, 1, u64> fp32_flag;
- TextureType GetTextureType() const {
+ [[nodiscard]] TextureType GetTextureType() const {
// The TLDS instruction has a weird encoding for the texture type.
if (texture_info <= 1) {
return TextureType::Texture1D;
@@ -1529,19 +1532,19 @@ union Instruction {
return TextureType::Texture3D;
}
- LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
- static_cast<u32>(texture_info.Value()));
+ LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}", texture_info.Value());
UNREACHABLE();
return TextureType::Texture1D;
}
- TextureProcessMode GetTextureProcessMode() const {
- if (texture_info == 1 || texture_info == 5 || texture_info == 12)
+ [[nodiscard]] TextureProcessMode GetTextureProcessMode() const {
+ if (texture_info == 1 || texture_info == 5 || texture_info == 12) {
return TextureProcessMode::LL;
+ }
return TextureProcessMode::LZ;
}
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::AOFFI:
return texture_info == 12 || texture_info == 4;
@@ -1555,7 +1558,7 @@ union Instruction {
return false;
}
- bool IsArrayTexture() const {
+ [[nodiscard]] bool IsArrayTexture() const {
// TEXS only supports Texture2D arrays.
return texture_info == 8;
}
@@ -1567,7 +1570,7 @@ union Instruction {
BitField<35, 1, u64> aoffi_flag;
BitField<49, 1, u64> nodep_flag;
- bool UsesMiscMode(TextureMiscMode mode) const {
+ [[nodiscard]] bool UsesMiscMode(TextureMiscMode mode) const {
switch (mode) {
case TextureMiscMode::AOFFI:
return aoffi_flag != 0;
@@ -1591,7 +1594,7 @@ union Instruction {
BitField<20, 3, StoreType> store_data_layout;
BitField<20, 4, u64> component_mask_selector;
- bool IsComponentEnabled(std::size_t component) const {
+ [[nodiscard]] bool IsComponentEnabled(std::size_t component) const {
ASSERT(mode == SurfaceDataMode::P);
constexpr u8 R = 0b0001;
constexpr u8 G = 0b0010;
@@ -1604,7 +1607,7 @@ union Instruction {
return std::bitset<4>{mask.at(component_mask_selector)}.test(component);
}
- StoreType GetStoreDataLayout() const {
+ [[nodiscard]] StoreType GetStoreDataLayout() const {
ASSERT(mode == SurfaceDataMode::D_BA);
return store_data_layout;
}
@@ -1622,14 +1625,15 @@ union Instruction {
BitField<20, 24, u64> target;
BitField<5, 1, u64> constant_buffer;
- s32 GetBranchTarget() const {
+ [[nodiscard]] s32 GetBranchTarget() const {
// Sign extend the branch target offset
- u32 mask = 1U << (24 - 1);
- u32 value = static_cast<u32>(target);
+ const auto mask = 1U << (24 - 1);
+ const auto target_value = static_cast<u32>(target);
+ constexpr auto instruction_size = static_cast<s32>(sizeof(Instruction));
+
// The branch offset is relative to the next instruction and is stored in bytes, so
// divide it by the size of an instruction and add 1 to it.
- return static_cast<s32>((value ^ mask) - mask) / static_cast<s32>(sizeof(Instruction)) +
- 1;
+ return static_cast<s32>((target_value ^ mask) - mask) / instruction_size + 1;
}
} bra;
@@ -1637,14 +1641,15 @@ union Instruction {
BitField<20, 24, u64> target;
BitField<5, 1, u64> constant_buffer;
- s32 GetBranchExtend() const {
+ [[nodiscard]] s32 GetBranchExtend() const {
// Sign extend the branch target offset
- u32 mask = 1U << (24 - 1);
- u32 value = static_cast<u32>(target);
+ const auto mask = 1U << (24 - 1);
+ const auto target_value = static_cast<u32>(target);
+ constexpr auto instruction_size = static_cast<s32>(sizeof(Instruction));
+
// The branch offset is relative to the next instruction and is stored in bytes, so
// divide it by the size of an instruction and add 1 to it.
- return static_cast<s32>((value ^ mask) - mask) / static_cast<s32>(sizeof(Instruction)) +
- 1;
+ return static_cast<s32>((target_value ^ mask) - mask) / instruction_size + 1;
}
} brx;
@@ -1697,7 +1702,7 @@ union Instruction {
BitField<50, 1, u64> is_op_b_register;
BitField<51, 3, VmnmxOperation> operation;
- VmnmxType SourceFormatA() const {
+ [[nodiscard]] VmnmxType SourceFormatA() const {
switch (src_format_a) {
case 0b11:
return VmnmxType::Bits32;
@@ -1708,7 +1713,7 @@ union Instruction {
}
}
- VmnmxType SourceFormatB() const {
+ [[nodiscard]] VmnmxType SourceFormatB() const {
switch (src_format_b) {
case 0b11:
return VmnmxType::Bits32;
@@ -1739,7 +1744,7 @@ union Instruction {
BitField<20, 14, u64> shifted_offset;
BitField<34, 5, u64> index;
- u64 GetOffset() const {
+ [[nodiscard]] u64 GetOffset() const {
return shifted_offset * 4;
}
} cbuf34;
@@ -1748,7 +1753,7 @@ union Instruction {
BitField<20, 16, s64> offset;
BitField<36, 5, u64> index;
- s64 GetOffset() const {
+ [[nodiscard]] s64 GetOffset() const {
return offset;
}
} cbuf36;
@@ -1893,6 +1898,7 @@ public:
ICMP_IMM,
FCMP_RR,
FCMP_RC,
+ FCMP_IMMR,
MUFU, // Multi-Function Operator
RRO_C, // Range Reduction Operator
RRO_R,
@@ -1996,29 +2002,29 @@ public:
/// Returns whether an opcode has an execution predicate field or not (ie, whether it can be
/// conditionally executed).
- static bool IsPredicatedInstruction(Id opcode) {
+ [[nodiscard]] static bool IsPredicatedInstruction(Id opcode) {
// TODO(Subv): Add the rest of unpredicated instructions.
return opcode != Id::SSY && opcode != Id::PBK;
}
class Matcher {
public:
- constexpr Matcher(const char* const name, u16 mask, u16 expected, Id id, Type type)
- : name{name}, mask{mask}, expected{expected}, id{id}, type{type} {}
+ constexpr Matcher(const char* const name_, u16 mask_, u16 expected_, Id id_, Type type_)
+ : name{name_}, mask{mask_}, expected{expected_}, id{id_}, type{type_} {}
- constexpr const char* GetName() const {
+ [[nodiscard]] constexpr const char* GetName() const {
return name;
}
- constexpr u16 GetMask() const {
+ [[nodiscard]] constexpr u16 GetMask() const {
return mask;
}
- constexpr Id GetId() const {
+ [[nodiscard]] constexpr Id GetId() const {
return id;
}
- constexpr Type GetType() const {
+ [[nodiscard]] constexpr Type GetType() const {
return type;
}
@@ -2027,7 +2033,7 @@ public:
* @param instruction The instruction to test
* @returns true if the given instruction matches.
*/
- constexpr bool Matches(u16 instruction) const {
+ [[nodiscard]] constexpr bool Matches(u16 instruction) const {
return (instruction & mask) == expected;
}
@@ -2039,7 +2045,8 @@ public:
Type type;
};
- static std::optional<std::reference_wrapper<const Matcher>> Decode(Instruction instr) {
+ using DecodeResult = std::optional<std::reference_wrapper<const Matcher>>;
+ [[nodiscard]] static DecodeResult Decode(Instruction instr) {
static const auto table{GetDecodeTable()};
const auto matches_instruction = [instr](const auto& matcher) {
@@ -2061,7 +2068,7 @@ private:
* A '0' in a bitstring indicates that a zero must be present at that bit position.
* A '1' in a bitstring indicates that a one must be present at that bit position.
*/
- static constexpr auto GetMaskAndExpect(const char* const bitstring) {
+ [[nodiscard]] static constexpr auto GetMaskAndExpect(const char* const bitstring) {
u16 mask = 0, expect = 0;
for (std::size_t i = 0; i < opcode_bitsize; i++) {
const std::size_t bit_position = opcode_bitsize - i - 1;
@@ -2083,14 +2090,14 @@ private:
public:
/// Creates a matcher that can match and parse instructions based on bitstring.
- static constexpr auto GetMatcher(const char* const bitstring, Id op, Type type,
- const char* const name) {
+ [[nodiscard]] static constexpr auto GetMatcher(const char* const bitstring, Id op,
+ Type type, const char* const name) {
const auto [mask, expected] = GetMaskAndExpect(bitstring);
return Matcher(name, mask, expected, op, type);
}
};
- static std::vector<Matcher> GetDecodeTable() {
+ [[nodiscard]] static std::vector<Matcher> GetDecodeTable() {
std::vector<Matcher> table = {
#define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name)
INST("111000110011----", Id::KIL, Type::Flow, "KIL"),
@@ -2205,6 +2212,7 @@ private:
INST("0111110-0-------", Id::HSET2_IMM, Type::HalfSet, "HSET2_IMM"),
INST("010110111010----", Id::FCMP_RR, Type::Arithmetic, "FCMP_RR"),
INST("010010111010----", Id::FCMP_RC, Type::Arithmetic, "FCMP_RC"),
+ INST("0011011-1010----", Id::FCMP_IMMR, Type::Arithmetic, "FCMP_IMMR"),
INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"),
INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"),
INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"),
diff --git a/src/video_core/engines/shader_header.h b/src/video_core/engines/shader_header.h
index 72e2a33d5..e0d7b89c5 100644
--- a/src/video_core/engines/shader_header.h
+++ b/src/video_core/engines/shader_header.h
@@ -41,37 +41,37 @@ struct Header {
BitField<26, 1, u32> does_load_or_store;
BitField<27, 1, u32> does_fp64;
BitField<28, 4, u32> stream_out_mask;
- } common0{};
+ } common0;
union {
BitField<0, 24, u32> shader_local_memory_low_size;
BitField<24, 8, u32> per_patch_attribute_count;
- } common1{};
+ } common1;
union {
BitField<0, 24, u32> shader_local_memory_high_size;
BitField<24, 8, u32> threads_per_input_primitive;
- } common2{};
+ } common2;
union {
BitField<0, 24, u32> shader_local_memory_crs_size;
BitField<24, 4, OutputTopology> output_topology;
BitField<28, 4, u32> reserved;
- } common3{};
+ } common3;
union {
BitField<0, 12, u32> max_output_vertices;
BitField<12, 8, u32> store_req_start; // NOTE: not used by geometry shaders.
BitField<20, 4, u32> reserved;
BitField<24, 8, u32> store_req_end; // NOTE: not used by geometry shaders.
- } common4{};
+ } common4;
union {
struct {
- INSERT_UNION_PADDING_BYTES(3); // ImapSystemValuesA
- INSERT_UNION_PADDING_BYTES(1); // ImapSystemValuesB
- INSERT_UNION_PADDING_BYTES(16); // ImapGenericVector[32]
- INSERT_UNION_PADDING_BYTES(2); // ImapColor
+ INSERT_PADDING_BYTES_NOINIT(3); // ImapSystemValuesA
+ INSERT_PADDING_BYTES_NOINIT(1); // ImapSystemValuesB
+ INSERT_PADDING_BYTES_NOINIT(16); // ImapGenericVector[32]
+ INSERT_PADDING_BYTES_NOINIT(2); // ImapColor
union {
BitField<0, 8, u16> clip_distances;
BitField<8, 1, u16> point_sprite_s;
@@ -82,20 +82,20 @@ struct Header {
BitField<14, 1, u16> instance_id;
BitField<15, 1, u16> vertex_id;
};
- INSERT_UNION_PADDING_BYTES(5); // ImapFixedFncTexture[10]
- INSERT_UNION_PADDING_BYTES(1); // ImapReserved
- INSERT_UNION_PADDING_BYTES(3); // OmapSystemValuesA
- INSERT_UNION_PADDING_BYTES(1); // OmapSystemValuesB
- INSERT_UNION_PADDING_BYTES(16); // OmapGenericVector[32]
- INSERT_UNION_PADDING_BYTES(2); // OmapColor
- INSERT_UNION_PADDING_BYTES(2); // OmapSystemValuesC
- INSERT_UNION_PADDING_BYTES(5); // OmapFixedFncTexture[10]
- INSERT_UNION_PADDING_BYTES(1); // OmapReserved
+ INSERT_PADDING_BYTES_NOINIT(5); // ImapFixedFncTexture[10]
+ INSERT_PADDING_BYTES_NOINIT(1); // ImapReserved
+ INSERT_PADDING_BYTES_NOINIT(3); // OmapSystemValuesA
+ INSERT_PADDING_BYTES_NOINIT(1); // OmapSystemValuesB
+ INSERT_PADDING_BYTES_NOINIT(16); // OmapGenericVector[32]
+ INSERT_PADDING_BYTES_NOINIT(2); // OmapColor
+ INSERT_PADDING_BYTES_NOINIT(2); // OmapSystemValuesC
+ INSERT_PADDING_BYTES_NOINIT(5); // OmapFixedFncTexture[10]
+ INSERT_PADDING_BYTES_NOINIT(1); // OmapReserved
} vtg;
struct {
- INSERT_UNION_PADDING_BYTES(3); // ImapSystemValuesA
- INSERT_UNION_PADDING_BYTES(1); // ImapSystemValuesB
+ INSERT_PADDING_BYTES_NOINIT(3); // ImapSystemValuesA
+ INSERT_PADDING_BYTES_NOINIT(1); // ImapSystemValuesB
union {
BitField<0, 2, PixelImap> x;
@@ -105,10 +105,10 @@ struct Header {
u8 raw;
} imap_generic_vector[32];
- INSERT_UNION_PADDING_BYTES(2); // ImapColor
- INSERT_UNION_PADDING_BYTES(2); // ImapSystemValuesC
- INSERT_UNION_PADDING_BYTES(10); // ImapFixedFncTexture[10]
- INSERT_UNION_PADDING_BYTES(2); // ImapReserved
+ INSERT_PADDING_BYTES_NOINIT(2); // ImapColor
+ INSERT_PADDING_BYTES_NOINIT(2); // ImapSystemValuesC
+ INSERT_PADDING_BYTES_NOINIT(10); // ImapFixedFncTexture[10]
+ INSERT_PADDING_BYTES_NOINIT(2); // ImapReserved
struct {
u32 target;
@@ -145,7 +145,7 @@ struct Header {
}
} ps;
- std::array<u32, 0xF> raw{};
+ std::array<u32, 0xF> raw;
};
u64 GetLocalMemorySize() const {
@@ -153,7 +153,6 @@ struct Header {
(common2.shader_local_memory_high_size << 24));
}
};
-
static_assert(sizeof(Header) == 0x50, "Incorrect structure size");
} // namespace Tegra::Shader
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index 06cc12d5a..f055b61e9 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -9,6 +9,7 @@
#include "common/common_types.h"
#include "core/core.h"
+#include "video_core/delayed_destruction_ring.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
@@ -17,11 +18,11 @@ namespace VideoCommon {
class FenceBase {
public:
- FenceBase(u32 payload, bool is_stubbed)
- : address{}, payload{payload}, is_semaphore{false}, is_stubbed{is_stubbed} {}
+ explicit FenceBase(u32 payload_, bool is_stubbed_)
+ : address{}, payload{payload_}, is_semaphore{false}, is_stubbed{is_stubbed_} {}
- FenceBase(GPUVAddr address, u32 payload, bool is_stubbed)
- : address{address}, payload{payload}, is_semaphore{true}, is_stubbed{is_stubbed} {}
+ explicit FenceBase(GPUVAddr address_, u32 payload_, bool is_stubbed_)
+ : address{address_}, payload{payload_}, is_semaphore{true}, is_stubbed{is_stubbed_} {}
GPUVAddr GetAddress() const {
return address;
@@ -47,6 +48,11 @@ protected:
template <typename TFence, typename TTextureCache, typename TTBufferCache, typename TQueryCache>
class FenceManager {
public:
+ /// Notify the fence manager about a new frame
+ void TickFrame() {
+ delayed_destruction_ring.Tick();
+ }
+
void SignalSemaphore(GPUVAddr addr, u32 value) {
TryReleasePendingFences();
const bool should_flush = ShouldFlush();
@@ -74,8 +80,6 @@ public:
}
void WaitPendingFences() {
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
while (!fences.empty()) {
TFence& current_fence = fences.front();
if (ShouldWait()) {
@@ -83,23 +87,23 @@ public:
}
PopAsyncFlushes();
if (current_fence->IsSemaphore()) {
- memory_manager.template Write<u32>(current_fence->GetAddress(),
- current_fence->GetPayload());
+ gpu_memory.template Write<u32>(current_fence->GetAddress(),
+ current_fence->GetPayload());
} else {
gpu.IncrementSyncPoint(current_fence->GetPayload());
}
- fences.pop();
+ PopFence();
}
}
protected:
- FenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- TTextureCache& texture_cache, TTBufferCache& buffer_cache,
- TQueryCache& query_cache)
- : system{system}, rasterizer{rasterizer}, texture_cache{texture_cache},
- buffer_cache{buffer_cache}, query_cache{query_cache} {}
+ explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
+ TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
+ TQueryCache& query_cache_)
+ : rasterizer{rasterizer_}, gpu{gpu_}, gpu_memory{gpu.MemoryManager()},
+ texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
- virtual ~FenceManager() {}
+ virtual ~FenceManager() = default;
/// Creates a Sync Point Fence Interface, does not create a backend fence if 'is_stubbed' is
/// true
@@ -113,16 +117,15 @@ protected:
/// Waits until a fence has been signalled by the host GPU.
virtual void WaitFence(TFence& fence) = 0;
- Core::System& system;
VideoCore::RasterizerInterface& rasterizer;
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
TTextureCache& texture_cache;
TTBufferCache& buffer_cache;
TQueryCache& query_cache;
private:
void TryReleasePendingFences() {
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
while (!fences.empty()) {
TFence& current_fence = fences.front();
if (ShouldWait() && !IsFenceSignaled(current_fence)) {
@@ -130,38 +133,49 @@ private:
}
PopAsyncFlushes();
if (current_fence->IsSemaphore()) {
- memory_manager.template Write<u32>(current_fence->GetAddress(),
- current_fence->GetPayload());
+ gpu_memory.template Write<u32>(current_fence->GetAddress(),
+ current_fence->GetPayload());
} else {
gpu.IncrementSyncPoint(current_fence->GetPayload());
}
- fences.pop();
+ PopFence();
}
}
bool ShouldWait() const {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
return texture_cache.ShouldWaitAsyncFlushes() || buffer_cache.ShouldWaitAsyncFlushes() ||
query_cache.ShouldWaitAsyncFlushes();
}
bool ShouldFlush() const {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
return texture_cache.HasUncommittedFlushes() || buffer_cache.HasUncommittedFlushes() ||
query_cache.HasUncommittedFlushes();
}
void PopAsyncFlushes() {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
texture_cache.PopAsyncFlushes();
buffer_cache.PopAsyncFlushes();
query_cache.PopAsyncFlushes();
}
void CommitAsyncFlushes() {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
texture_cache.CommitAsyncFlushes();
buffer_cache.CommitAsyncFlushes();
query_cache.CommitAsyncFlushes();
}
+ void PopFence() {
+ delayed_destruction_ring.Push(std::move(fences.front()));
+ fences.pop();
+ }
+
std::queue<TFence> fences;
+
+ DelayedDestructionRing<TFence, 6> delayed_destruction_ring;
};
} // namespace VideoCommon
diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h
new file mode 100644
index 000000000..b86c3a757
--- /dev/null
+++ b/src/video_core/framebuffer_config.h
@@ -0,0 +1,31 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Tegra {
+
+/**
+ * Struct describing framebuffer configuration
+ */
+struct FramebufferConfig {
+ enum class PixelFormat : u32 {
+ A8B8G8R8_UNORM = 1,
+ RGB565_UNORM = 4,
+ B8G8R8A8_UNORM = 5,
+ };
+
+ VAddr address{};
+ u32 offset{};
+ u32 width{};
+ u32 height{};
+ u32 stride{};
+ PixelFormat pixel_format{};
+
+ using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
+ TransformFlags transform_flags{};
+ Common::Rectangle<int> crop_rect;
+};
+
+} // namespace Tegra
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index acb6e6d46..2a9bd4121 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -10,6 +10,7 @@
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/frontend/emu_window.h"
+#include "core/hardware_interrupt_manager.h"
#include "core/memory.h"
#include "core/settings.h"
#include "video_core/engines/fermi_2d.h"
@@ -27,22 +28,24 @@ namespace Tegra {
MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
-GPU::GPU(Core::System& system_, bool is_async_)
- : system{system_}, dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)},
- memory_manager{std::make_unique<Tegra::MemoryManager>(system)},
+GPU::GPU(Core::System& system_, bool is_async_, bool use_nvdec_)
+ : system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(system)},
+ dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)},
+ cdma_pusher{std::make_unique<Tegra::CDmaPusher>(*this)}, use_nvdec{use_nvdec_},
maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)},
fermi_2d{std::make_unique<Engines::Fermi2D>()},
kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)},
maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)},
kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)},
- shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_} {}
+ shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_},
+ gpu_thread{system_, is_async_} {}
GPU::~GPU() = default;
void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
renderer = std::move(renderer_);
+ rasterizer = renderer->ReadRasterizer();
- VideoCore::RasterizerInterface& rasterizer = renderer->Rasterizer();
memory_manager->BindRasterizer(rasterizer);
maxwell_3d->BindRasterizer(rasterizer);
fermi_2d->BindRasterizer(rasterizer);
@@ -77,31 +80,46 @@ DmaPusher& GPU::DmaPusher() {
return *dma_pusher;
}
+Tegra::CDmaPusher& GPU::CDmaPusher() {
+ return *cdma_pusher;
+}
+
const DmaPusher& GPU::DmaPusher() const {
return *dma_pusher;
}
+const Tegra::CDmaPusher& GPU::CDmaPusher() const {
+ return *cdma_pusher;
+}
+
void GPU::WaitFence(u32 syncpoint_id, u32 value) {
// Synced GPU, is always in sync
if (!is_async) {
return;
}
+ if (syncpoint_id == UINT32_MAX) {
+ // TODO: Research what this does.
+ LOG_ERROR(HW_GPU, "Waiting for syncpoint -1 not implemented");
+ return;
+ }
MICROPROFILE_SCOPE(GPU_wait);
std::unique_lock lock{sync_mutex};
- sync_cv.wait(lock, [=, this] { return syncpoints[syncpoint_id].load() >= value; });
+ sync_cv.wait(lock, [=, this] { return syncpoints.at(syncpoint_id).load() >= value; });
}
void GPU::IncrementSyncPoint(const u32 syncpoint_id) {
- syncpoints[syncpoint_id]++;
+ auto& syncpoint = syncpoints.at(syncpoint_id);
+ syncpoint++;
std::lock_guard lock{sync_mutex};
sync_cv.notify_all();
- if (!syncpt_interrupts[syncpoint_id].empty()) {
- u32 value = syncpoints[syncpoint_id].load();
- auto it = syncpt_interrupts[syncpoint_id].begin();
- while (it != syncpt_interrupts[syncpoint_id].end()) {
+ auto& interrupt = syncpt_interrupts.at(syncpoint_id);
+ if (!interrupt.empty()) {
+ u32 value = syncpoint.load();
+ auto it = interrupt.begin();
+ while (it != interrupt.end()) {
if (value >= *it) {
TriggerCpuInterrupt(syncpoint_id, *it);
- it = syncpt_interrupts[syncpoint_id].erase(it);
+ it = interrupt.erase(it);
continue;
}
it++;
@@ -110,22 +128,22 @@ void GPU::IncrementSyncPoint(const u32 syncpoint_id) {
}
u32 GPU::GetSyncpointValue(const u32 syncpoint_id) const {
- return syncpoints[syncpoint_id].load();
+ return syncpoints.at(syncpoint_id).load();
}
void GPU::RegisterSyncptInterrupt(const u32 syncpoint_id, const u32 value) {
- auto& interrupt = syncpt_interrupts[syncpoint_id];
+ auto& interrupt = syncpt_interrupts.at(syncpoint_id);
bool contains = std::any_of(interrupt.begin(), interrupt.end(),
[value](u32 in_value) { return in_value == value; });
if (contains) {
return;
}
- syncpt_interrupts[syncpoint_id].emplace_back(value);
+ interrupt.emplace_back(value);
}
bool GPU::CancelSyncptInterrupt(const u32 syncpoint_id, const u32 value) {
std::lock_guard lock{sync_mutex};
- auto& interrupt = syncpt_interrupts[syncpoint_id];
+ auto& interrupt = syncpt_interrupts.at(syncpoint_id);
const auto iter =
std::find_if(interrupt.begin(), interrupt.end(),
[value](u32 interrupt_value) { return value == interrupt_value; });
@@ -153,7 +171,7 @@ void GPU::TickWork() {
const std::size_t size = request.size;
flush_requests.pop_front();
flush_request_mutex.unlock();
- renderer->Rasterizer().FlushRegion(addr, size);
+ rasterizer->FlushRegion(addr, size);
current_flush_fence.store(fence);
flush_request_mutex.lock();
}
@@ -175,41 +193,13 @@ u64 GPU::GetTicks() const {
}
void GPU::FlushCommands() {
- renderer->Rasterizer().FlushCommands();
+ rasterizer->FlushCommands();
}
void GPU::SyncGuestHost() {
- renderer->Rasterizer().SyncGuestHost();
+ rasterizer->SyncGuestHost();
}
-void GPU::OnCommandListEnd() {
- renderer->Rasterizer().ReleaseFences();
-}
-// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
-// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4.
-// So the values you see in docs might be multiplied by 4.
-enum class BufferMethods {
- BindObject = 0x0,
- Nop = 0x2,
- SemaphoreAddressHigh = 0x4,
- SemaphoreAddressLow = 0x5,
- SemaphoreSequence = 0x6,
- SemaphoreTrigger = 0x7,
- NotifyIntr = 0x8,
- WrcacheFlush = 0x9,
- Unk28 = 0xA,
- UnkCacheFlush = 0xB,
- RefCnt = 0x14,
- SemaphoreAcquire = 0x1A,
- SemaphoreRelease = 0x1B,
- FenceValue = 0x1C,
- FenceAction = 0x1D,
- Unk78 = 0x1E,
- Unk7c = 0x1F,
- Yield = 0x20,
- NonPullerMethods = 0x40,
-};
-
enum class GpuSemaphoreOperation {
AcquireEqual = 0x1,
WriteLong = 0x2,
@@ -240,8 +230,12 @@ void GPU::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32
CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
} else {
for (std::size_t i = 0; i < amount; i++) {
- CallPullerMethod(
- {method, base_start[i], subchannel, methods_pending - static_cast<u32>(i)});
+ CallPullerMethod(MethodCall{
+ method,
+ base_start[i],
+ subchannel,
+ methods_pending - static_cast<u32>(i),
+ });
}
}
}
@@ -268,7 +262,12 @@ void GPU::CallPullerMethod(const MethodCall& method_call) {
case BufferMethods::UnkCacheFlush:
case BufferMethods::WrcacheFlush:
case BufferMethods::FenceValue:
+ break;
case BufferMethods::FenceAction:
+ ProcessFenceActionMethod();
+ break;
+ case BufferMethods::WaitForInterrupt:
+ ProcessWaitForInterruptMethod();
break;
case BufferMethods::SemaphoreTrigger: {
ProcessSemaphoreTriggerMethod();
@@ -298,8 +297,7 @@ void GPU::CallPullerMethod(const MethodCall& method_call) {
break;
}
default:
- LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented",
- static_cast<u32>(method));
+ LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method);
break;
}
}
@@ -378,10 +376,28 @@ void GPU::ProcessBindMethod(const MethodCall& method_call) {
dma_pusher->BindSubchannel(kepler_memory.get(), method_call.subchannel);
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", static_cast<u32>(engine_id));
+ UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
+ }
+}
+
+void GPU::ProcessFenceActionMethod() {
+ switch (regs.fence_action.op) {
+ case FenceOperation::Acquire:
+ WaitFence(regs.fence_action.syncpoint_id, regs.fence_value);
+ break;
+ case FenceOperation::Increment:
+ IncrementSyncPoint(regs.fence_action.syncpoint_id);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
}
}
+void GPU::ProcessWaitForInterruptMethod() {
+ // TODO(bunnei) ImplementMe
+ LOG_WARNING(HW_GPU, "(STUBBED) called");
+}
+
void GPU::ProcessSemaphoreTriggerMethod() {
const auto semaphoreOperationMask = 0xF;
const auto op =
@@ -443,4 +459,75 @@ void GPU::ProcessSemaphoreAcquire() {
}
}
+void GPU::Start() {
+ gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher, *cdma_pusher);
+ cpu_context = renderer->GetRenderWindow().CreateSharedContext();
+ cpu_context->MakeCurrent();
+}
+
+void GPU::ObtainContext() {
+ cpu_context->MakeCurrent();
+}
+
+void GPU::ReleaseContext() {
+ cpu_context->DoneCurrent();
+}
+
+void GPU::PushGPUEntries(Tegra::CommandList&& entries) {
+ gpu_thread.SubmitList(std::move(entries));
+}
+
+void GPU::PushCommandBuffer(Tegra::ChCommandHeaderList& entries) {
+ if (!use_nvdec) {
+ return;
+ }
+ // This condition fires when a video stream ends, clear all intermediary data
+ if (entries[0].raw == 0xDEADB33F) {
+ cdma_pusher.reset();
+ return;
+ }
+ if (!cdma_pusher) {
+ cdma_pusher = std::make_unique<Tegra::CDmaPusher>(*this);
+ }
+
+ // SubmitCommandBuffer would make the nvdec operations async, this is not currently working
+ // TODO(ameerj): RE proper async nvdec operation
+ // gpu_thread.SubmitCommandBuffer(std::move(entries));
+
+ cdma_pusher->Push(std::move(entries));
+ cdma_pusher->DispatchCalls();
+}
+
+void GPU::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
+ gpu_thread.SwapBuffers(framebuffer);
+}
+
+void GPU::FlushRegion(VAddr addr, u64 size) {
+ gpu_thread.FlushRegion(addr, size);
+}
+
+void GPU::InvalidateRegion(VAddr addr, u64 size) {
+ gpu_thread.InvalidateRegion(addr, size);
+}
+
+void GPU::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+ gpu_thread.FlushAndInvalidateRegion(addr, size);
+}
+
+void GPU::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const {
+ auto& interrupt_manager = system.InterruptManager();
+ interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
+}
+
+void GPU::WaitIdle() const {
+ gpu_thread.WaitIdle();
+}
+
+void GPU::OnCommandListEnd() {
+ if (is_async) {
+ // This command only applies to asynchronous GPU mode
+ gpu_thread.OnCommandListEnd();
+ }
+}
+
} // namespace Tegra
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index c7d11deb2..b2ee45496 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -13,14 +13,17 @@
#include "common/common_types.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
+#include "video_core/cdma_pusher.h"
#include "video_core/dma_pusher.h"
+#include "video_core/framebuffer_config.h"
+#include "video_core/gpu_thread.h"
using CacheAddr = std::uintptr_t;
-inline CacheAddr ToCacheAddr(const void* host_ptr) {
+[[nodiscard]] inline CacheAddr ToCacheAddr(const void* host_ptr) {
return reinterpret_cast<CacheAddr>(host_ptr);
}
-inline u8* FromCacheAddr(CacheAddr cache_addr) {
+[[nodiscard]] inline u8* FromCacheAddr(CacheAddr cache_addr) {
return reinterpret_cast<u8*>(cache_addr);
}
@@ -100,28 +103,6 @@ enum class DepthFormat : u32 {
struct CommandListHeader;
class DebugContext;
-/**
- * Struct describing framebuffer configuration
- */
-struct FramebufferConfig {
- enum class PixelFormat : u32 {
- A8B8G8R8_UNORM = 1,
- RGB565_UNORM = 4,
- B8G8R8A8_UNORM = 5,
- };
-
- VAddr address;
- u32 offset;
- u32 width;
- u32 height;
- u32 stride;
- PixelFormat pixel_format;
-
- using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
- TransformFlags transform_flags;
- Common::Rectangle<int> crop_rect;
-};
-
namespace Engines {
class Fermi2D;
class Maxwell3D;
@@ -140,7 +121,7 @@ enum class EngineID {
class MemoryManager;
-class GPU {
+class GPU final {
public:
struct MethodCall {
u32 method{};
@@ -148,17 +129,17 @@ public:
u32 subchannel{};
u32 method_count{};
- bool IsLastCall() const {
+ explicit MethodCall(u32 method_, u32 argument_, u32 subchannel_ = 0, u32 method_count_ = 0)
+ : method(method_), argument(argument_), subchannel(subchannel_),
+ method_count(method_count_) {}
+
+ [[nodiscard]] bool IsLastCall() const {
return method_count <= 1;
}
-
- MethodCall(u32 method, u32 argument, u32 subchannel = 0, u32 method_count = 0)
- : method(method), argument(argument), subchannel(subchannel),
- method_count(method_count) {}
};
- explicit GPU(Core::System& system, bool is_async);
- virtual ~GPU();
+ explicit GPU(Core::System& system_, bool is_async_, bool use_nvdec_);
+ ~GPU();
/// Binds a renderer to the GPU.
void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
@@ -175,13 +156,13 @@ public:
/// Synchronizes CPU writes with Host GPU memory.
void SyncGuestHost();
/// Signal the ending of command list.
- virtual void OnCommandListEnd();
+ void OnCommandListEnd();
/// Request a host GPU memory flush from the CPU.
- u64 RequestFlush(VAddr addr, std::size_t size);
+ [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size);
/// Obtains current flush request fence id.
- u64 CurrentFlushRequestFence() const {
+ [[nodiscard]] u64 CurrentFlushRequestFence() const {
return current_flush_fence.load(std::memory_order_relaxed);
}
@@ -189,80 +170,112 @@ public:
void TickWork();
/// Returns a reference to the Maxwell3D GPU engine.
- Engines::Maxwell3D& Maxwell3D();
+ [[nodiscard]] Engines::Maxwell3D& Maxwell3D();
/// Returns a const reference to the Maxwell3D GPU engine.
- const Engines::Maxwell3D& Maxwell3D() const;
+ [[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const;
/// Returns a reference to the KeplerCompute GPU engine.
- Engines::KeplerCompute& KeplerCompute();
+ [[nodiscard]] Engines::KeplerCompute& KeplerCompute();
/// Returns a reference to the KeplerCompute GPU engine.
- const Engines::KeplerCompute& KeplerCompute() const;
+ [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const;
/// Returns a reference to the GPU memory manager.
- Tegra::MemoryManager& MemoryManager();
+ [[nodiscard]] Tegra::MemoryManager& MemoryManager();
/// Returns a const reference to the GPU memory manager.
- const Tegra::MemoryManager& MemoryManager() const;
+ [[nodiscard]] const Tegra::MemoryManager& MemoryManager() const;
/// Returns a reference to the GPU DMA pusher.
- Tegra::DmaPusher& DmaPusher();
+ [[nodiscard]] Tegra::DmaPusher& DmaPusher();
+
+ /// Returns a const reference to the GPU DMA pusher.
+ [[nodiscard]] const Tegra::DmaPusher& DmaPusher() const;
- VideoCore::RendererBase& Renderer() {
+ /// Returns a reference to the GPU CDMA pusher.
+ [[nodiscard]] Tegra::CDmaPusher& CDmaPusher();
+
+ /// Returns a const reference to the GPU CDMA pusher.
+ [[nodiscard]] const Tegra::CDmaPusher& CDmaPusher() const;
+
+ /// Returns a reference to the underlying renderer.
+ [[nodiscard]] VideoCore::RendererBase& Renderer() {
return *renderer;
}
- const VideoCore::RendererBase& Renderer() const {
+ /// Returns a const reference to the underlying renderer.
+ [[nodiscard]] const VideoCore::RendererBase& Renderer() const {
return *renderer;
}
- VideoCore::ShaderNotify& ShaderNotify() {
+ /// Returns a reference to the shader notifier.
+ [[nodiscard]] VideoCore::ShaderNotify& ShaderNotify() {
return *shader_notify;
}
- const VideoCore::ShaderNotify& ShaderNotify() const {
+ /// Returns a const reference to the shader notifier.
+ [[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const {
return *shader_notify;
}
// Waits for the GPU to finish working
- virtual void WaitIdle() const = 0;
+ void WaitIdle() const;
/// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
void WaitFence(u32 syncpoint_id, u32 value);
void IncrementSyncPoint(u32 syncpoint_id);
- u32 GetSyncpointValue(u32 syncpoint_id) const;
+ [[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const;
void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value);
- bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
+ [[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
- u64 GetTicks() const;
+ [[nodiscard]] u64 GetTicks() const;
- std::unique_lock<std::mutex> LockSync() {
+ [[nodiscard]] std::unique_lock<std::mutex> LockSync() {
return std::unique_lock{sync_mutex};
}
- bool IsAsync() const {
+ [[nodiscard]] bool IsAsync() const {
return is_async;
}
- /// Returns a const reference to the GPU DMA pusher.
- const Tegra::DmaPusher& DmaPusher() const;
+ [[nodiscard]] bool UseNvdec() const {
+ return use_nvdec;
+ }
+
+ enum class FenceOperation : u32 {
+ Acquire = 0,
+ Increment = 1,
+ };
+
+ union FenceAction {
+ u32 raw;
+ BitField<0, 1, FenceOperation> op;
+ BitField<8, 24, u32> syncpoint_id;
+
+ [[nodiscard]] static CommandHeader Build(FenceOperation op, u32 syncpoint_id) {
+ FenceAction result{};
+ result.op.Assign(op);
+ result.syncpoint_id.Assign(syncpoint_id);
+ return {result.raw};
+ }
+ };
struct Regs {
static constexpr size_t NUM_REGS = 0x40;
union {
struct {
- INSERT_UNION_PADDING_WORDS(0x4);
+ INSERT_PADDING_WORDS_NOINIT(0x4);
struct {
u32 address_high;
u32 address_low;
- GPUVAddr SemaphoreAddress() const {
+ [[nodiscard]] GPUVAddr SemaphoreAddress() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
address_low);
}
@@ -270,21 +283,18 @@ public:
u32 semaphore_sequence;
u32 semaphore_trigger;
- INSERT_UNION_PADDING_WORDS(0xC);
+ INSERT_PADDING_WORDS_NOINIT(0xC);
// The pusher and the puller share the reference counter, the pusher only has read
// access
u32 reference_count;
- INSERT_UNION_PADDING_WORDS(0x5);
+ INSERT_PADDING_WORDS_NOINIT(0x5);
u32 semaphore_acquire;
u32 semaphore_release;
u32 fence_value;
- union {
- BitField<4, 4, u32> operation;
- BitField<8, 8, u32> id;
- } fence_action;
- INSERT_UNION_PADDING_WORDS(0xE2);
+ FenceAction fence_action;
+ INSERT_PADDING_WORDS_NOINIT(0xE2);
// Puller state
u32 acquire_mode;
@@ -300,34 +310,39 @@ public:
/// Performs any additional setup necessary in order to begin GPU emulation.
/// This can be used to launch any necessary threads and register any necessary
/// core timing events.
- virtual void Start() = 0;
+ void Start();
/// Obtain the CPU Context
- virtual void ObtainContext() = 0;
+ void ObtainContext();
/// Release the CPU Context
- virtual void ReleaseContext() = 0;
+ void ReleaseContext();
/// Push GPU command entries to be processed
- virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0;
+ void PushGPUEntries(Tegra::CommandList&& entries);
+
+ /// Push GPU command buffer entries to be processed
+ void PushCommandBuffer(Tegra::ChCommandHeaderList& entries);
/// Swap buffers (render frame)
- virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
+ void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
- virtual void FlushRegion(VAddr addr, u64 size) = 0;
+ void FlushRegion(VAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be invalidated
- virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
+ void InvalidateRegion(VAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
- virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
+ void FlushAndInvalidateRegion(VAddr addr, u64 size);
protected:
- virtual void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const = 0;
+ void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const;
private:
void ProcessBindMethod(const MethodCall& method_call);
+ void ProcessFenceActionMethod();
+ void ProcessWaitForInterruptMethod();
void ProcessSemaphoreTriggerMethod();
void ProcessSemaphoreRelease();
void ProcessSemaphoreAcquire();
@@ -343,16 +358,18 @@ private:
u32 methods_pending);
/// Determines where the method should be executed.
- bool ExecuteMethodOnEngine(u32 method);
+ [[nodiscard]] bool ExecuteMethodOnEngine(u32 method);
protected:
Core::System& system;
+ std::unique_ptr<Tegra::MemoryManager> memory_manager;
std::unique_ptr<Tegra::DmaPusher> dma_pusher;
+ std::unique_ptr<Tegra::CDmaPusher> cdma_pusher;
std::unique_ptr<VideoCore::RendererBase> renderer;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
+ const bool use_nvdec;
private:
- std::unique_ptr<Tegra::MemoryManager> memory_manager;
-
/// Mapping of command subchannels to their bound engine ids
std::array<EngineID, 8> bound_engines = {};
/// 3D engine
@@ -373,12 +390,13 @@ private:
std::array<std::list<u32>, Service::Nvidia::MaxSyncPoints> syncpt_interrupts;
std::mutex sync_mutex;
+ std::mutex device_mutex;
std::condition_variable sync_cv;
struct FlushRequest {
- FlushRequest(u64 fence, VAddr addr, std::size_t size)
- : fence{fence}, addr{addr}, size{size} {}
+ explicit FlushRequest(u64 fence_, VAddr addr_, std::size_t size_)
+ : fence{fence_}, addr{addr_}, size{size_} {}
u64 fence;
VAddr addr;
std::size_t size;
@@ -390,6 +408,9 @@ private:
std::mutex flush_request_mutex;
const bool is_async;
+
+ VideoCommon::GPUThread::ThreadManager gpu_thread;
+ std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
};
#define ASSERT_REG_POSITION(field_name, position) \
diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp
deleted file mode 100644
index 70a3d5738..000000000
--- a/src/video_core/gpu_asynch.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "core/core.h"
-#include "core/hardware_interrupt_manager.h"
-#include "video_core/gpu_asynch.h"
-#include "video_core/gpu_thread.h"
-#include "video_core/renderer_base.h"
-
-namespace VideoCommon {
-
-GPUAsynch::GPUAsynch(Core::System& system) : GPU{system, true}, gpu_thread{system} {}
-
-GPUAsynch::~GPUAsynch() = default;
-
-void GPUAsynch::Start() {
- gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher);
- cpu_context = renderer->GetRenderWindow().CreateSharedContext();
- cpu_context->MakeCurrent();
-}
-
-void GPUAsynch::ObtainContext() {
- cpu_context->MakeCurrent();
-}
-
-void GPUAsynch::ReleaseContext() {
- cpu_context->DoneCurrent();
-}
-
-void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) {
- gpu_thread.SubmitList(std::move(entries));
-}
-
-void GPUAsynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
- gpu_thread.SwapBuffers(framebuffer);
-}
-
-void GPUAsynch::FlushRegion(VAddr addr, u64 size) {
- gpu_thread.FlushRegion(addr, size);
-}
-
-void GPUAsynch::InvalidateRegion(VAddr addr, u64 size) {
- gpu_thread.InvalidateRegion(addr, size);
-}
-
-void GPUAsynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
- gpu_thread.FlushAndInvalidateRegion(addr, size);
-}
-
-void GPUAsynch::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const {
- auto& interrupt_manager = system.InterruptManager();
- interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
-}
-
-void GPUAsynch::WaitIdle() const {
- gpu_thread.WaitIdle();
-}
-
-void GPUAsynch::OnCommandListEnd() {
- gpu_thread.OnCommandListEnd();
-}
-
-} // namespace VideoCommon
diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h
deleted file mode 100644
index f89c855a5..000000000
--- a/src/video_core/gpu_asynch.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "video_core/gpu.h"
-#include "video_core/gpu_thread.h"
-
-namespace Core::Frontend {
-class GraphicsContext;
-}
-
-namespace VideoCore {
-class RendererBase;
-} // namespace VideoCore
-
-namespace VideoCommon {
-
-/// Implementation of GPU interface that runs the GPU asynchronously
-class GPUAsynch final : public Tegra::GPU {
-public:
- explicit GPUAsynch(Core::System& system);
- ~GPUAsynch() override;
-
- void Start() override;
- void ObtainContext() override;
- void ReleaseContext() override;
- void PushGPUEntries(Tegra::CommandList&& entries) override;
- void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
- void FlushRegion(VAddr addr, u64 size) override;
- void InvalidateRegion(VAddr addr, u64 size) override;
- void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
- void WaitIdle() const override;
-
- void OnCommandListEnd() override;
-
-protected:
- void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const override;
-
-private:
- GPUThread::ThreadManager gpu_thread;
- std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
-};
-
-} // namespace VideoCommon
diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp
deleted file mode 100644
index 1ca47ddef..000000000
--- a/src/video_core/gpu_synch.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "video_core/gpu_synch.h"
-#include "video_core/renderer_base.h"
-
-namespace VideoCommon {
-
-GPUSynch::GPUSynch(Core::System& system) : GPU{system, false} {}
-
-GPUSynch::~GPUSynch() = default;
-
-void GPUSynch::Start() {}
-
-void GPUSynch::ObtainContext() {
- renderer->Context().MakeCurrent();
-}
-
-void GPUSynch::ReleaseContext() {
- renderer->Context().DoneCurrent();
-}
-
-void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) {
- dma_pusher->Push(std::move(entries));
- dma_pusher->DispatchCalls();
-}
-
-void GPUSynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
- renderer->SwapBuffers(framebuffer);
-}
-
-void GPUSynch::FlushRegion(VAddr addr, u64 size) {
- renderer->Rasterizer().FlushRegion(addr, size);
-}
-
-void GPUSynch::InvalidateRegion(VAddr addr, u64 size) {
- renderer->Rasterizer().InvalidateRegion(addr, size);
-}
-
-void GPUSynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
- renderer->Rasterizer().FlushAndInvalidateRegion(addr, size);
-}
-
-} // namespace VideoCommon
diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h
deleted file mode 100644
index 297258cb1..000000000
--- a/src/video_core/gpu_synch.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "video_core/gpu.h"
-
-namespace Core::Frontend {
-class GraphicsContext;
-}
-
-namespace VideoCore {
-class RendererBase;
-} // namespace VideoCore
-
-namespace VideoCommon {
-
-/// Implementation of GPU interface that runs the GPU synchronously
-class GPUSynch final : public Tegra::GPU {
-public:
- explicit GPUSynch(Core::System& system);
- ~GPUSynch() override;
-
- void Start() override;
- void ObtainContext() override;
- void ReleaseContext() override;
- void PushGPUEntries(Tegra::CommandList&& entries) override;
- void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
- void FlushRegion(VAddr addr, u64 size) override;
- void InvalidateRegion(VAddr addr, u64 size) override;
- void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
- void WaitIdle() const override {}
-
-protected:
- void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id,
- [[maybe_unused]] u32 value) const override {}
-};
-
-} // namespace VideoCommon
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index bf761abf2..50319f1d5 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -4,6 +4,7 @@
#include "common/assert.h"
#include "common/microprofile.h"
+#include "common/scope_exit.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
@@ -18,9 +19,11 @@ namespace VideoCommon::GPUThread {
/// Runs the GPU thread
static void RunThread(Core::System& system, VideoCore::RendererBase& renderer,
Core::Frontend::GraphicsContext& context, Tegra::DmaPusher& dma_pusher,
- SynchState& state) {
+ SynchState& state, Tegra::CDmaPusher& cdma_pusher) {
std::string name = "yuzu:GPU";
MicroProfileOnThreadCreate(name.c_str());
+ SCOPE_EXIT({ MicroProfileOnThreadExit(); });
+
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
system.RegisterHostThread();
@@ -35,23 +38,28 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer,
}
auto current_context = context.Acquire();
+ VideoCore::RasterizerInterface* const rasterizer = renderer.ReadRasterizer();
CommandDataContainer next;
while (state.is_running) {
next = state.queue.PopWait();
- if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) {
+ if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
dma_pusher.Push(std::move(submit_list->entries));
dma_pusher.DispatchCalls();
- } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) {
+ } else if (auto* command_list = std::get_if<SubmitChCommandEntries>(&next.data)) {
+ // NVDEC
+ cdma_pusher.Push(std::move(command_list->entries));
+ cdma_pusher.DispatchCalls();
+ } else if (const auto* data = std::get_if<SwapBuffersCommand>(&next.data)) {
renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
} else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) {
- renderer.Rasterizer().ReleaseFences();
+ rasterizer->ReleaseFences();
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
system.GPU().TickWork();
- } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) {
- renderer.Rasterizer().FlushRegion(data->addr, data->size);
- } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) {
- renderer.Rasterizer().OnCPUWrite(data->addr, data->size);
+ } else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
+ rasterizer->FlushRegion(flush->addr, flush->size);
+ } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
+ rasterizer->OnCPUWrite(invalidate->addr, invalidate->size);
} else if (std::holds_alternative<EndProcessingCommand>(next.data)) {
return;
} else {
@@ -61,7 +69,8 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer,
}
}
-ThreadManager::ThreadManager(Core::System& system) : system{system} {}
+ThreadManager::ThreadManager(Core::System& system_, bool is_async_)
+ : system{system_}, is_async{is_async_} {}
ThreadManager::~ThreadManager() {
if (!thread.joinable()) {
@@ -75,47 +84,64 @@ ThreadManager::~ThreadManager() {
void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
Core::Frontend::GraphicsContext& context,
- Tegra::DmaPusher& dma_pusher) {
- thread = std::thread{RunThread, std::ref(system), std::ref(renderer),
- std::ref(context), std::ref(dma_pusher), std::ref(state)};
+ Tegra::DmaPusher& dma_pusher, Tegra::CDmaPusher& cdma_pusher) {
+ rasterizer = renderer.ReadRasterizer();
+ thread = std::thread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
+ std::ref(dma_pusher), std::ref(state), std::ref(cdma_pusher));
}
void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
PushCommand(SubmitListCommand(std::move(entries)));
}
+void ThreadManager::SubmitCommandBuffer(Tegra::ChCommandHeaderList&& entries) {
+ PushCommand(SubmitChCommandEntries(std::move(entries)));
+}
+
void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
PushCommand(SwapBuffersCommand(framebuffer ? std::make_optional(*framebuffer) : std::nullopt));
}
void ThreadManager::FlushRegion(VAddr addr, u64 size) {
- if (!Settings::IsGPULevelHigh()) {
+ if (!is_async) {
+ // Always flush with synchronous GPU mode
PushCommand(FlushRegionCommand(addr, size));
return;
}
- if (!Settings::IsGPULevelExtreme()) {
- return;
- }
- if (system.Renderer().Rasterizer().MustFlushRegion(addr, size)) {
+
+ // Asynchronous GPU mode
+ switch (Settings::values.gpu_accuracy.GetValue()) {
+ case Settings::GPUAccuracy::Normal:
+ PushCommand(FlushRegionCommand(addr, size));
+ break;
+ case Settings::GPUAccuracy::High:
+ // TODO(bunnei): Is this right? Preserving existing behavior for now
+ break;
+ case Settings::GPUAccuracy::Extreme: {
auto& gpu = system.GPU();
u64 fence = gpu.RequestFlush(addr, size);
PushCommand(GPUTickCommand());
while (fence > gpu.CurrentFlushRequestFence()) {
}
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unsupported gpu_accuracy {}", Settings::values.gpu_accuracy.GetValue());
}
}
void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
- system.Renderer().Rasterizer().OnCPUWrite(addr, size);
+ rasterizer->OnCPUWrite(addr, size);
}
void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
// Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important
- system.Renderer().Rasterizer().OnCPUWrite(addr, size);
+ rasterizer->OnCPUWrite(addr, size);
}
void ThreadManager::WaitIdle() const {
- while (state.last_fence > state.signaled_fence.load(std::memory_order_relaxed)) {
+ while (state.last_fence > state.signaled_fence.load(std::memory_order_relaxed) &&
+ system.IsPoweredOn()) {
}
}
@@ -126,6 +152,12 @@ void ThreadManager::OnCommandListEnd() {
u64 ThreadManager::PushCommand(CommandData&& command_data) {
const u64 fence{++state.last_fence};
state.queue.Push(CommandDataContainer(std::move(command_data), fence));
+
+ if (!is_async) {
+ // In synchronous GPU mode, block the caller until the command has executed
+ WaitIdle();
+ }
+
return fence;
}
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index 5a28335d6..4cd951169 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -10,8 +10,9 @@
#include <optional>
#include <thread>
#include <variant>
+
#include "common/threadsafe_queue.h"
-#include "video_core/gpu.h"
+#include "video_core/framebuffer_config.h"
namespace Tegra {
struct FramebufferConfig;
@@ -25,6 +26,11 @@ class GraphicsContext;
class System;
} // namespace Core
+namespace VideoCore {
+class RasterizerInterface;
+class RendererBase;
+} // namespace VideoCore
+
namespace VideoCommon::GPUThread {
/// Command to signal to the GPU thread that processing has ended
@@ -32,22 +38,30 @@ struct EndProcessingCommand final {};
/// Command to signal to the GPU thread that a command list is ready for processing
struct SubmitListCommand final {
- explicit SubmitListCommand(Tegra::CommandList&& entries) : entries{std::move(entries)} {}
+ explicit SubmitListCommand(Tegra::CommandList&& entries_) : entries{std::move(entries_)} {}
Tegra::CommandList entries;
};
+/// Command to signal to the GPU thread that a cdma command list is ready for processing
+struct SubmitChCommandEntries final {
+ explicit SubmitChCommandEntries(Tegra::ChCommandHeaderList&& entries_)
+ : entries{std::move(entries_)} {}
+
+ Tegra::ChCommandHeaderList entries;
+};
+
/// Command to signal to the GPU thread that a swap buffers is pending
struct SwapBuffersCommand final {
- explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer)
- : framebuffer{std::move(framebuffer)} {}
+ explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer_)
+ : framebuffer{std::move(framebuffer_)} {}
std::optional<Tegra::FramebufferConfig> framebuffer;
};
/// Command to signal to the GPU thread to flush a region
struct FlushRegionCommand final {
- explicit constexpr FlushRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {}
+ explicit constexpr FlushRegionCommand(VAddr addr_, u64 size_) : addr{addr_}, size{size_} {}
VAddr addr;
u64 size;
@@ -55,7 +69,7 @@ struct FlushRegionCommand final {
/// Command to signal to the GPU thread to invalidate a region
struct InvalidateRegionCommand final {
- explicit constexpr InvalidateRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {}
+ explicit constexpr InvalidateRegionCommand(VAddr addr_, u64 size_) : addr{addr_}, size{size_} {}
VAddr addr;
u64 size;
@@ -63,8 +77,8 @@ struct InvalidateRegionCommand final {
/// Command to signal to the GPU thread to flush and invalidate a region
struct FlushAndInvalidateRegionCommand final {
- explicit constexpr FlushAndInvalidateRegionCommand(VAddr addr, u64 size)
- : addr{addr}, size{size} {}
+ explicit constexpr FlushAndInvalidateRegionCommand(VAddr addr_, u64 size_)
+ : addr{addr_}, size{size_} {}
VAddr addr;
u64 size;
@@ -77,15 +91,15 @@ struct OnCommandListEndCommand final {};
struct GPUTickCommand final {};
using CommandData =
- std::variant<EndProcessingCommand, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand,
- InvalidateRegionCommand, FlushAndInvalidateRegionCommand, OnCommandListEndCommand,
- GPUTickCommand>;
+ std::variant<EndProcessingCommand, SubmitListCommand, SubmitChCommandEntries,
+ SwapBuffersCommand, FlushRegionCommand, InvalidateRegionCommand,
+ FlushAndInvalidateRegionCommand, OnCommandListEndCommand, GPUTickCommand>;
struct CommandDataContainer {
CommandDataContainer() = default;
- CommandDataContainer(CommandData&& data, u64 next_fence)
- : data{std::move(data)}, fence{next_fence} {}
+ explicit CommandDataContainer(CommandData&& data_, u64 next_fence_)
+ : data{std::move(data_)}, fence{next_fence_} {}
CommandData data;
u64 fence{};
@@ -104,16 +118,19 @@ struct SynchState final {
/// Class used to manage the GPU thread
class ThreadManager final {
public:
- explicit ThreadManager(Core::System& system);
+ explicit ThreadManager(Core::System& system_, bool is_async_);
~ThreadManager();
/// Creates and starts the GPU thread.
void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
- Tegra::DmaPusher& dma_pusher);
+ Tegra::DmaPusher& dma_pusher, Tegra::CDmaPusher& cdma_pusher);
/// Push GPU command entries to be processed
void SubmitList(Tegra::CommandList&& entries);
+ /// Push GPU CDMA command buffer entries to be processed
+ void SubmitCommandBuffer(Tegra::ChCommandHeaderList&& entries);
+
/// Swap buffers (render frame)
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
@@ -135,11 +152,12 @@ private:
/// Pushes a command to be executed by the GPU thread
u64 PushCommand(CommandData&& command_data);
-private:
- SynchState state;
Core::System& system;
+ const bool is_async;
+ VideoCore::RasterizerInterface* rasterizer = nullptr;
+
+ SynchState state;
std::thread thread;
- std::thread::id thread_id;
};
} // namespace VideoCommon::GPUThread
diff --git a/src/video_core/guest_driver.h b/src/video_core/guest_driver.h
index 99450777e..21e569ba1 100644
--- a/src/video_core/guest_driver.h
+++ b/src/video_core/guest_driver.h
@@ -19,8 +19,8 @@ namespace VideoCore {
class GuestDriverProfile {
public:
explicit GuestDriverProfile() = default;
- explicit GuestDriverProfile(std::optional<u32> texture_handler_size)
- : texture_handler_size{texture_handler_size} {}
+ explicit GuestDriverProfile(std::optional<u32> texture_handler_size_)
+ : texture_handler_size{texture_handler_size_} {}
void DeduceTextureHandlerSize(std::vector<u32> bound_offsets);
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index aa62363a7..970120acc 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -1,38 +1,83 @@
set(SHADER_FILES
+ block_linear_unswizzle_2d.comp
+ block_linear_unswizzle_3d.comp
+ convert_depth_to_float.frag
+ convert_float_to_depth.frag
+ full_screen_triangle.vert
+ opengl_copy_bc4.comp
opengl_present.frag
opengl_present.vert
+ pitch_unswizzle.comp
+ vulkan_blit_color_float.frag
+ vulkan_blit_depth_stencil.frag
+ vulkan_present.frag
+ vulkan_present.vert
+ vulkan_quad_indexed.comp
+ vulkan_uint8.comp
)
+find_program(GLSLANGVALIDATOR "glslangValidator" REQUIRED)
+
+set(GLSL_FLAGS "")
+set(QUIET_FLAG "--quiet")
+
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
+set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
-set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
-add_custom_command(
- OUTPUT
- ${SHADER_DIR}
+set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
+set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
+
+# Check if `--quiet` is available on host's glslangValidator version
+# glslangValidator prints to STDERR iff an unrecognized flag is passed to it
+execute_process(
COMMAND
- ${CMAKE_COMMAND} -E make_directory ${SHADER_DIR}
+ ${GLSLANGVALIDATOR} ${QUIET_FLAG}
+ ERROR_VARIABLE
+ GLSLANG_ERROR
+ # STDOUT variable defined to silence unnecessary output during CMake configuration
+ OUTPUT_VARIABLE
+ GLSLANG_OUTPUT
)
-set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
-set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
+if (NOT GLSLANG_ERROR STREQUAL "")
+ message(WARNING "Refusing to use unavailable flag `${QUIET_FLAG}` on `${GLSLANGVALIDATOR}`")
+ set(QUIET_FLAG "")
+endif()
foreach(FILENAME IN ITEMS ${SHADER_FILES})
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
- set(HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
- add_custom_command(
- OUTPUT
- ${HEADER_FILE}
- COMMAND
- ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${HEADER_FILE} ${INPUT_FILE}
- MAIN_DEPENDENCY
- ${SOURCE_FILE}
- DEPENDS
- ${HEADER_GENERATOR}
- ${INPUT_FILE}
- )
- set(SHADER_HEADERS ${SHADER_HEADERS} ${HEADER_FILE})
+ # Skip generating source headers on Vulkan exclusive files
+ if (NOT ${FILENAME} MATCHES "vulkan.*")
+ set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
+ add_custom_command(
+ OUTPUT
+ ${SOURCE_HEADER_FILE}
+ COMMAND
+ ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE}
+ MAIN_DEPENDENCY
+ ${SOURCE_FILE}
+ DEPENDS
+ ${INPUT_FILE}
+ # HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified
+ )
+ set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE})
+ endif()
+ # Skip compiling to SPIR-V OpenGL exclusive files
+ if (NOT ${FILENAME} MATCHES "opengl.*")
+ string(TOUPPER ${SHADER_NAME}_SPV SPIRV_VARIABLE_NAME)
+ set(SPIRV_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}_spv.h)
+ add_custom_command(
+ OUTPUT
+ ${SPIRV_HEADER_FILE}
+ COMMAND
+ ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} ${GLSL_FLAGS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE}
+ MAIN_DEPENDENCY
+ ${SOURCE_FILE}
+ )
+ set(SHADER_HEADERS ${SHADER_HEADERS} ${SPIRV_HEADER_FILE})
+ endif()
endforeach()
add_custom_target(host_shaders
diff --git a/src/video_core/host_shaders/StringShaderHeader.cmake b/src/video_core/host_shaders/StringShaderHeader.cmake
index 368bce0ed..c0fc49768 100644
--- a/src/video_core/host_shaders/StringShaderHeader.cmake
+++ b/src/video_core/host_shaders/StringShaderHeader.cmake
@@ -8,4 +8,6 @@ string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME)
file(READ ${SOURCE_FILE} CONTENTS)
+get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
+make_directory(${OUTPUT_DIR})
configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY)
diff --git a/src/video_core/host_shaders/block_linear_unswizzle_2d.comp b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp
new file mode 100644
index 000000000..a131be79e
--- /dev/null
+++ b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp
@@ -0,0 +1,122 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 430
+
+#ifdef VULKAN
+
+#extension GL_EXT_shader_16bit_storage : require
+#extension GL_EXT_shader_8bit_storage : require
+#define HAS_EXTENDED_TYPES 1
+#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
+#define END_PUSH_CONSTANTS };
+#define UNIFORM(n)
+#define BINDING_SWIZZLE_BUFFER 0
+#define BINDING_INPUT_BUFFER 1
+#define BINDING_OUTPUT_IMAGE 2
+
+#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
+
+#extension GL_NV_gpu_shader5 : enable
+#ifdef GL_NV_gpu_shader5
+#define HAS_EXTENDED_TYPES 1
+#else
+#define HAS_EXTENDED_TYPES 0
+#endif
+#define BEGIN_PUSH_CONSTANTS
+#define END_PUSH_CONSTANTS
+#define UNIFORM(n) layout (location = n) uniform
+#define BINDING_SWIZZLE_BUFFER 0
+#define BINDING_INPUT_BUFFER 1
+#define BINDING_OUTPUT_IMAGE 0
+
+#endif
+
+BEGIN_PUSH_CONSTANTS
+UNIFORM(0) uvec3 origin;
+UNIFORM(1) ivec3 destination;
+UNIFORM(2) uint bytes_per_block_log2;
+UNIFORM(3) uint layer_stride;
+UNIFORM(4) uint block_size;
+UNIFORM(5) uint x_shift;
+UNIFORM(6) uint block_height;
+UNIFORM(7) uint block_height_mask;
+END_PUSH_CONSTANTS
+
+layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
+ uint swizzle_table[];
+};
+
+#if HAS_EXTENDED_TYPES
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
+#endif
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
+
+layout(binding = BINDING_OUTPUT_IMAGE) uniform writeonly uimage2DArray output_image;
+
+layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
+
+const uint GOB_SIZE_X = 64;
+const uint GOB_SIZE_Y = 8;
+const uint GOB_SIZE_Z = 1;
+const uint GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z;
+
+const uint GOB_SIZE_X_SHIFT = 6;
+const uint GOB_SIZE_Y_SHIFT = 3;
+const uint GOB_SIZE_Z_SHIFT = 0;
+const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
+
+const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1);
+
+uint SwizzleOffset(uvec2 pos) {
+ pos = pos & SWIZZLE_MASK;
+ return swizzle_table[pos.y * 64 + pos.x];
+}
+
+uvec4 ReadTexel(uint offset) {
+ switch (bytes_per_block_log2) {
+#if HAS_EXTENDED_TYPES
+ case 0:
+ return uvec4(u8data[offset], 0, 0, 0);
+ case 1:
+ return uvec4(u16data[offset / 2], 0, 0, 0);
+#else
+ case 0:
+ return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 24), 8), 0, 0, 0);
+ case 1:
+ return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 16), 16), 0, 0, 0);
+#endif
+ case 2:
+ return uvec4(u32data[offset / 4], 0, 0, 0);
+ case 3:
+ return uvec4(u64data[offset / 8], 0, 0);
+ case 4:
+ return u128data[offset / 16];
+ }
+ return uvec4(0);
+}
+
+void main() {
+ uvec3 pos = gl_GlobalInvocationID + origin;
+ pos.x <<= bytes_per_block_log2;
+
+ // Read as soon as possible due to its latency
+ const uint swizzle = SwizzleOffset(pos.xy);
+
+ const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
+
+ uint offset = 0;
+ offset += pos.z * layer_stride;
+ offset += (block_y >> block_height) * block_size;
+ offset += (block_y & block_height_mask) << GOB_SIZE_SHIFT;
+ offset += (pos.x >> GOB_SIZE_X_SHIFT) << x_shift;
+ offset += swizzle;
+
+ const uvec4 texel = ReadTexel(offset);
+ const ivec3 coord = ivec3(gl_GlobalInvocationID) + destination;
+ imageStore(output_image, coord, texel);
+}
diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp
new file mode 100644
index 000000000..bb6872e6b
--- /dev/null
+++ b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp
@@ -0,0 +1,125 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 430
+
+#ifdef VULKAN
+
+#extension GL_EXT_shader_16bit_storage : require
+#extension GL_EXT_shader_8bit_storage : require
+#define HAS_EXTENDED_TYPES 1
+#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
+#define END_PUSH_CONSTANTS };
+#define UNIFORM(n)
+#define BINDING_SWIZZLE_BUFFER 0
+#define BINDING_INPUT_BUFFER 1
+#define BINDING_OUTPUT_IMAGE 2
+
+#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
+
+#extension GL_NV_gpu_shader5 : enable
+#ifdef GL_NV_gpu_shader5
+#define HAS_EXTENDED_TYPES 1
+#else
+#define HAS_EXTENDED_TYPES 0
+#endif
+#define BEGIN_PUSH_CONSTANTS
+#define END_PUSH_CONSTANTS
+#define UNIFORM(n) layout (location = n) uniform
+#define BINDING_SWIZZLE_BUFFER 0
+#define BINDING_INPUT_BUFFER 1
+#define BINDING_OUTPUT_IMAGE 0
+
+#endif
+
+BEGIN_PUSH_CONSTANTS
+UNIFORM(0) uvec3 origin;
+UNIFORM(1) ivec3 destination;
+UNIFORM(2) uint bytes_per_block_log2;
+UNIFORM(3) uint slice_size;
+UNIFORM(4) uint block_size;
+UNIFORM(5) uint x_shift;
+UNIFORM(6) uint block_height;
+UNIFORM(7) uint block_height_mask;
+UNIFORM(8) uint block_depth;
+UNIFORM(9) uint block_depth_mask;
+END_PUSH_CONSTANTS
+
+layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable {
+ uint swizzle_table[];
+};
+
+#if HAS_EXTENDED_TYPES
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; };
+#endif
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
+
+layout(binding = BINDING_OUTPUT_IMAGE) uniform writeonly uimage3D output_image;
+
+layout(local_size_x = 16, local_size_y = 8, local_size_z = 8) in;
+
+const uint GOB_SIZE_X = 64;
+const uint GOB_SIZE_Y = 8;
+const uint GOB_SIZE_Z = 1;
+const uint GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z;
+
+const uint GOB_SIZE_X_SHIFT = 6;
+const uint GOB_SIZE_Y_SHIFT = 3;
+const uint GOB_SIZE_Z_SHIFT = 0;
+const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
+
+const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1);
+
+uint SwizzleOffset(uvec2 pos) {
+ pos = pos & SWIZZLE_MASK;
+ return swizzle_table[pos.y * 64 + pos.x];
+}
+
+uvec4 ReadTexel(uint offset) {
+ switch (bytes_per_block_log2) {
+#if HAS_EXTENDED_TYPES
+ case 0:
+ return uvec4(u8data[offset], 0, 0, 0);
+ case 1:
+ return uvec4(u16data[offset / 2], 0, 0, 0);
+#else
+ case 0:
+ return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 24), 8), 0, 0, 0);
+ case 1:
+ return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 16), 16), 0, 0, 0);
+#endif
+ case 2:
+ return uvec4(u32data[offset / 4], 0, 0, 0);
+ case 3:
+ return uvec4(u64data[offset / 8], 0, 0);
+ case 4:
+ return u128data[offset / 16];
+ }
+ return uvec4(0);
+}
+
+void main() {
+ uvec3 pos = gl_GlobalInvocationID + origin;
+ pos.x <<= bytes_per_block_log2;
+
+ // Read as soon as possible due to its latency
+ const uint swizzle = SwizzleOffset(pos.xy);
+
+ const uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
+
+ uint offset = 0;
+ offset += (pos.z >> block_depth) * slice_size;
+ offset += (pos.z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height);
+ offset += (block_y >> block_height) * block_size;
+ offset += (block_y & block_height_mask) << GOB_SIZE_SHIFT;
+ offset += (pos.x >> GOB_SIZE_X_SHIFT) << x_shift;
+ offset += swizzle;
+
+ const uvec4 texel = ReadTexel(offset);
+ const ivec3 coord = ivec3(gl_GlobalInvocationID) + destination;
+ imageStore(output_image, coord, texel);
+}
diff --git a/src/video_core/host_shaders/convert_depth_to_float.frag b/src/video_core/host_shaders/convert_depth_to_float.frag
new file mode 100644
index 000000000..624c58509
--- /dev/null
+++ b/src/video_core/host_shaders/convert_depth_to_float.frag
@@ -0,0 +1,13 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 450
+
+layout(binding = 0) uniform sampler2D depth_texture;
+layout(location = 0) out float output_color;
+
+void main() {
+ ivec2 coord = ivec2(gl_FragCoord.xy);
+ output_color = texelFetch(depth_texture, coord, 0).r;
+}
diff --git a/src/video_core/host_shaders/convert_float_to_depth.frag b/src/video_core/host_shaders/convert_float_to_depth.frag
new file mode 100644
index 000000000..d86c795f4
--- /dev/null
+++ b/src/video_core/host_shaders/convert_float_to_depth.frag
@@ -0,0 +1,13 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 450
+
+layout(binding = 0) uniform sampler2D color_texture;
+
+void main() {
+ ivec2 coord = ivec2(gl_FragCoord.xy);
+ float color = texelFetch(color_texture, coord, 0).r;
+ gl_FragDepth = color;
+}
diff --git a/src/video_core/host_shaders/full_screen_triangle.vert b/src/video_core/host_shaders/full_screen_triangle.vert
new file mode 100644
index 000000000..452ad6502
--- /dev/null
+++ b/src/video_core/host_shaders/full_screen_triangle.vert
@@ -0,0 +1,29 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 450
+
+#ifdef VULKAN
+#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
+#define END_PUSH_CONSTANTS };
+#define UNIFORM(n)
+#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
+#define BEGIN_PUSH_CONSTANTS
+#define END_PUSH_CONSTANTS
+#define UNIFORM(n) layout (location = n) uniform
+#endif
+
+BEGIN_PUSH_CONSTANTS
+UNIFORM(0) vec2 tex_scale;
+UNIFORM(1) vec2 tex_offset;
+END_PUSH_CONSTANTS
+
+layout(location = 0) out vec2 texcoord;
+
+void main() {
+ float x = float((gl_VertexIndex & 1) << 2);
+ float y = float((gl_VertexIndex & 2) << 1);
+ gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0);
+ texcoord = fma(vec2(x, y) / 2.0, tex_scale, tex_offset);
+}
diff --git a/src/video_core/host_shaders/opengl_copy_bc4.comp b/src/video_core/host_shaders/opengl_copy_bc4.comp
new file mode 100644
index 000000000..7b8e20fbe
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_copy_bc4.comp
@@ -0,0 +1,70 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 430 core
+#extension GL_ARB_gpu_shader_int64 : require
+
+layout (local_size_x = 4, local_size_y = 4) in;
+
+layout(binding = 0, rg32ui) readonly uniform uimage3D bc4_input;
+layout(binding = 1, rgba8ui) writeonly uniform uimage3D bc4_output;
+
+layout(location = 0) uniform uvec3 src_offset;
+layout(location = 1) uniform uvec3 dst_offset;
+
+// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_rgtc.txt
+uint DecompressBlock(uint64_t bits, uvec2 coord) {
+ const uint code_offset = 16 + 3 * (4 * coord.y + coord.x);
+ const uint code = uint(bits >> code_offset) & 7;
+ const uint red0 = uint(bits >> 0) & 0xff;
+ const uint red1 = uint(bits >> 8) & 0xff;
+ if (red0 > red1) {
+ switch (code) {
+ case 0:
+ return red0;
+ case 1:
+ return red1;
+ case 2:
+ return (6 * red0 + 1 * red1) / 7;
+ case 3:
+ return (5 * red0 + 2 * red1) / 7;
+ case 4:
+ return (4 * red0 + 3 * red1) / 7;
+ case 5:
+ return (3 * red0 + 4 * red1) / 7;
+ case 6:
+ return (2 * red0 + 5 * red1) / 7;
+ case 7:
+ return (1 * red0 + 6 * red1) / 7;
+ }
+ } else {
+ switch (code) {
+ case 0:
+ return red0;
+ case 1:
+ return red1;
+ case 2:
+ return (4 * red0 + 1 * red1) / 5;
+ case 3:
+ return (3 * red0 + 2 * red1) / 5;
+ case 4:
+ return (2 * red0 + 3 * red1) / 5;
+ case 5:
+ return (1 * red0 + 4 * red1) / 5;
+ case 6:
+ return 0;
+ case 7:
+ return 0xff;
+ }
+ }
+ return 0;
+}
+
+void main() {
+ uvec2 packed_bits = imageLoad(bc4_input, ivec3(gl_WorkGroupID + src_offset)).rg;
+ uint64_t bits = packUint2x32(packed_bits);
+ uint red = DecompressBlock(bits, gl_LocalInvocationID.xy);
+ uvec4 color = uvec4(red & 0xff, 0, 0, 0xff);
+ imageStore(bc4_output, ivec3(gl_GlobalInvocationID + dst_offset), color);
+}
diff --git a/src/video_core/host_shaders/opengl_present.frag b/src/video_core/host_shaders/opengl_present.frag
index 8a4cb024b..84b818227 100644
--- a/src/video_core/host_shaders/opengl_present.frag
+++ b/src/video_core/host_shaders/opengl_present.frag
@@ -1,3 +1,7 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
#version 430 core
layout (location = 0) in vec2 frag_tex_coord;
diff --git a/src/video_core/host_shaders/opengl_present.vert b/src/video_core/host_shaders/opengl_present.vert
index 2235d31a4..c3b5adbba 100644
--- a/src/video_core/host_shaders/opengl_present.vert
+++ b/src/video_core/host_shaders/opengl_present.vert
@@ -1,3 +1,7 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
#version 430 core
out gl_PerVertex {
diff --git a/src/video_core/host_shaders/pitch_unswizzle.comp b/src/video_core/host_shaders/pitch_unswizzle.comp
new file mode 100644
index 000000000..cb48ec170
--- /dev/null
+++ b/src/video_core/host_shaders/pitch_unswizzle.comp
@@ -0,0 +1,86 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 430
+
+#ifdef VULKAN
+
+#extension GL_EXT_shader_16bit_storage : require
+#extension GL_EXT_shader_8bit_storage : require
+#define HAS_EXTENDED_TYPES 1
+#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
+#define END_PUSH_CONSTANTS };
+#define UNIFORM(n)
+#define BINDING_INPUT_BUFFER 0
+#define BINDING_OUTPUT_IMAGE 1
+
+#else // ^^^ Vulkan ^^^ // vvv OpenGL vvv
+
+#extension GL_NV_gpu_shader5 : enable
+#ifdef GL_NV_gpu_shader5
+#define HAS_EXTENDED_TYPES 1
+#else
+#define HAS_EXTENDED_TYPES 0
+#endif
+#define BEGIN_PUSH_CONSTANTS
+#define END_PUSH_CONSTANTS
+#define UNIFORM(n) layout (location = n) uniform
+#define BINDING_INPUT_BUFFER 0
+#define BINDING_OUTPUT_IMAGE 0
+
+#endif
+
+BEGIN_PUSH_CONSTANTS
+UNIFORM(0) uvec2 origin;
+UNIFORM(1) ivec2 destination;
+UNIFORM(2) uint bytes_per_block;
+UNIFORM(3) uint pitch;
+END_PUSH_CONSTANTS
+
+#if HAS_EXTENDED_TYPES
+layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU8 { uint8_t u8data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU16 { uint16_t u16data[]; };
+#endif
+layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU32 { uint u32data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU64 { uvec2 u64data[]; };
+layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBufferU128 { uvec4 u128data[]; };
+
+layout(binding = BINDING_OUTPUT_IMAGE) writeonly uniform uimage2D output_image;
+
+layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
+
+uvec4 ReadTexel(uint offset) {
+ switch (bytes_per_block) {
+#if HAS_EXTENDED_TYPES
+ case 1:
+ return uvec4(u8data[offset], 0, 0, 0);
+ case 2:
+ return uvec4(u16data[offset / 2], 0, 0, 0);
+#else
+ case 1:
+ return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 24), 8), 0, 0, 0);
+ case 2:
+ return uvec4(bitfieldExtract(u32data[offset / 4], int((offset * 8) & 16), 16), 0, 0, 0);
+#endif
+ case 4:
+ return uvec4(u32data[offset / 4], 0, 0, 0);
+ case 8:
+ return uvec4(u64data[offset / 8], 0, 0);
+ case 16:
+ return u128data[offset / 16];
+ }
+ return uvec4(0);
+}
+
+void main() {
+ uvec2 pos = gl_GlobalInvocationID.xy + origin;
+
+ uint offset = 0;
+ offset += pos.x * bytes_per_block;
+ offset += pos.y * pitch;
+
+ const uvec4 texel = ReadTexel(offset);
+ const ivec2 coord = ivec2(gl_GlobalInvocationID.xy) + destination;
+ imageStore(output_image, coord, texel);
+}
diff --git a/src/video_core/host_shaders/vulkan_blit_color_float.frag b/src/video_core/host_shaders/vulkan_blit_color_float.frag
new file mode 100644
index 000000000..4a6aae410
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_blit_color_float.frag
@@ -0,0 +1,14 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 450
+
+layout(binding = 0) uniform sampler2D tex;
+
+layout(location = 0) in vec2 texcoord;
+layout(location = 0) out vec4 color;
+
+void main() {
+ color = textureLod(tex, texcoord, 0);
+}
diff --git a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag
new file mode 100644
index 000000000..19bb23a5a
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag
@@ -0,0 +1,16 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 450
+#extension GL_ARB_shader_stencil_export : require
+
+layout(binding = 0) uniform sampler2D depth_tex;
+layout(binding = 1) uniform isampler2D stencil_tex;
+
+layout(location = 0) in vec2 texcoord;
+
+void main() {
+ gl_FragDepth = textureLod(depth_tex, texcoord, 0).r;
+ gl_FragStencilRefARB = textureLod(stencil_tex, texcoord, 0).r;
+}
diff --git a/src/video_core/host_shaders/vulkan_present.frag b/src/video_core/host_shaders/vulkan_present.frag
new file mode 100644
index 000000000..0979ff3e6
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_present.frag
@@ -0,0 +1,15 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 460 core
+
+layout (location = 0) in vec2 frag_tex_coord;
+
+layout (location = 0) out vec4 color;
+
+layout (binding = 1) uniform sampler2D color_texture;
+
+void main() {
+ color = texture(color_texture, frag_tex_coord);
+}
diff --git a/src/video_core/host_shaders/vulkan_present.vert b/src/video_core/host_shaders/vulkan_present.vert
new file mode 100644
index 000000000..00b868958
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_present.vert
@@ -0,0 +1,19 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 460 core
+
+layout (location = 0) in vec2 vert_position;
+layout (location = 1) in vec2 vert_tex_coord;
+
+layout (location = 0) out vec2 frag_tex_coord;
+
+layout (set = 0, binding = 0) uniform MatrixBlock {
+ mat4 modelview_matrix;
+};
+
+void main() {
+ gl_Position = modelview_matrix * vec4(vert_position, 0.0, 1.0);
+ frag_tex_coord = vert_tex_coord;
+}
diff --git a/src/video_core/host_shaders/vulkan_quad_indexed.comp b/src/video_core/host_shaders/vulkan_quad_indexed.comp
new file mode 100644
index 000000000..8655591d0
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_quad_indexed.comp
@@ -0,0 +1,41 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 460 core
+
+layout (local_size_x = 1024) in;
+
+layout (std430, set = 0, binding = 0) readonly buffer InputBuffer {
+ uint input_indexes[];
+};
+
+layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
+ uint output_indexes[];
+};
+
+layout (push_constant) uniform PushConstants {
+ uint base_vertex;
+ int index_shift; // 0: uint8, 1: uint16, 2: uint32
+};
+
+void main() {
+ int primitive = int(gl_GlobalInvocationID.x);
+ if (primitive * 6 >= output_indexes.length()) {
+ return;
+ }
+
+ int index_size = 8 << index_shift;
+ int flipped_shift = 2 - index_shift;
+ int mask = (1 << flipped_shift) - 1;
+
+ const int quad_swizzle[6] = int[](0, 1, 2, 0, 2, 3);
+ for (uint vertex = 0; vertex < 6; ++vertex) {
+ int offset = primitive * 4 + quad_swizzle[vertex];
+ int int_offset = offset >> flipped_shift;
+ int bit_offset = (offset & mask) * index_size;
+ uint packed_input = input_indexes[int_offset];
+ uint index = bitfieldExtract(packed_input, bit_offset, index_size);
+ output_indexes[primitive * 6 + vertex] = index + base_vertex;
+ }
+}
diff --git a/src/video_core/host_shaders/vulkan_uint8.comp b/src/video_core/host_shaders/vulkan_uint8.comp
new file mode 100644
index 000000000..872291670
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_uint8.comp
@@ -0,0 +1,31 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#version 460 core
+#extension GL_EXT_shader_16bit_storage : require
+#extension GL_EXT_shader_8bit_storage : require
+
+layout (local_size_x = 1024) in;
+
+layout (std430, set = 0, binding = 0) readonly buffer InputBuffer {
+ uint8_t input_indexes[];
+};
+
+layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
+ uint16_t output_indexes[];
+};
+
+uint AssembleIndex(uint id) {
+ // Most primitive restart indices are 0xFF
+ // Hardcode this to 0xFF for now
+ uint index = uint(input_indexes[id]);
+ return index == 0xFF ? 0xFFFF : index;
+}
+
+void main() {
+ uint id = gl_GlobalInvocationID.x;
+ if (id < input_indexes.length()) {
+ output_indexes[id] = uint16_t(AssembleIndex(id));
+ }
+}
diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp
index a50e7b4e0..cd21a2112 100644
--- a/src/video_core/macro/macro.cpp
+++ b/src/video_core/macro/macro.cpp
@@ -36,7 +36,7 @@ void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method,
}
} else {
// Macro not compiled, check if it's uploaded and if so, compile it
- std::optional<u32> mid_method = std::nullopt;
+ std::optional<u32> mid_method;
const auto macro_code = uploaded_macro_code.find(method);
if (macro_code == uploaded_macro_code.end()) {
for (const auto& [method_base, code] : uploaded_macro_code) {
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index df00b57df..70ac7c620 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -85,7 +85,7 @@ constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{
{0x0217920100488FF7, &HLE_0217920100488FF7},
}};
-HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
+HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {}
HLEMacro::~HLEMacro() = default;
std::optional<std::unique_ptr<CachedMacro>> HLEMacro::GetHLEProgram(u64 hash) const {
@@ -99,8 +99,8 @@ std::optional<std::unique_ptr<CachedMacro>> HLEMacro::GetHLEProgram(u64 hash) co
HLEMacroImpl::~HLEMacroImpl() = default;
-HLEMacroImpl::HLEMacroImpl(Engines::Maxwell3D& maxwell3d, HLEFunction func)
- : maxwell3d(maxwell3d), func(func) {}
+HLEMacroImpl::HLEMacroImpl(Engines::Maxwell3D& maxwell3d_, HLEFunction func_)
+ : maxwell3d{maxwell3d_}, func{func_} {}
void HLEMacroImpl::Execute(const std::vector<u32>& parameters, u32 method) {
func(maxwell3d, parameters);
diff --git a/src/video_core/macro/macro_hle.h b/src/video_core/macro/macro_hle.h
index 37af875a0..cb3bd1600 100644
--- a/src/video_core/macro/macro_hle.h
+++ b/src/video_core/macro/macro_hle.h
@@ -20,7 +20,7 @@ using HLEFunction = void (*)(Engines::Maxwell3D& maxwell3d, const std::vector<u3
class HLEMacro {
public:
- explicit HLEMacro(Engines::Maxwell3D& maxwell3d);
+ explicit HLEMacro(Engines::Maxwell3D& maxwell3d_);
~HLEMacro();
std::optional<std::unique_ptr<CachedMacro>> GetHLEProgram(u64 hash) const;
diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp
index bd01fd1f2..8da26fd59 100644
--- a/src/video_core/macro/macro_interpreter.cpp
+++ b/src/video_core/macro/macro_interpreter.cpp
@@ -11,29 +11,29 @@
MICROPROFILE_DEFINE(MacroInterp, "GPU", "Execute macro interpreter", MP_RGB(128, 128, 192));
namespace Tegra {
-MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d)
- : MacroEngine::MacroEngine(maxwell3d), maxwell3d(maxwell3d) {}
+MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d_)
+ : MacroEngine{maxwell3d_}, maxwell3d{maxwell3d_} {}
std::unique_ptr<CachedMacro> MacroInterpreter::Compile(const std::vector<u32>& code) {
return std::make_unique<MacroInterpreterImpl>(maxwell3d, code);
}
-MacroInterpreterImpl::MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d,
- const std::vector<u32>& code)
- : maxwell3d(maxwell3d), code(code) {}
+MacroInterpreterImpl::MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_,
+ const std::vector<u32>& code_)
+ : maxwell3d{maxwell3d_}, code{code_} {}
-void MacroInterpreterImpl::Execute(const std::vector<u32>& parameters, u32 method) {
+void MacroInterpreterImpl::Execute(const std::vector<u32>& params, u32 method) {
MICROPROFILE_SCOPE(MacroInterp);
Reset();
- registers[1] = parameters[0];
- num_parameters = parameters.size();
+ registers[1] = params[0];
+ num_parameters = params.size();
if (num_parameters > parameters_capacity) {
parameters_capacity = num_parameters;
- this->parameters = std::make_unique<u32[]>(num_parameters);
+ parameters = std::make_unique<u32[]>(num_parameters);
}
- std::memcpy(this->parameters.get(), parameters.data(), num_parameters * sizeof(u32));
+ std::memcpy(parameters.get(), params.data(), num_parameters * sizeof(u32));
// Execute the code until we hit an exit condition.
bool keep_executing = true;
@@ -133,8 +133,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {
break;
}
default:
- UNIMPLEMENTED_MSG("Unimplemented macro operation {}",
- static_cast<u32>(opcode.operation.Value()));
+ UNIMPLEMENTED_MSG("Unimplemented macro operation {}", opcode.operation.Value());
}
// An instruction with the Exit flag will not actually
@@ -182,7 +181,7 @@ u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a,
return ~(src_a & src_b);
default:
- UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation));
+ UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", operation);
return 0;
}
}
@@ -230,7 +229,7 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r
Send((result >> 12) & 0b111111);
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented result operation {}", static_cast<u32>(operation));
+ UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation);
}
}
diff --git a/src/video_core/macro/macro_interpreter.h b/src/video_core/macro/macro_interpreter.h
index 90217fc89..d50c619ce 100644
--- a/src/video_core/macro/macro_interpreter.h
+++ b/src/video_core/macro/macro_interpreter.h
@@ -17,7 +17,7 @@ class Maxwell3D;
class MacroInterpreter final : public MacroEngine {
public:
- explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d);
+ explicit MacroInterpreter(Engines::Maxwell3D& maxwell3d_);
protected:
std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code) override;
@@ -28,8 +28,8 @@ private:
class MacroInterpreterImpl : public CachedMacro {
public:
- MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code);
- void Execute(const std::vector<u32>& parameters, u32 method) override;
+ explicit MacroInterpreterImpl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_);
+ void Execute(const std::vector<u32>& params, u32 method) override;
private:
/// Resets the execution engine state, zeroing registers, etc.
@@ -38,9 +38,9 @@ private:
/**
* Executes a single macro instruction located at the current program counter. Returns whether
* the interpreter should keep running.
- * @param offset Offset to start execution at.
+ *
* @param is_delay_slot Whether the current step is being executed due to a delay slot in a
- * previous instruction.
+ * previous instruction.
*/
bool Step(bool is_delay_slot);
diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp
index c1b9e4ad9..c6b2b2109 100644
--- a/src/video_core/macro/macro_jit_x64.cpp
+++ b/src/video_core/macro/macro_jit_x64.cpp
@@ -14,11 +14,11 @@ MICROPROFILE_DEFINE(MacroJitCompile, "GPU", "Compile macro JIT", MP_RGB(173, 255
MICROPROFILE_DEFINE(MacroJitExecute, "GPU", "Execute macro JIT", MP_RGB(255, 255, 0));
namespace Tegra {
-static const Xbyak::Reg64 STATE = Xbyak::util::rbx;
-static const Xbyak::Reg32 RESULT = Xbyak::util::ebp;
-static const Xbyak::Reg64 PARAMETERS = Xbyak::util::r12;
-static const Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d;
-static const Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15;
+constexpr Xbyak::Reg64 STATE = Xbyak::util::rbx;
+constexpr Xbyak::Reg32 RESULT = Xbyak::util::ebp;
+constexpr Xbyak::Reg64 PARAMETERS = Xbyak::util::r12;
+constexpr Xbyak::Reg32 METHOD_ADDRESS = Xbyak::util::r14d;
+constexpr Xbyak::Reg64 BRANCH_HOLDER = Xbyak::util::r15;
static const std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({
STATE,
@@ -28,15 +28,15 @@ static const std::bitset<32> PERSISTENT_REGISTERS = Common::X64::BuildRegSet({
BRANCH_HOLDER,
});
-MacroJITx64::MacroJITx64(Engines::Maxwell3D& maxwell3d)
- : MacroEngine::MacroEngine(maxwell3d), maxwell3d(maxwell3d) {}
+MacroJITx64::MacroJITx64(Engines::Maxwell3D& maxwell3d_)
+ : MacroEngine{maxwell3d_}, maxwell3d{maxwell3d_} {}
std::unique_ptr<CachedMacro> MacroJITx64::Compile(const std::vector<u32>& code) {
return std::make_unique<MacroJITx64Impl>(maxwell3d, code);
}
-MacroJITx64Impl::MacroJITx64Impl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code)
- : Xbyak::CodeGenerator(MAX_CODE_SIZE), code(code), maxwell3d(maxwell3d) {
+MacroJITx64Impl::MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_)
+ : CodeGenerator{MAX_CODE_SIZE}, code{code_}, maxwell3d{maxwell3d_} {
Compile();
}
@@ -165,8 +165,7 @@ void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) {
}
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented ALU operation {}",
- static_cast<std::size_t>(opcode.alu_operation.Value()));
+ UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", opcode.alu_operation.Value());
break;
}
Compile_ProcessResult(opcode.result_operation, opcode.dst);
@@ -553,15 +552,15 @@ Xbyak::Reg32 MacroJITx64Impl::Compile_GetRegister(u32 index, Xbyak::Reg32 dst) {
}
void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u32 reg) {
- const auto SetRegister = [this](u32 reg, const Xbyak::Reg32& result) {
+ const auto SetRegister = [this](u32 reg_index, const Xbyak::Reg32& result) {
// Register 0 is supposed to always return 0. NOP is implemented as a store to the zero
// register.
- if (reg == 0) {
+ if (reg_index == 0) {
return;
}
- mov(dword[STATE + offsetof(JITState, registers) + reg * sizeof(u32)], result);
+ mov(dword[STATE + offsetof(JITState, registers) + reg_index * sizeof(u32)], result);
};
- const auto SetMethodAddress = [this](const Xbyak::Reg32& reg) { mov(METHOD_ADDRESS, reg); };
+ const auto SetMethodAddress = [this](const Xbyak::Reg32& reg32) { mov(METHOD_ADDRESS, reg32); };
switch (operation) {
case Macro::ResultOperation::IgnoreAndFetch:
@@ -604,7 +603,7 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3
Compile_Send(RESULT);
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented macro operation {}", static_cast<std::size_t>(operation));
+ UNIMPLEMENTED_MSG("Unimplemented macro operation {}", operation);
}
}
diff --git a/src/video_core/macro/macro_jit_x64.h b/src/video_core/macro/macro_jit_x64.h
index a180e7428..7f50ac2f8 100644
--- a/src/video_core/macro/macro_jit_x64.h
+++ b/src/video_core/macro/macro_jit_x64.h
@@ -23,7 +23,7 @@ constexpr size_t MAX_CODE_SIZE = 0x10000;
class MacroJITx64 final : public MacroEngine {
public:
- explicit MacroJITx64(Engines::Maxwell3D& maxwell3d);
+ explicit MacroJITx64(Engines::Maxwell3D& maxwell3d_);
protected:
std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code) override;
@@ -34,7 +34,7 @@ private:
class MacroJITx64Impl : public Xbyak::CodeGenerator, public CachedMacro {
public:
- MacroJITx64Impl(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& code);
+ explicit MacroJITx64Impl(Engines::Maxwell3D& maxwell3d_, const std::vector<u32>& code_);
~MacroJITx64Impl();
void Execute(const std::vector<u32>& parameters, u32 method) override;
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 16b2aaa27..44240a9c4 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -4,6 +4,7 @@
#include "common/alignment.h"
#include "common/assert.h"
+#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/memory/page_table.h"
#include "core/hle/kernel/process.h"
@@ -11,6 +12,7 @@
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
namespace Tegra {
@@ -19,8 +21,8 @@ MemoryManager::MemoryManager(Core::System& system_)
MemoryManager::~MemoryManager() = default;
-void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface& rasterizer_) {
- rasterizer = &rasterizer_;
+void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
+ rasterizer = rasterizer_;
}
GPUVAddr MemoryManager::UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size) {
@@ -37,6 +39,12 @@ GPUVAddr MemoryManager::UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std
}
GPUVAddr MemoryManager::Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size) {
+ const auto it = std::ranges::lower_bound(map_ranges, gpu_addr, {}, &MapRange::first);
+ if (it != map_ranges.end() && it->first == gpu_addr) {
+ it->second = size;
+ } else {
+ map_ranges.insert(it, MapRange{gpu_addr, size});
+ }
return UpdateRange(gpu_addr, cpu_addr, size);
}
@@ -44,13 +52,28 @@ GPUVAddr MemoryManager::MapAllocate(VAddr cpu_addr, std::size_t size, std::size_
return Map(cpu_addr, *FindFreeRange(size, align), size);
}
+GPUVAddr MemoryManager::MapAllocate32(VAddr cpu_addr, std::size_t size) {
+ const std::optional<GPUVAddr> gpu_addr = FindFreeRange(size, 1, true);
+ ASSERT(gpu_addr);
+ return Map(cpu_addr, *gpu_addr, size);
+}
+
void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
- if (!size) {
+ if (size == 0) {
return;
}
-
+ const auto it = std::ranges::lower_bound(map_ranges, gpu_addr, {}, &MapRange::first);
+ if (it != map_ranges.end()) {
+ ASSERT(it->first == gpu_addr);
+ map_ranges.erase(it);
+ } else {
+ UNREACHABLE_MSG("Unmapping non-existent GPU address=0x{:x}", gpu_addr);
+ }
// Flush and invalidate through the GPU interface, to be asynchronous if possible.
- system.GPU().FlushAndInvalidateRegion(*GpuToCpuAddress(gpu_addr), size);
+ const std::optional<VAddr> cpu_addr = GpuToCpuAddress(gpu_addr);
+ ASSERT(cpu_addr);
+
+ rasterizer->UnmapMemory(*cpu_addr, size);
UpdateRange(gpu_addr, PageEntry::State::Unmapped, size);
}
@@ -58,7 +81,7 @@ void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
std::optional<GPUVAddr> MemoryManager::AllocateFixed(GPUVAddr gpu_addr, std::size_t size) {
for (u64 offset{}; offset < size; offset += page_size) {
if (!GetPageEntry(gpu_addr + offset).IsUnmapped()) {
- return {};
+ return std::nullopt;
}
}
@@ -108,7 +131,8 @@ void MemoryManager::SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::s
page_table[PageEntryIndex(gpu_addr)] = page_entry;
}
-std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align) const {
+std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size_t align,
+ bool start_32bit_address) const {
if (!align) {
align = page_size;
} else {
@@ -116,7 +140,7 @@ std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size
}
u64 available_size{};
- GPUVAddr gpu_addr{address_space_start};
+ GPUVAddr gpu_addr{start_32bit_address ? address_space_start_low : address_space_start};
while (gpu_addr + available_size < address_space_size) {
if (GetPageEntry(gpu_addr + available_size).IsUnmapped()) {
available_size += page_size;
@@ -135,13 +159,13 @@ std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size
}
}
- return {};
+ return std::nullopt;
}
std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const {
const auto page_entry{GetPageEntry(gpu_addr)};
if (!page_entry.IsValid()) {
- return {};
+ return std::nullopt;
}
return page_entry.ToAddress() + (gpu_addr & page_mask);
@@ -207,6 +231,12 @@ const u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) const {
return system.Memory().GetPointer(*address);
}
+size_t MemoryManager::BytesToMapEnd(GPUVAddr gpu_addr) const noexcept {
+ auto it = std::ranges::upper_bound(map_ranges, gpu_addr, {}, &MapRange::first);
+ --it;
+ return it->second - (gpu_addr - it->first);
+}
+
void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const {
std::size_t remaining_size{size};
std::size_t page_index{gpu_src_addr >> page_bits};
@@ -303,17 +333,29 @@ void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buf
}
}
+void MemoryManager::FlushRegion(GPUVAddr gpu_addr, size_t size) const {
+ size_t remaining_size{size};
+ size_t page_index{gpu_addr >> page_bits};
+ size_t page_offset{gpu_addr & page_mask};
+ while (remaining_size > 0) {
+ const size_t num_bytes{std::min(page_size - page_offset, remaining_size)};
+ if (const auto page_addr{GpuToCpuAddress(page_index << page_bits)}; page_addr) {
+ rasterizer->FlushRegion(*page_addr + page_offset, num_bytes);
+ }
+ ++page_index;
+ page_offset = 0;
+ remaining_size -= num_bytes;
+ }
+}
+
void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size) {
std::vector<u8> tmp_buffer(size);
ReadBlock(gpu_src_addr, tmp_buffer.data(), size);
- WriteBlock(gpu_dest_addr, tmp_buffer.data(), size);
-}
-void MemoryManager::CopyBlockUnsafe(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr,
- std::size_t size) {
- std::vector<u8> tmp_buffer(size);
- ReadBlockUnsafe(gpu_src_addr, tmp_buffer.data(), size);
- WriteBlockUnsafe(gpu_dest_addr, tmp_buffer.data(), size);
+ // The output block must be flushed in case it has data modified from the GPU.
+ // Fixes NPC geometry in Zombie Panic in Wonderland DX
+ FlushRegion(gpu_dest_addr, size);
+ WriteBlock(gpu_dest_addr, tmp_buffer.data(), size);
}
bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const {
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 53c8d122a..b3538d503 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -28,7 +28,7 @@ public:
};
constexpr PageEntry() = default;
- constexpr PageEntry(State state) : state{state} {}
+ constexpr PageEntry(State state_) : state{state_} {}
constexpr PageEntry(VAddr addr) : state{static_cast<State>(addr >> ShiftBits)} {}
[[nodiscard]] constexpr bool IsUnmapped() const {
@@ -68,11 +68,11 @@ static_assert(sizeof(PageEntry) == 4, "PageEntry is too large");
class MemoryManager final {
public:
- explicit MemoryManager(Core::System& system);
+ explicit MemoryManager(Core::System& system_);
~MemoryManager();
/// Binds a renderer to the memory manager.
- void BindRasterizer(VideoCore::RasterizerInterface& rasterizer);
+ void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
[[nodiscard]] std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr) const;
@@ -85,6 +85,9 @@ public:
[[nodiscard]] u8* GetPointer(GPUVAddr addr);
[[nodiscard]] const u8* GetPointer(GPUVAddr addr) const;
+ /// Returns the number of bytes until the end of the memory map containing the given GPU address
+ [[nodiscard]] size_t BytesToMapEnd(GPUVAddr gpu_addr) const noexcept;
+
/**
* ReadBlock and WriteBlock are full read and write operations over virtual
* GPU Memory. It's important to use these when GPU memory may not be continuous
@@ -107,7 +110,6 @@ public:
*/
void ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size) const;
void WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size);
- void CopyBlockUnsafe(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size);
/**
* IsGranularRange checks if a gpu region can be simply read with a pointer.
@@ -116,6 +118,7 @@ public:
[[nodiscard]] GPUVAddr Map(VAddr cpu_addr, GPUVAddr gpu_addr, std::size_t size);
[[nodiscard]] GPUVAddr MapAllocate(VAddr cpu_addr, std::size_t size, std::size_t align);
+ [[nodiscard]] GPUVAddr MapAllocate32(VAddr cpu_addr, std::size_t size);
[[nodiscard]] std::optional<GPUVAddr> AllocateFixed(GPUVAddr gpu_addr, std::size_t size);
[[nodiscard]] GPUVAddr Allocate(std::size_t size, std::size_t align);
void Unmap(GPUVAddr gpu_addr, std::size_t size);
@@ -124,17 +127,21 @@ private:
[[nodiscard]] PageEntry GetPageEntry(GPUVAddr gpu_addr) const;
void SetPageEntry(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size = page_size);
GPUVAddr UpdateRange(GPUVAddr gpu_addr, PageEntry page_entry, std::size_t size);
- [[nodiscard]] std::optional<GPUVAddr> FindFreeRange(std::size_t size, std::size_t align) const;
+ [[nodiscard]] std::optional<GPUVAddr> FindFreeRange(std::size_t size, std::size_t align,
+ bool start_32bit_address = false) const;
void TryLockPage(PageEntry page_entry, std::size_t size);
void TryUnlockPage(PageEntry page_entry, std::size_t size);
+ void FlushRegion(GPUVAddr gpu_addr, size_t size) const;
+
[[nodiscard]] static constexpr std::size_t PageEntryIndex(GPUVAddr gpu_addr) {
return (gpu_addr >> page_bits) & page_table_mask;
}
static constexpr u64 address_space_size = 1ULL << 40;
static constexpr u64 address_space_start = 1ULL << 32;
+ static constexpr u64 address_space_start_low = 1ULL << 16;
static constexpr u64 page_bits{16};
static constexpr u64 page_size{1 << page_bits};
static constexpr u64 page_mask{page_size - 1};
@@ -147,6 +154,11 @@ private:
VideoCore::RasterizerInterface* rasterizer = nullptr;
std::vector<PageEntry> page_table;
+
+ using MapRange = std::pair<GPUVAddr, size_t>;
+ std::vector<MapRange> map_ranges;
+
+ std::vector<std::pair<VAddr, std::size_t>> cache_invalidate_queue;
};
} // namespace Tegra
diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp
deleted file mode 100644
index 9da9fb4ff..000000000
--- a/src/video_core/morton.cpp
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-#include <cstring>
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "video_core/morton.h"
-#include "video_core/surface.h"
-#include "video_core/textures/decoders.h"
-
-namespace VideoCore {
-
-using Surface::GetBytesPerPixel;
-using Surface::PixelFormat;
-
-using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, u8*);
-using ConversionArray = std::array<MortonCopyFn, Surface::MaxPixelFormat>;
-
-template <bool morton_to_linear, PixelFormat format>
-static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth,
- u32 tile_width_spacing, u8* buffer, u8* addr) {
- constexpr u32 bytes_per_pixel = GetBytesPerPixel(format);
-
- // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
- // pixel values.
- constexpr u32 tile_size_x{GetDefaultBlockWidth(format)};
- constexpr u32 tile_size_y{GetDefaultBlockHeight(format)};
-
- if constexpr (morton_to_linear) {
- Tegra::Texture::UnswizzleTexture(buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel,
- stride, height, depth, block_height, block_depth,
- tile_width_spacing);
- } else {
- Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x,
- (height + tile_size_y - 1) / tile_size_y, depth,
- bytes_per_pixel, bytes_per_pixel, addr, buffer, false,
- block_height, block_depth, tile_width_spacing);
- }
-}
-
-static constexpr ConversionArray morton_to_linear_fns = {
- MortonCopy<true, PixelFormat::A8B8G8R8_UNORM>,
- MortonCopy<true, PixelFormat::A8B8G8R8_SNORM>,
- MortonCopy<true, PixelFormat::A8B8G8R8_SINT>,
- MortonCopy<true, PixelFormat::A8B8G8R8_UINT>,
- MortonCopy<true, PixelFormat::R5G6B5_UNORM>,
- MortonCopy<true, PixelFormat::B5G6R5_UNORM>,
- MortonCopy<true, PixelFormat::A1R5G5B5_UNORM>,
- MortonCopy<true, PixelFormat::A2B10G10R10_UNORM>,
- MortonCopy<true, PixelFormat::A2B10G10R10_UINT>,
- MortonCopy<true, PixelFormat::A1B5G5R5_UNORM>,
- MortonCopy<true, PixelFormat::R8_UNORM>,
- MortonCopy<true, PixelFormat::R8_SNORM>,
- MortonCopy<true, PixelFormat::R8_SINT>,
- MortonCopy<true, PixelFormat::R8_UINT>,
- MortonCopy<true, PixelFormat::R16G16B16A16_FLOAT>,
- MortonCopy<true, PixelFormat::R16G16B16A16_UNORM>,
- MortonCopy<true, PixelFormat::R16G16B16A16_SNORM>,
- MortonCopy<true, PixelFormat::R16G16B16A16_SINT>,
- MortonCopy<true, PixelFormat::R16G16B16A16_UINT>,
- MortonCopy<true, PixelFormat::B10G11R11_FLOAT>,
- MortonCopy<true, PixelFormat::R32G32B32A32_UINT>,
- MortonCopy<true, PixelFormat::BC1_RGBA_UNORM>,
- MortonCopy<true, PixelFormat::BC2_UNORM>,
- MortonCopy<true, PixelFormat::BC3_UNORM>,
- MortonCopy<true, PixelFormat::BC4_UNORM>,
- MortonCopy<true, PixelFormat::BC4_SNORM>,
- MortonCopy<true, PixelFormat::BC5_UNORM>,
- MortonCopy<true, PixelFormat::BC5_SNORM>,
- MortonCopy<true, PixelFormat::BC7_UNORM>,
- MortonCopy<true, PixelFormat::BC6H_UFLOAT>,
- MortonCopy<true, PixelFormat::BC6H_SFLOAT>,
- MortonCopy<true, PixelFormat::ASTC_2D_4X4_UNORM>,
- MortonCopy<true, PixelFormat::B8G8R8A8_UNORM>,
- MortonCopy<true, PixelFormat::R32G32B32A32_FLOAT>,
- MortonCopy<true, PixelFormat::R32G32B32A32_SINT>,
- MortonCopy<true, PixelFormat::R32G32_FLOAT>,
- MortonCopy<true, PixelFormat::R32G32_SINT>,
- MortonCopy<true, PixelFormat::R32_FLOAT>,
- MortonCopy<true, PixelFormat::R16_FLOAT>,
- MortonCopy<true, PixelFormat::R16_UNORM>,
- MortonCopy<true, PixelFormat::R16_SNORM>,
- MortonCopy<true, PixelFormat::R16_UINT>,
- MortonCopy<true, PixelFormat::R16_SINT>,
- MortonCopy<true, PixelFormat::R16G16_UNORM>,
- MortonCopy<true, PixelFormat::R16G16_FLOAT>,
- MortonCopy<true, PixelFormat::R16G16_UINT>,
- MortonCopy<true, PixelFormat::R16G16_SINT>,
- MortonCopy<true, PixelFormat::R16G16_SNORM>,
- MortonCopy<true, PixelFormat::R32G32B32_FLOAT>,
- MortonCopy<true, PixelFormat::A8B8G8R8_SRGB>,
- MortonCopy<true, PixelFormat::R8G8_UNORM>,
- MortonCopy<true, PixelFormat::R8G8_SNORM>,
- MortonCopy<true, PixelFormat::R8G8_SINT>,
- MortonCopy<true, PixelFormat::R8G8_UINT>,
- MortonCopy<true, PixelFormat::R32G32_UINT>,
- MortonCopy<true, PixelFormat::R16G16B16X16_FLOAT>,
- MortonCopy<true, PixelFormat::R32_UINT>,
- MortonCopy<true, PixelFormat::R32_SINT>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X8_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X5_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X4_UNORM>,
- MortonCopy<true, PixelFormat::B8G8R8A8_SRGB>,
- MortonCopy<true, PixelFormat::BC1_RGBA_SRGB>,
- MortonCopy<true, PixelFormat::BC2_SRGB>,
- MortonCopy<true, PixelFormat::BC3_SRGB>,
- MortonCopy<true, PixelFormat::BC7_SRGB>,
- MortonCopy<true, PixelFormat::A4B4G4R4_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X5_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X8_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_6X6_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_6X6_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X10_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X10_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_12X12_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_12X12_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X6_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X6_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_6X5_UNORM>,
- MortonCopy<true, PixelFormat::ASTC_2D_6X5_SRGB>,
- MortonCopy<true, PixelFormat::E5B9G9R9_FLOAT>,
- MortonCopy<true, PixelFormat::D32_FLOAT>,
- MortonCopy<true, PixelFormat::D16_UNORM>,
- MortonCopy<true, PixelFormat::D24_UNORM_S8_UINT>,
- MortonCopy<true, PixelFormat::S8_UINT_D24_UNORM>,
- MortonCopy<true, PixelFormat::D32_FLOAT_S8_UINT>,
-};
-
-static constexpr ConversionArray linear_to_morton_fns = {
- MortonCopy<false, PixelFormat::A8B8G8R8_UNORM>,
- MortonCopy<false, PixelFormat::A8B8G8R8_SNORM>,
- MortonCopy<false, PixelFormat::A8B8G8R8_SINT>,
- MortonCopy<false, PixelFormat::A8B8G8R8_UINT>,
- MortonCopy<false, PixelFormat::R5G6B5_UNORM>,
- MortonCopy<false, PixelFormat::B5G6R5_UNORM>,
- MortonCopy<false, PixelFormat::A1R5G5B5_UNORM>,
- MortonCopy<false, PixelFormat::A2B10G10R10_UNORM>,
- MortonCopy<false, PixelFormat::A2B10G10R10_UINT>,
- MortonCopy<false, PixelFormat::A1B5G5R5_UNORM>,
- MortonCopy<false, PixelFormat::R8_UNORM>,
- MortonCopy<false, PixelFormat::R8_SNORM>,
- MortonCopy<false, PixelFormat::R8_SINT>,
- MortonCopy<false, PixelFormat::R8_UINT>,
- MortonCopy<false, PixelFormat::R16G16B16A16_FLOAT>,
- MortonCopy<false, PixelFormat::R16G16B16A16_SNORM>,
- MortonCopy<false, PixelFormat::R16G16B16A16_SINT>,
- MortonCopy<false, PixelFormat::R16G16B16A16_UNORM>,
- MortonCopy<false, PixelFormat::R16G16B16A16_UINT>,
- MortonCopy<false, PixelFormat::B10G11R11_FLOAT>,
- MortonCopy<false, PixelFormat::R32G32B32A32_UINT>,
- MortonCopy<false, PixelFormat::BC1_RGBA_UNORM>,
- MortonCopy<false, PixelFormat::BC2_UNORM>,
- MortonCopy<false, PixelFormat::BC3_UNORM>,
- MortonCopy<false, PixelFormat::BC4_UNORM>,
- MortonCopy<false, PixelFormat::BC4_SNORM>,
- MortonCopy<false, PixelFormat::BC5_UNORM>,
- MortonCopy<false, PixelFormat::BC5_SNORM>,
- MortonCopy<false, PixelFormat::BC7_UNORM>,
- MortonCopy<false, PixelFormat::BC6H_UFLOAT>,
- MortonCopy<false, PixelFormat::BC6H_SFLOAT>,
- // TODO(Subv): Swizzling ASTC formats are not supported
- nullptr,
- MortonCopy<false, PixelFormat::B8G8R8A8_UNORM>,
- MortonCopy<false, PixelFormat::R32G32B32A32_FLOAT>,
- MortonCopy<false, PixelFormat::R32G32B32A32_SINT>,
- MortonCopy<false, PixelFormat::R32G32_FLOAT>,
- MortonCopy<false, PixelFormat::R32G32_SINT>,
- MortonCopy<false, PixelFormat::R32_FLOAT>,
- MortonCopy<false, PixelFormat::R16_FLOAT>,
- MortonCopy<false, PixelFormat::R16_UNORM>,
- MortonCopy<false, PixelFormat::R16_SNORM>,
- MortonCopy<false, PixelFormat::R16_UINT>,
- MortonCopy<false, PixelFormat::R16_SINT>,
- MortonCopy<false, PixelFormat::R16G16_UNORM>,
- MortonCopy<false, PixelFormat::R16G16_FLOAT>,
- MortonCopy<false, PixelFormat::R16G16_UINT>,
- MortonCopy<false, PixelFormat::R16G16_SINT>,
- MortonCopy<false, PixelFormat::R16G16_SNORM>,
- MortonCopy<false, PixelFormat::R32G32B32_FLOAT>,
- MortonCopy<false, PixelFormat::A8B8G8R8_SRGB>,
- MortonCopy<false, PixelFormat::R8G8_UNORM>,
- MortonCopy<false, PixelFormat::R8G8_SNORM>,
- MortonCopy<false, PixelFormat::R8G8_SINT>,
- MortonCopy<false, PixelFormat::R8G8_UINT>,
- MortonCopy<false, PixelFormat::R32G32_UINT>,
- MortonCopy<false, PixelFormat::R16G16B16X16_FLOAT>,
- MortonCopy<false, PixelFormat::R32_UINT>,
- MortonCopy<false, PixelFormat::R32_SINT>,
- nullptr,
- nullptr,
- nullptr,
- MortonCopy<false, PixelFormat::B8G8R8A8_SRGB>,
- MortonCopy<false, PixelFormat::BC1_RGBA_SRGB>,
- MortonCopy<false, PixelFormat::BC2_SRGB>,
- MortonCopy<false, PixelFormat::BC3_SRGB>,
- MortonCopy<false, PixelFormat::BC7_SRGB>,
- MortonCopy<false, PixelFormat::A4B4G4R4_UNORM>,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- MortonCopy<false, PixelFormat::E5B9G9R9_FLOAT>,
- MortonCopy<false, PixelFormat::D32_FLOAT>,
- MortonCopy<false, PixelFormat::D16_UNORM>,
- MortonCopy<false, PixelFormat::D24_UNORM_S8_UINT>,
- MortonCopy<false, PixelFormat::S8_UINT_D24_UNORM>,
- MortonCopy<false, PixelFormat::D32_FLOAT_S8_UINT>,
-};
-
-static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFormat format) {
- switch (mode) {
- case MortonSwizzleMode::MortonToLinear:
- return morton_to_linear_fns[static_cast<std::size_t>(format)];
- case MortonSwizzleMode::LinearToMorton:
- return linear_to_morton_fns[static_cast<std::size_t>(format)];
- }
- UNREACHABLE();
- return morton_to_linear_fns[static_cast<std::size_t>(format)];
-}
-
-void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride,
- u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
- u8* buffer, u8* addr) {
- GetSwizzleFunction(mode, format)(stride, block_height, height, block_depth, depth,
- tile_width_spacing, buffer, addr);
-}
-
-} // namespace VideoCore
diff --git a/src/video_core/morton.h b/src/video_core/morton.h
deleted file mode 100644
index b714a7e3f..000000000
--- a/src/video_core/morton.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_types.h"
-#include "video_core/surface.h"
-
-namespace VideoCore {
-
-enum class MortonSwizzleMode { MortonToLinear, LinearToMorton };
-
-void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat format, u32 stride,
- u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
- u8* buffer, u8* addr);
-
-} // namespace VideoCore
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index 0d3a88765..203f2af05 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -28,8 +28,8 @@ namespace VideoCommon {
template <class QueryCache, class HostCounter>
class CounterStreamBase {
public:
- explicit CounterStreamBase(QueryCache& cache, VideoCore::QueryType type)
- : cache{cache}, type{type} {}
+ explicit CounterStreamBase(QueryCache& cache_, VideoCore::QueryType type_)
+ : cache{cache_}, type{type_} {}
/// Updates the state of the stream, enabling or disabling as needed.
void Update(bool enabled) {
@@ -91,14 +91,15 @@ private:
std::shared_ptr<HostCounter> last;
};
-template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter,
- class QueryPool>
+template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
class QueryCacheBase {
public:
- explicit QueryCacheBase(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
- : system{system}, rasterizer{rasterizer}, streams{{CounterStream{
- static_cast<QueryCache&>(*this),
- VideoCore::QueryType::SamplesPassed}}} {}
+ explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::MemoryManager& gpu_memory_)
+ : rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
+ gpu_memory{gpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
+ VideoCore::QueryType::SamplesPassed}}} {}
void InvalidateRegion(VAddr addr, std::size_t size) {
std::unique_lock lock{mutex};
@@ -118,29 +119,27 @@ public:
*/
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) {
std::unique_lock lock{mutex};
- auto& memory_manager = system.GPU().MemoryManager();
- const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr);
- ASSERT(cpu_addr_opt);
- VAddr cpu_addr = *cpu_addr_opt;
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ ASSERT(cpu_addr);
- CachedQuery* query = TryGet(cpu_addr);
+ CachedQuery* query = TryGet(*cpu_addr);
if (!query) {
- ASSERT_OR_EXECUTE(cpu_addr_opt, return;);
- const auto host_ptr = memory_manager.GetPointer(gpu_addr);
+ ASSERT_OR_EXECUTE(cpu_addr, return;);
+ u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
- query = Register(type, cpu_addr, host_ptr, timestamp.has_value());
+ query = Register(type, *cpu_addr, host_ptr, timestamp.has_value());
}
query->BindCounter(Stream(type).Current(), timestamp);
if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
- AsyncFlushQuery(cpu_addr);
+ AsyncFlushQuery(*cpu_addr);
}
}
/// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch.
void UpdateCounters() {
std::unique_lock lock{mutex};
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable);
}
@@ -206,9 +205,6 @@ public:
committed_flushes.pop_front();
}
-protected:
- std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
-
private:
/// Flushes a memory range to guest memory and removes it from the cache.
void FlushAndRemoveRegion(VAddr addr, std::size_t size) {
@@ -270,8 +266,9 @@ private:
static constexpr std::uintptr_t PAGE_SIZE = 4096;
static constexpr unsigned PAGE_BITS = 12;
- Core::System& system;
VideoCore::RasterizerInterface& rasterizer;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::MemoryManager& gpu_memory;
std::recursive_mutex mutex;
@@ -337,8 +334,8 @@ private:
template <class HostCounter>
class CachedQueryBase {
public:
- explicit CachedQueryBase(VAddr cpu_addr, u8* host_ptr)
- : cpu_addr{cpu_addr}, host_ptr{host_ptr} {}
+ explicit CachedQueryBase(VAddr cpu_addr_, u8* host_ptr_)
+ : cpu_addr{cpu_addr_}, host_ptr{host_ptr_} {}
virtual ~CachedQueryBase() = default;
CachedQueryBase(CachedQueryBase&&) noexcept = default;
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 3cbdac8e7..50491b758 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -7,6 +7,7 @@
#include <atomic>
#include <functional>
#include <optional>
+#include <span>
#include "common/common_types.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/gpu.h"
@@ -32,7 +33,7 @@ using DiskResourceLoadCallback = std::function<void(LoadCallbackStage, std::size
class RasterizerInterface {
public:
- virtual ~RasterizerInterface() {}
+ virtual ~RasterizerInterface() = default;
/// Dispatches a draw invocation
virtual void Draw(bool is_indexed, bool is_instanced) = 0;
@@ -49,6 +50,10 @@ public:
/// Records a GPU query and caches it
virtual void Query(GPUVAddr gpu_addr, QueryType type, std::optional<u64> timestamp) = 0;
+ /// Signal an uniform buffer binding
+ virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
+ u32 size) = 0;
+
/// Signal a GPU based semaphore as a fence
virtual void SignalSemaphore(GPUVAddr addr, u32 value) = 0;
@@ -76,6 +81,9 @@ public:
/// Sync memory between guest and host.
virtual void SyncGuestHost() = 0;
+ /// Unmap memory range
+ virtual void UnmapMemory(VAddr addr, u64 size) = 0;
+
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
/// and invalidated
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
@@ -83,6 +91,12 @@ public:
/// Notify the host renderer to wait for previous primitive and compute operations.
virtual void WaitForIdle() = 0;
+ /// Notify the host renderer to wait for reads and writes to render targets and flush caches.
+ virtual void FragmentBarrier() = 0;
+
+ /// Notify the host renderer to make available previous render target writes.
+ virtual void TiledCacheBarrier() = 0;
+
/// Notify the rasterizer to send all written commands to the host GPU.
virtual void FlushCommands() = 0;
@@ -90,15 +104,15 @@ public:
virtual void TickFrame() = 0;
/// Attempt to use a faster method to perform a surface copy
- virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
- const Tegra::Engines::Fermi2D::Regs::Surface& dst,
- const Tegra::Engines::Fermi2D::Config& copy_config) {
+ [[nodiscard]] virtual bool AccelerateSurfaceCopy(
+ const Tegra::Engines::Fermi2D::Surface& src, const Tegra::Engines::Fermi2D::Surface& dst,
+ const Tegra::Engines::Fermi2D::Config& copy_config) {
return false;
}
/// Attempt to use a faster method to display the framebuffer to screen
- virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
- u32 pixel_stride) {
+ [[nodiscard]] virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config,
+ VAddr framebuffer_addr, u32 pixel_stride) {
return false;
}
@@ -106,19 +120,16 @@ public:
virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
/// Initialize disk cached resources for the game being emulated
- virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false,
- const DiskResourceLoadCallback& callback = {}) {}
-
- /// Initializes renderer dirty flags
- virtual void SetupDirtyFlags() {}
+ virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
+ const DiskResourceLoadCallback& callback) {}
/// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
- GuestDriverProfile& AccessGuestDriverProfile() {
+ [[nodiscard]] GuestDriverProfile& AccessGuestDriverProfile() {
return guest_driver_profile;
}
/// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
- const GuestDriverProfile& AccessGuestDriverProfile() const {
+ [[nodiscard]] const GuestDriverProfile& AccessGuestDriverProfile() const {
return guest_driver_profile;
}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 649074acd..320ee8d30 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -37,60 +37,43 @@ public:
std::unique_ptr<Core::Frontend::GraphicsContext> context);
virtual ~RendererBase();
- /// Initialize the renderer
- virtual bool Init() = 0;
-
- /// Shutdown the renderer
- virtual void ShutDown() = 0;
-
/// Finalize rendering the guest frame and draw into the presentation texture
virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
- /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
- /// specific implementation)
- /// Returns true if a frame was drawn
- virtual bool TryPresent(int timeout_ms) = 0;
+ [[nodiscard]] virtual RasterizerInterface* ReadRasterizer() = 0;
// Getter/setter functions:
// ------------------------
- f32 GetCurrentFPS() const {
+ [[nodiscard]] f32 GetCurrentFPS() const {
return m_current_fps;
}
- int GetCurrentFrame() const {
+ [[nodiscard]] int GetCurrentFrame() const {
return m_current_frame;
}
- RasterizerInterface& Rasterizer() {
- return *rasterizer;
- }
-
- const RasterizerInterface& Rasterizer() const {
- return *rasterizer;
- }
-
- Core::Frontend::GraphicsContext& Context() {
+ [[nodiscard]] Core::Frontend::GraphicsContext& Context() {
return *context;
}
- const Core::Frontend::GraphicsContext& Context() const {
+ [[nodiscard]] const Core::Frontend::GraphicsContext& Context() const {
return *context;
}
- Core::Frontend::EmuWindow& GetRenderWindow() {
+ [[nodiscard]] Core::Frontend::EmuWindow& GetRenderWindow() {
return render_window;
}
- const Core::Frontend::EmuWindow& GetRenderWindow() const {
+ [[nodiscard]] const Core::Frontend::EmuWindow& GetRenderWindow() const {
return render_window;
}
- RendererSettings& Settings() {
+ [[nodiscard]] RendererSettings& Settings() {
return renderer_settings;
}
- const RendererSettings& Settings() const {
+ [[nodiscard]] const RendererSettings& Settings() const {
return renderer_settings;
}
@@ -103,7 +86,6 @@ public:
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
- std::unique_ptr<RasterizerInterface> rasterizer;
std::unique_ptr<Core::Frontend::GraphicsContext> context;
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer
int m_current_frame = 0; ///< Current frame, should be set by the renderer
diff --git a/src/video_core/renderer_opengl/gl_arb_decompiler.cpp b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp
index b7e9ed2e9..3e4d88c30 100644
--- a/src/video_core/renderer_opengl/gl_arb_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_arb_decompiler.cpp
@@ -39,8 +39,8 @@ using Operation = const OperationNode&;
constexpr std::array INTERNAL_FLAG_NAMES = {"ZERO", "SIGN", "CARRY", "OVERFLOW"};
char Swizzle(std::size_t component) {
- ASSERT(component < 4);
- return component["xyzw"];
+ static constexpr std::string_view SWIZZLE{"xyzw"};
+ return SWIZZLE.at(component);
}
constexpr bool IsGenericAttribute(Attribute::Index index) {
@@ -71,7 +71,7 @@ std::string_view GetInputFlags(PixelImap attribute) {
case PixelImap::Unused:
break;
}
- UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<int>(attribute));
+ UNIMPLEMENTED_MSG("Unknown attribute usage index={}", attribute);
return {};
}
@@ -123,7 +123,7 @@ std::string_view PrimitiveDescription(Tegra::Engines::Maxwell3D::Regs::Primitive
case Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
return "TRIANGLES_ADJACENCY";
default:
- UNIMPLEMENTED_MSG("topology={}", static_cast<int>(topology));
+ UNIMPLEMENTED_MSG("topology={}", topology);
return "POINTS";
}
}
@@ -137,7 +137,7 @@ std::string_view TopologyName(Tegra::Shader::OutputTopology topology) {
case Tegra::Shader::OutputTopology::TriangleStrip:
return "TRIANGLE_STRIP";
default:
- UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast<u32>(topology));
+ UNIMPLEMENTED_MSG("Unknown output topology: {}", topology);
return "points";
}
}
@@ -187,8 +187,8 @@ std::string TextureType(const MetaTexture& meta) {
class ARBDecompiler final {
public:
- explicit ARBDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry,
- ShaderType stage, std::string_view identifier);
+ explicit ARBDecompiler(const Device& device_, const ShaderIR& ir_, const Registry& registry_,
+ ShaderType stage_, std::string_view identifier);
std::string Code() const {
return shader_source;
@@ -224,7 +224,7 @@ private:
std::string Visit(const Node& node);
- std::pair<std::string, std::size_t> BuildCoords(Operation);
+ std::tuple<std::string, std::string, std::size_t> BuildCoords(Operation);
std::string BuildAoffi(Operation);
std::string GlobalMemoryPointer(const GmemNode& gmem);
void Exit();
@@ -376,9 +376,11 @@ private:
std::string temporary = AllocTemporary();
std::string address;
std::string_view opname;
+ bool robust = false;
if (const auto gmem = std::get_if<GmemNode>(&*operation[0])) {
address = GlobalMemoryPointer(*gmem);
opname = "ATOM";
+ robust = true;
} else if (const auto smem = std::get_if<SmemNode>(&*operation[0])) {
address = fmt::format("shared_mem[{}]", Visit(smem->GetAddress()));
opname = "ATOMS";
@@ -386,7 +388,15 @@ private:
UNREACHABLE();
return "{0, 0, 0, 0}";
}
+ if (robust) {
+ AddLine("IF NE.x;");
+ }
AddLine("{}.{}.{} {}, {}, {};", opname, op, type, temporary, Visit(operation[1]), address);
+ if (robust) {
+ AddLine("ELSE;");
+ AddLine("MOV.S {}, 0;", temporary);
+ AddLine("ENDIF;");
+ }
return temporary;
}
@@ -792,9 +802,9 @@ private:
};
};
-ARBDecompiler::ARBDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry,
- ShaderType stage, std::string_view identifier)
- : device{device}, ir{ir}, registry{registry}, stage{stage} {
+ARBDecompiler::ARBDecompiler(const Device& device_, const ShaderIR& ir_, const Registry& registry_,
+ ShaderType stage_, std::string_view identifier)
+ : device{device_}, ir{ir_}, registry{registry_}, stage{stage_} {
DefineGlobalMemory();
AddLine("TEMP RC;");
@@ -980,10 +990,9 @@ void ARBDecompiler::DeclareLocalMemory() {
}
void ARBDecompiler::DeclareGlobalMemory() {
- const std::size_t num_entries = ir.GetGlobalMemory().size();
+ const size_t num_entries = ir.GetGlobalMemory().size();
if (num_entries > 0) {
- const std::size_t num_vectors = Common::AlignUp(num_entries, 2) / 2;
- AddLine("PARAM c[{}] = {{ program.local[0..{}] }};", num_vectors, num_vectors - 1);
+ AddLine("PARAM c[{}] = {{ program.local[0..{}] }};", num_entries, num_entries - 1);
}
}
@@ -1125,44 +1134,44 @@ void ARBDecompiler::VisitAST(const ASTNode& node) {
for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) {
VisitAST(current);
}
- } else if (const auto ast = std::get_if<ASTIfThen>(&*node->GetInnerData())) {
- const std::string condition = VisitExpression(ast->condition);
+ } else if (const auto if_then = std::get_if<ASTIfThen>(&*node->GetInnerData())) {
+ const std::string condition = VisitExpression(if_then->condition);
ResetTemporaries();
AddLine("MOVC.U RC.x, {};", condition);
AddLine("IF NE.x;");
- for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) {
+ for (ASTNode current = if_then->nodes.GetFirst(); current; current = current->GetNext()) {
VisitAST(current);
}
AddLine("ENDIF;");
- } else if (const auto ast = std::get_if<ASTIfElse>(&*node->GetInnerData())) {
+ } else if (const auto if_else = std::get_if<ASTIfElse>(&*node->GetInnerData())) {
AddLine("ELSE;");
- for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) {
+ for (ASTNode current = if_else->nodes.GetFirst(); current; current = current->GetNext()) {
VisitAST(current);
}
- } else if (const auto ast = std::get_if<ASTBlockDecoded>(&*node->GetInnerData())) {
- VisitBlock(ast->nodes);
- } else if (const auto ast = std::get_if<ASTVarSet>(&*node->GetInnerData())) {
- AddLine("MOV.U F{}, {};", ast->index, VisitExpression(ast->condition));
+ } else if (const auto decoded = std::get_if<ASTBlockDecoded>(&*node->GetInnerData())) {
+ VisitBlock(decoded->nodes);
+ } else if (const auto var_set = std::get_if<ASTVarSet>(&*node->GetInnerData())) {
+ AddLine("MOV.U F{}, {};", var_set->index, VisitExpression(var_set->condition));
ResetTemporaries();
- } else if (const auto ast = std::get_if<ASTDoWhile>(&*node->GetInnerData())) {
- const std::string condition = VisitExpression(ast->condition);
+ } else if (const auto do_while = std::get_if<ASTDoWhile>(&*node->GetInnerData())) {
+ const std::string condition = VisitExpression(do_while->condition);
ResetTemporaries();
AddLine("REP;");
- for (ASTNode current = ast->nodes.GetFirst(); current; current = current->GetNext()) {
+ for (ASTNode current = do_while->nodes.GetFirst(); current; current = current->GetNext()) {
VisitAST(current);
}
AddLine("MOVC.U RC.x, {};", condition);
AddLine("BRK (NE.x);");
AddLine("ENDREP;");
- } else if (const auto ast = std::get_if<ASTReturn>(&*node->GetInnerData())) {
- const bool is_true = ExprIsTrue(ast->condition);
+ } else if (const auto ast_return = std::get_if<ASTReturn>(&*node->GetInnerData())) {
+ const bool is_true = ExprIsTrue(ast_return->condition);
if (!is_true) {
- AddLine("MOVC.U RC.x, {};", VisitExpression(ast->condition));
+ AddLine("MOVC.U RC.x, {};", VisitExpression(ast_return->condition));
AddLine("IF NE.x;");
ResetTemporaries();
}
- if (ast->kills) {
+ if (ast_return->kills) {
AddLine("KIL TR;");
} else {
Exit();
@@ -1170,11 +1179,11 @@ void ARBDecompiler::VisitAST(const ASTNode& node) {
if (!is_true) {
AddLine("ENDIF;");
}
- } else if (const auto ast = std::get_if<ASTBreak>(&*node->GetInnerData())) {
- if (ExprIsTrue(ast->condition)) {
+ } else if (const auto ast_break = std::get_if<ASTBreak>(&*node->GetInnerData())) {
+ if (ExprIsTrue(ast_break->condition)) {
AddLine("BRK;");
} else {
- AddLine("MOVC.U RC.x, {};", VisitExpression(ast->condition));
+ AddLine("MOVC.U RC.x, {};", VisitExpression(ast_break->condition));
AddLine("BRK (NE.x);");
ResetTemporaries();
}
@@ -1342,7 +1351,7 @@ std::string ARBDecompiler::Visit(const Node& node) {
GetGenericAttributeIndex(index), swizzle);
}
}
- UNIMPLEMENTED_MSG("Unimplemented input attribute={}", static_cast<int>(index));
+ UNIMPLEMENTED_MSG("Unimplemented input attribute={}", index);
break;
}
return "{0, 0, 0, 0}.x";
@@ -1363,7 +1372,8 @@ std::string ARBDecompiler::Visit(const Node& node) {
if (const auto gmem = std::get_if<GmemNode>(&*node)) {
std::string temporary = AllocTemporary();
- AddLine("LOAD.U32 {}, {};", temporary, GlobalMemoryPointer(*gmem));
+ AddLine("MOV {}, 0;", temporary);
+ AddLine("LOAD.U32 {} (NE.x), {};", temporary, GlobalMemoryPointer(*gmem));
return temporary;
}
@@ -1406,12 +1416,12 @@ std::string ARBDecompiler::Visit(const Node& node) {
return {};
}
-std::pair<std::string, std::size_t> ARBDecompiler::BuildCoords(Operation operation) {
+std::tuple<std::string, std::string, std::size_t> ARBDecompiler::BuildCoords(Operation operation) {
const auto& meta = std::get<MetaTexture>(operation.GetMeta());
UNIMPLEMENTED_IF(meta.sampler.is_indexed);
- UNIMPLEMENTED_IF(meta.sampler.is_shadow && meta.sampler.is_array &&
- meta.sampler.type == Tegra::Shader::TextureType::TextureCube);
+ const bool is_extended = meta.sampler.is_shadow && meta.sampler.is_array &&
+ meta.sampler.type == Tegra::Shader::TextureType::TextureCube;
const std::size_t count = operation.GetOperandsCount();
std::string temporary = AllocVectorTemporary();
std::size_t i = 0;
@@ -1419,12 +1429,21 @@ std::pair<std::string, std::size_t> ARBDecompiler::BuildCoords(Operation operati
AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i), Visit(operation[i]));
}
if (meta.sampler.is_array) {
- AddLine("I2F.S {}.{}, {};", temporary, Swizzle(i++), Visit(meta.array));
+ AddLine("I2F.S {}.{}, {};", temporary, Swizzle(i), Visit(meta.array));
+ ++i;
}
if (meta.sampler.is_shadow) {
- AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i++), Visit(meta.depth_compare));
+ std::string compare = Visit(meta.depth_compare);
+ if (is_extended) {
+ ASSERT(i == 4);
+ std::string extra_coord = AllocVectorTemporary();
+ AddLine("MOV.F {}.x, {};", extra_coord, compare);
+ return {fmt::format("{}, {}", temporary, extra_coord), extra_coord, 0};
+ }
+ AddLine("MOV.F {}.{}, {};", temporary, Swizzle(i), compare);
+ ++i;
}
- return {std::move(temporary), i};
+ return {temporary, temporary, i};
}
std::string ARBDecompiler::BuildAoffi(Operation operation) {
@@ -1441,18 +1460,21 @@ std::string ARBDecompiler::BuildAoffi(Operation operation) {
}
std::string ARBDecompiler::GlobalMemoryPointer(const GmemNode& gmem) {
+ // Read a bindless SSBO, return its address and set CC accordingly
+ // address = c[binding].xy
+ // length = c[binding].z
const u32 binding = global_memory_names.at(gmem.GetDescriptor());
- const char result_swizzle = binding % 2 == 0 ? 'x' : 'y';
const std::string pointer = AllocLongVectorTemporary();
std::string temporary = AllocTemporary();
- const u32 local_index = binding / 2;
- AddLine("PK64.U {}, c[{}];", pointer, local_index);
+ AddLine("PK64.U {}, c[{}];", pointer, binding);
AddLine("SUB.U {}, {}, {};", temporary, Visit(gmem.GetRealAddress()),
Visit(gmem.GetBaseAddress()));
AddLine("CVT.U64.U32 {}.z, {};", pointer, temporary);
- AddLine("ADD.U64 {}.x, {}.{}, {}.z;", pointer, pointer, result_swizzle, pointer);
+ AddLine("ADD.U64 {}.x, {}.x, {}.z;", pointer, pointer, pointer);
+ // Compare offset to length and set CC
+ AddLine("SLT.U.CC RC.x, {}, c[{}].z;", temporary, binding);
return fmt::format("{}.x", pointer);
}
@@ -1463,9 +1485,7 @@ void ARBDecompiler::Exit() {
}
const auto safe_get_register = [this](u32 reg) -> std::string {
- // TODO(Rodrigo): Replace with contains once C++20 releases
- const auto& used_registers = ir.GetRegisters();
- if (used_registers.find(reg) != used_registers.end()) {
+ if (ir.GetRegisters().contains(reg)) {
return fmt::format("R{}.x", reg);
}
return "{0, 0, 0, 0}.x";
@@ -1552,7 +1572,9 @@ std::string ARBDecompiler::Assign(Operation operation) {
ResetTemporaries();
return {};
} else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
+ AddLine("IF NE.x;");
AddLine("STORE.U32 {}, {};", Visit(src), GlobalMemoryPointer(*gmem));
+ AddLine("ENDIF;");
ResetTemporaries();
return {};
} else {
@@ -1844,7 +1866,7 @@ std::string ARBDecompiler::LogicalAddCarry(Operation operation) {
std::string ARBDecompiler::Texture(Operation operation) {
const auto& meta = std::get<MetaTexture>(operation.GetMeta());
const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
- const auto [temporary, swizzle] = BuildCoords(operation);
+ const auto [coords, temporary, swizzle] = BuildCoords(operation);
std::string_view opcode = "TEX";
std::string extra;
@@ -1873,7 +1895,7 @@ std::string ARBDecompiler::Texture(Operation operation) {
}
}
- AddLine("{}.F {}, {},{} texture[{}], {}{};", opcode, temporary, temporary, extra, sampler_id,
+ AddLine("{}.F {}, {},{} texture[{}], {}{};", opcode, temporary, coords, extra, sampler_id,
TextureType(meta), BuildAoffi(operation));
AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element));
return fmt::format("{}.x", temporary);
@@ -1882,7 +1904,7 @@ std::string ARBDecompiler::Texture(Operation operation) {
std::string ARBDecompiler::TextureGather(Operation operation) {
const auto& meta = std::get<MetaTexture>(operation.GetMeta());
const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
- const auto [temporary, swizzle] = BuildCoords(operation);
+ const auto [coords, temporary, swizzle] = BuildCoords(operation);
std::string comp;
if (!meta.sampler.is_shadow) {
@@ -1892,7 +1914,7 @@ std::string ARBDecompiler::TextureGather(Operation operation) {
AddLine("TXG.F {}, {}, texture[{}]{}, {}{};", temporary, temporary, sampler_id, comp,
TextureType(meta), BuildAoffi(operation));
- AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element));
+ AddLine("MOV.U {}.x, {}.{};", temporary, coords, Swizzle(meta.element));
return fmt::format("{}.x", temporary);
}
@@ -1930,13 +1952,13 @@ std::string ARBDecompiler::TextureQueryLod(Operation operation) {
std::string ARBDecompiler::TexelFetch(Operation operation) {
const auto& meta = std::get<MetaTexture>(operation.GetMeta());
const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
- const auto [temporary, swizzle] = BuildCoords(operation);
+ const auto [coords, temporary, swizzle] = BuildCoords(operation);
if (!meta.sampler.is_buffer) {
ASSERT(swizzle < 4);
AddLine("MOV.F {}.w, {};", temporary, Visit(meta.lod));
}
- AddLine("TXF.F {}, {}, texture[{}], {}{};", temporary, temporary, sampler_id, TextureType(meta),
+ AddLine("TXF.F {}, {}, texture[{}], {}{};", temporary, coords, sampler_id, TextureType(meta),
BuildAoffi(operation));
AddLine("MOV.U {}.x, {}.{};", temporary, temporary, Swizzle(meta.element));
return fmt::format("{}.x", temporary);
@@ -1947,7 +1969,7 @@ std::string ARBDecompiler::TextureGradient(Operation operation) {
const u32 sampler_id = device.GetBaseBindings(stage).sampler + meta.sampler.index;
const std::string ddx = AllocVectorTemporary();
const std::string ddy = AllocVectorTemporary();
- const std::string coord = BuildCoords(operation).first;
+ const std::string coord = std::get<1>(BuildCoords(operation));
const std::size_t num_components = meta.derivates.size() / 2;
for (std::size_t index = 0; index < num_components; ++index) {
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index e866d8f2f..6da3906a4 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -2,98 +2,208 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <memory>
+#include <span>
-#include <glad/glad.h>
-
-#include "common/assert.h"
-#include "common/microprofile.h"
#include "video_core/buffer_cache/buffer_cache.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_device.h"
-#include "video_core/renderer_opengl/gl_rasterizer.h"
-#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
+namespace {
+struct BindlessSSBO {
+ GLuint64EXT address;
+ GLsizei length;
+ GLsizei padding;
+};
+static_assert(sizeof(BindlessSSBO) == sizeof(GLuint) * 4);
+
+constexpr std::array PROGRAM_LUT{
+ GL_VERTEX_PROGRAM_NV, GL_TESS_CONTROL_PROGRAM_NV, GL_TESS_EVALUATION_PROGRAM_NV,
+ GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV,
+};
+} // Anonymous namespace
+
+Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
+ : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {}
+
+Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
+ VAddr cpu_addr_, u64 size_bytes_)
+ : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_) {
+ buffer.Create();
+ const std::string name = fmt::format("Buffer 0x{:x}", CpuAddr());
+ glObjectLabel(GL_BUFFER, buffer.handle, static_cast<GLsizei>(name.size()), name.data());
+ glNamedBufferData(buffer.handle, SizeBytes(), nullptr, GL_DYNAMIC_DRAW);
+
+ if (runtime.has_unified_vertex_buffers) {
+ glGetNamedBufferParameterui64vNV(buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &address);
+ }
+}
-using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+void Buffer::ImmediateUpload(size_t offset, std::span<const u8> data) noexcept {
+ glNamedBufferSubData(buffer.handle, static_cast<GLintptr>(offset),
+ static_cast<GLsizeiptr>(data.size_bytes()), data.data());
+}
-MICROPROFILE_DEFINE(OpenGL_Buffer_Download, "OpenGL", "Buffer Download", MP_RGB(192, 192, 128));
+void Buffer::ImmediateDownload(size_t offset, std::span<u8> data) noexcept {
+ glGetNamedBufferSubData(buffer.handle, static_cast<GLintptr>(offset),
+ static_cast<GLsizeiptr>(data.size_bytes()), data.data());
+}
-Buffer::Buffer(const Device& device, VAddr cpu_addr, std::size_t size)
- : VideoCommon::BufferBlock{cpu_addr, size} {
- gl_buffer.Create();
- glNamedBufferData(gl_buffer.handle, static_cast<GLsizeiptr>(size), nullptr, GL_DYNAMIC_DRAW);
- if (device.UseAssemblyShaders() || device.HasVertexBufferUnifiedMemory()) {
- glMakeNamedBufferResidentNV(gl_buffer.handle, GL_READ_WRITE);
- glGetNamedBufferParameterui64vNV(gl_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &gpu_address);
+void Buffer::MakeResident(GLenum access) noexcept {
+ // Abuse GLenum's order to exit early
+ // GL_NONE (default) < GL_READ_ONLY < GL_READ_WRITE
+ if (access <= current_residency_access || buffer.handle == 0) {
+ return;
+ }
+ if (std::exchange(current_residency_access, access) != GL_NONE) {
+ // If the buffer is already resident, remove its residency before promoting it
+ glMakeNamedBufferNonResidentNV(buffer.handle);
}
+ glMakeNamedBufferResidentNV(buffer.handle, access);
}
-Buffer::~Buffer() = default;
-
-void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) {
- glNamedBufferSubData(Handle(), static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size),
- data);
+BufferCacheRuntime::BufferCacheRuntime(const Device& device_)
+ : device{device_}, has_fast_buffer_sub_data{device.HasFastBufferSubData()},
+ use_assembly_shaders{device.UseAssemblyShaders()},
+ has_unified_vertex_buffers{device.HasVertexBufferUnifiedMemory()},
+ stream_buffer{has_fast_buffer_sub_data ? std::nullopt : std::make_optional<StreamBuffer>()} {
+ GLint gl_max_attributes;
+ glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_max_attributes);
+ max_attributes = static_cast<u32>(gl_max_attributes);
+ for (auto& stage_uniforms : fast_uniforms) {
+ for (OGLBuffer& buffer : stage_uniforms) {
+ buffer.Create();
+ glNamedBufferData(buffer.handle, BufferCache::SKIP_CACHE_SIZE, nullptr, GL_STREAM_DRAW);
+ }
+ }
+ for (auto& stage_uniforms : copy_uniforms) {
+ for (OGLBuffer& buffer : stage_uniforms) {
+ buffer.Create();
+ glNamedBufferData(buffer.handle, 0x10'000, nullptr, GL_STREAM_COPY);
+ }
+ }
+ for (OGLBuffer& buffer : copy_compute_uniforms) {
+ buffer.Create();
+ glNamedBufferData(buffer.handle, 0x10'000, nullptr, GL_STREAM_COPY);
+ }
}
-void Buffer::Download(std::size_t offset, std::size_t size, u8* data) {
- MICROPROFILE_SCOPE(OpenGL_Buffer_Download);
- const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size);
- const GLintptr gl_offset = static_cast<GLintptr>(offset);
- if (read_buffer.handle == 0) {
- read_buffer.Create();
- glNamedBufferData(read_buffer.handle, static_cast<GLsizeiptr>(Size()), nullptr,
- GL_STREAM_READ);
+void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
+ std::span<const VideoCommon::BufferCopy> copies) {
+ for (const VideoCommon::BufferCopy& copy : copies) {
+ glCopyNamedBufferSubData(
+ src_buffer.Handle(), dst_buffer.Handle(), static_cast<GLintptr>(copy.src_offset),
+ static_cast<GLintptr>(copy.dst_offset), static_cast<GLsizeiptr>(copy.size));
}
- glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
- glCopyNamedBufferSubData(gl_buffer.handle, read_buffer.handle, gl_offset, gl_offset, gl_size);
- glGetNamedBufferSubData(read_buffer.handle, gl_offset, gl_size, data);
}
-void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
- std::size_t size) {
- glCopyNamedBufferSubData(src.Handle(), Handle(), static_cast<GLintptr>(src_offset),
- static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size));
+void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
+ if (has_unified_vertex_buffers) {
+ buffer.MakeResident(GL_READ_ONLY);
+ glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset,
+ static_cast<GLsizeiptr>(size));
+ } else {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle());
+ index_buffer_offset = offset;
+ }
}
-OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
- const Device& device_, std::size_t stream_size)
- : GenericBufferCache{rasterizer, system,
- std::make_unique<OGLStreamBuffer>(device_, stream_size, true)},
- device{device_} {
- if (!device.HasFastBufferSubData()) {
+void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size,
+ u32 stride) {
+ if (index >= max_attributes) {
return;
}
-
- static constexpr GLsizeiptr size = static_cast<GLsizeiptr>(Maxwell::MaxConstBufferSize);
- glCreateBuffers(static_cast<GLsizei>(std::size(cbufs)), std::data(cbufs));
- for (const GLuint cbuf : cbufs) {
- glNamedBufferData(cbuf, size, nullptr, GL_STREAM_DRAW);
+ if (has_unified_vertex_buffers) {
+ buffer.MakeResident(GL_READ_ONLY);
+ glBindVertexBuffer(index, 0, 0, static_cast<GLsizei>(stride));
+ glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, index,
+ buffer.HostGpuAddr() + offset, static_cast<GLsizeiptr>(size));
+ } else {
+ glBindVertexBuffer(index, buffer.Handle(), static_cast<GLintptr>(offset),
+ static_cast<GLsizei>(stride));
}
}
-OGLBufferCache::~OGLBufferCache() {
- glDeleteBuffers(static_cast<GLsizei>(std::size(cbufs)), std::data(cbufs));
+void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer,
+ u32 offset, u32 size) {
+ if (use_assembly_shaders) {
+ GLuint handle;
+ if (offset != 0) {
+ handle = copy_uniforms[stage][binding_index].handle;
+ glCopyNamedBufferSubData(buffer.Handle(), handle, offset, 0, size);
+ } else {
+ handle = buffer.Handle();
+ }
+ glBindBufferRangeNV(PABO_LUT[stage], binding_index, handle, 0,
+ static_cast<GLsizeiptr>(size));
+ } else {
+ const GLuint base_binding = device.GetBaseBindings(stage).uniform_buffer;
+ const GLuint binding = base_binding + binding_index;
+ glBindBufferRange(GL_UNIFORM_BUFFER, binding, buffer.Handle(),
+ static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
+ }
}
-std::shared_ptr<Buffer> OGLBufferCache::CreateBlock(VAddr cpu_addr, std::size_t size) {
- return std::make_shared<Buffer>(device, cpu_addr, size);
+void BufferCacheRuntime::BindComputeUniformBuffer(u32 binding_index, Buffer& buffer, u32 offset,
+ u32 size) {
+ if (use_assembly_shaders) {
+ GLuint handle;
+ if (offset != 0) {
+ handle = copy_compute_uniforms[binding_index].handle;
+ glCopyNamedBufferSubData(buffer.Handle(), handle, offset, 0, size);
+ } else {
+ handle = buffer.Handle();
+ }
+ glBindBufferRangeNV(GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV, binding_index, handle, 0,
+ static_cast<GLsizeiptr>(size));
+ } else {
+ glBindBufferRange(GL_UNIFORM_BUFFER, binding_index, buffer.Handle(),
+ static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
+ }
}
-OGLBufferCache::BufferInfo OGLBufferCache::GetEmptyBuffer(std::size_t) {
- return {0, 0, 0};
+void BufferCacheRuntime::BindStorageBuffer(size_t stage, u32 binding_index, Buffer& buffer,
+ u32 offset, u32 size, bool is_written) {
+ if (use_assembly_shaders) {
+ const BindlessSSBO ssbo{
+ .address = buffer.HostGpuAddr() + offset,
+ .length = static_cast<GLsizei>(size),
+ .padding = 0,
+ };
+ buffer.MakeResident(is_written ? GL_READ_WRITE : GL_READ_ONLY);
+ glProgramLocalParametersI4uivNV(PROGRAM_LUT[stage], binding_index, 1,
+ reinterpret_cast<const GLuint*>(&ssbo));
+ } else {
+ const GLuint base_binding = device.GetBaseBindings(stage).shader_storage_buffer;
+ const GLuint binding = base_binding + binding_index;
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, buffer.Handle(),
+ static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
+ }
}
-OGLBufferCache::BufferInfo OGLBufferCache::ConstBufferUpload(const void* raw_pointer,
- std::size_t size) {
- DEBUG_ASSERT(cbuf_cursor < std::size(cbufs));
- const GLuint cbuf = cbufs[cbuf_cursor++];
+void BufferCacheRuntime::BindComputeStorageBuffer(u32 binding_index, Buffer& buffer, u32 offset,
+ u32 size, bool is_written) {
+ if (use_assembly_shaders) {
+ const BindlessSSBO ssbo{
+ .address = buffer.HostGpuAddr() + offset,
+ .length = static_cast<GLsizei>(size),
+ .padding = 0,
+ };
+ buffer.MakeResident(is_written ? GL_READ_WRITE : GL_READ_ONLY);
+ glProgramLocalParametersI4uivNV(GL_COMPUTE_PROGRAM_NV, binding_index, 1,
+ reinterpret_cast<const GLuint*>(&ssbo));
+ } else if (size == 0) {
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding_index, 0, 0, 0);
+ } else {
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding_index, buffer.Handle(),
+ static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
+ }
+}
- glNamedBufferSubData(cbuf, 0, static_cast<GLsizeiptr>(size), raw_pointer);
- return {cbuf, 0, 0};
+void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset,
+ u32 size) {
+ glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, buffer.Handle(),
+ static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 88fdc0536..d8b20a9af 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -5,76 +5,157 @@
#pragma once
#include <array>
-#include <memory>
+#include <span>
+#include "common/alignment.h"
#include "common/common_types.h"
+#include "common/dynamic_library.h"
#include "video_core/buffer_cache/buffer_cache.h"
-#include "video_core/engines/maxwell_3d.h"
+#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
-namespace Core {
-class System;
-}
-
namespace OpenGL {
-class Device;
-class OGLStreamBuffer;
-class RasterizerOpenGL;
+class BufferCacheRuntime;
-class Buffer : public VideoCommon::BufferBlock {
+class Buffer : public VideoCommon::BufferBase<VideoCore::RasterizerInterface> {
public:
- explicit Buffer(const Device& device, VAddr cpu_addr, std::size_t size);
- ~Buffer();
+ explicit Buffer(BufferCacheRuntime&, VideoCore::RasterizerInterface& rasterizer, VAddr cpu_addr,
+ u64 size_bytes);
+ explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams);
- void Upload(std::size_t offset, std::size_t size, const u8* data);
+ void ImmediateUpload(size_t offset, std::span<const u8> data) noexcept;
- void Download(std::size_t offset, std::size_t size, u8* data);
+ void ImmediateDownload(size_t offset, std::span<u8> data) noexcept;
- void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
- std::size_t size);
+ void MakeResident(GLenum access) noexcept;
- GLuint Handle() const noexcept {
- return gl_buffer.handle;
+ [[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept {
+ return address;
}
- u64 Address() const noexcept {
- return gpu_address;
+ [[nodiscard]] GLuint Handle() const noexcept {
+ return buffer.handle;
}
private:
- OGLBuffer gl_buffer;
- OGLBuffer read_buffer;
- u64 gpu_address = 0;
+ GLuint64EXT address = 0;
+ OGLBuffer buffer;
+ GLenum current_residency_access = GL_NONE;
};
-using GenericBufferCache = VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>;
-class OGLBufferCache final : public GenericBufferCache {
+class BufferCacheRuntime {
+ friend Buffer;
+
public:
- explicit OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
- const Device& device, std::size_t stream_size);
- ~OGLBufferCache();
+ static constexpr u8 INVALID_BINDING = std::numeric_limits<u8>::max();
+
+ explicit BufferCacheRuntime(const Device& device_);
+
+ void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
+ std::span<const VideoCommon::BufferCopy> copies);
+
+ void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
+
+ void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
+
+ void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
+
+ void BindComputeUniformBuffer(u32 binding_index, Buffer& buffer, u32 offset, u32 size);
+
+ void BindStorageBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size,
+ bool is_written);
+
+ void BindComputeStorageBuffer(u32 binding_index, Buffer& buffer, u32 offset, u32 size,
+ bool is_written);
+
+ void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size);
+
+ void BindFastUniformBuffer(size_t stage, u32 binding_index, u32 size) {
+ if (use_assembly_shaders) {
+ const GLuint handle = fast_uniforms[stage][binding_index].handle;
+ const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size);
+ glBindBufferRangeNV(PABO_LUT[stage], binding_index, handle, 0, gl_size);
+ } else {
+ const GLuint base_binding = device.GetBaseBindings(stage).uniform_buffer;
+ const GLuint binding = base_binding + binding_index;
+ glBindBufferRange(GL_UNIFORM_BUFFER, binding,
+ fast_uniforms[stage][binding_index].handle, 0,
+ static_cast<GLsizeiptr>(size));
+ }
+ }
- BufferInfo GetEmptyBuffer(std::size_t) override;
+ void PushFastUniformBuffer(size_t stage, u32 binding_index, std::span<const u8> data) {
+ if (use_assembly_shaders) {
+ glProgramBufferParametersIuivNV(
+ PABO_LUT[stage], binding_index, 0,
+ static_cast<GLsizei>(data.size_bytes() / sizeof(GLuint)),
+ reinterpret_cast<const GLuint*>(data.data()));
+ } else {
+ glNamedBufferSubData(fast_uniforms[stage][binding_index].handle, 0,
+ static_cast<GLsizeiptr>(data.size_bytes()), data.data());
+ }
+ }
- void Acquire() noexcept {
- cbuf_cursor = 0;
+ std::span<u8> BindMappedUniformBuffer(size_t stage, u32 binding_index, u32 size) noexcept {
+ const auto [mapped_span, offset] = stream_buffer->Request(static_cast<size_t>(size));
+ const GLuint base_binding = device.GetBaseBindings(stage).uniform_buffer;
+ const GLuint binding = base_binding + binding_index;
+ glBindBufferRange(GL_UNIFORM_BUFFER, binding, stream_buffer->Handle(),
+ static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
+ return mapped_span;
}
-protected:
- std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) override;
+ [[nodiscard]] const GLvoid* IndexOffset() const noexcept {
+ return reinterpret_cast<const GLvoid*>(static_cast<uintptr_t>(index_buffer_offset));
+ }
- BufferInfo ConstBufferUpload(const void* raw_pointer, std::size_t size) override;
+ [[nodiscard]] bool HasFastBufferSubData() const noexcept {
+ return has_fast_buffer_sub_data;
+ }
private:
- static constexpr std::size_t NUM_CBUFS = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers *
- Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram;
+ static constexpr std::array PABO_LUT{
+ GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
+ GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV, GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV,
+ GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV,
+ };
const Device& device;
- std::size_t cbuf_cursor = 0;
- std::array<GLuint, NUM_CBUFS> cbufs{};
+ bool has_fast_buffer_sub_data = false;
+ bool use_assembly_shaders = false;
+ bool has_unified_vertex_buffers = false;
+
+ u32 max_attributes = 0;
+
+ std::optional<StreamBuffer> stream_buffer;
+
+ std::array<std::array<OGLBuffer, VideoCommon::NUM_GRAPHICS_UNIFORM_BUFFERS>,
+ VideoCommon::NUM_STAGES>
+ fast_uniforms;
+ std::array<std::array<OGLBuffer, VideoCommon::NUM_GRAPHICS_UNIFORM_BUFFERS>,
+ VideoCommon::NUM_STAGES>
+ copy_uniforms;
+ std::array<OGLBuffer, VideoCommon::NUM_COMPUTE_UNIFORM_BUFFERS> copy_compute_uniforms;
+
+ u32 index_buffer_offset = 0;
+};
+
+struct BufferCacheParams {
+ using Runtime = OpenGL::BufferCacheRuntime;
+ using Buffer = OpenGL::Buffer;
+
+ static constexpr bool IS_OPENGL = true;
+ static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = true;
+ static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = true;
+ static constexpr bool NEEDS_BIND_UNIFORM_INDEX = true;
+ static constexpr bool NEEDS_BIND_STORAGE_INDEX = true;
+ static constexpr bool USE_MEMORY_MAPS = false;
};
+using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
+
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index e7d95149f..48d5c4a5e 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -5,9 +5,11 @@
#include <algorithm>
#include <array>
#include <cstddef>
+#include <cstdlib>
#include <cstring>
#include <limits>
#include <optional>
+#include <span>
#include <vector>
#include <glad/glad.h>
@@ -19,35 +21,35 @@
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
-
namespace {
-
// One uniform block is reserved for emulation purposes
constexpr u32 ReservedUniformBlocks = 1;
constexpr u32 NumStages = 5;
-constexpr std::array LimitUBOs = {
+constexpr std::array LIMIT_UBOS = {
GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS,
GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, GL_MAX_GEOMETRY_UNIFORM_BLOCKS,
- GL_MAX_FRAGMENT_UNIFORM_BLOCKS, GL_MAX_COMPUTE_UNIFORM_BLOCKS};
-
-constexpr std::array LimitSSBOs = {
+ GL_MAX_FRAGMENT_UNIFORM_BLOCKS, GL_MAX_COMPUTE_UNIFORM_BLOCKS,
+};
+constexpr std::array LIMIT_SSBOS = {
GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS,
GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS,
- GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS};
-
-constexpr std::array LimitSamplers = {GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,
- GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS,
- GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS,
- GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS,
- GL_MAX_TEXTURE_IMAGE_UNITS,
- GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS};
-
-constexpr std::array LimitImages = {
+ GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS,
+};
+constexpr std::array LIMIT_SAMPLERS = {
+ GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,
+ GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS,
+ GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS,
+ GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS,
+ GL_MAX_TEXTURE_IMAGE_UNITS,
+ GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS,
+};
+constexpr std::array LIMIT_IMAGES = {
GL_MAX_VERTEX_IMAGE_UNIFORMS, GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS,
GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS, GL_MAX_GEOMETRY_IMAGE_UNIFORMS,
- GL_MAX_FRAGMENT_IMAGE_UNIFORMS, GL_MAX_COMPUTE_IMAGE_UNIFORMS};
+ GL_MAX_FRAGMENT_IMAGE_UNIFORMS, GL_MAX_COMPUTE_IMAGE_UNIFORMS,
+};
template <typename T>
T GetInteger(GLenum pname) {
@@ -76,8 +78,8 @@ std::vector<std::string_view> GetExtensions() {
return extensions;
}
-bool HasExtension(const std::vector<std::string_view>& images, std::string_view extension) {
- return std::find(images.begin(), images.end(), extension) != images.end();
+bool HasExtension(std::span<const std::string_view> extensions, std::string_view extension) {
+ return std::ranges::find(extensions, extension) != extensions.end();
}
u32 Extract(u32& base, u32& num, u32 amount, std::optional<GLenum> limit = {}) {
@@ -91,8 +93,8 @@ u32 Extract(u32& base, u32& num, u32 amount, std::optional<GLenum> limit = {}) {
std::array<u32, Tegra::Engines::MaxShaderTypes> BuildMaxUniformBuffers() noexcept {
std::array<u32, Tegra::Engines::MaxShaderTypes> max;
- std::transform(LimitUBOs.begin(), LimitUBOs.end(), max.begin(),
- [](GLenum pname) { return GetInteger<u32>(pname); });
+ std::ranges::transform(LIMIT_UBOS, max.begin(),
+ [](GLenum pname) { return GetInteger<u32>(pname); });
return max;
}
@@ -115,9 +117,10 @@ std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindin
for (std::size_t i = 0; i < NumStages; ++i) {
const std::size_t stage = stage_swizzle[i];
bindings[stage] = {
- Extract(base_ubo, num_ubos, total_ubos / NumStages, LimitUBOs[stage]),
- Extract(base_ssbo, num_ssbos, total_ssbos / NumStages, LimitSSBOs[stage]),
- Extract(base_samplers, num_samplers, total_samplers / NumStages, LimitSamplers[stage])};
+ Extract(base_ubo, num_ubos, total_ubos / NumStages, LIMIT_UBOS[stage]),
+ Extract(base_ssbo, num_ssbos, total_ssbos / NumStages, LIMIT_SSBOS[stage]),
+ Extract(base_samplers, num_samplers, total_samplers / NumStages,
+ LIMIT_SAMPLERS[stage])};
}
u32 num_images = GetInteger<u32>(GL_MAX_IMAGE_UNITS);
@@ -130,7 +133,7 @@ std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindin
// Reserve at least 4 image bindings on the fragment stage.
bindings[4].image =
- Extract(base_images, num_images, std::max(4U, num_images / NumStages), LimitImages[4]);
+ Extract(base_images, num_images, std::max(4U, num_images / NumStages), LIMIT_IMAGES[4]);
// This is guaranteed to be at least 1.
const u32 total_extracted_images = num_images / (NumStages - 1);
@@ -142,7 +145,7 @@ std::array<Device::BaseBindings, Tegra::Engines::MaxShaderTypes> BuildBaseBindin
continue;
}
bindings[stage].image =
- Extract(base_images, num_images, total_extracted_images, LimitImages[stage]);
+ Extract(base_images, num_images, total_extracted_images, LIMIT_IMAGES[stage]);
}
// Compute doesn't care about any of this.
@@ -188,17 +191,24 @@ bool IsASTCSupported() {
return true;
}
+[[nodiscard]] bool IsDebugToolAttached(std::span<const std::string_view> extensions) {
+ const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
+ return nsight || HasExtension(extensions, "GL_EXT_debug_tool");
+}
} // Anonymous namespace
-Device::Device()
- : max_uniform_buffers{BuildMaxUniformBuffers()}, base_bindings{BuildBaseBindings()} {
+Device::Device() {
+ if (!GLAD_GL_VERSION_4_6) {
+ LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available");
+ throw std::runtime_error{"Insufficient version"};
+ }
const std::string_view vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
- const std::string_view renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
const std::vector extensions = GetExtensions();
const bool is_nvidia = vendor == "NVIDIA Corporation";
const bool is_amd = vendor == "ATI Technologies Inc.";
+ const bool is_intel = vendor == "Intel";
bool disable_fast_buffer_sub_data = false;
if (is_nvidia && version == "4.6.0 NVIDIA 443.24") {
@@ -208,8 +218,10 @@ Device::Device()
disable_fast_buffer_sub_data = true;
}
- uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
- shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
+ max_uniform_buffers = BuildMaxUniformBuffers();
+ base_bindings = BuildBaseBindings();
+ uniform_buffer_alignment = GetInteger<size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
+ shader_storage_alignment = GetInteger<size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS);
max_compute_shared_memory_size = GetInteger<u32>(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE);
@@ -223,8 +235,10 @@ Device::Device()
has_variable_aoffi = TestVariableAoffi();
has_component_indexing_bug = is_amd;
has_precise_bug = TestPreciseBug();
+ has_broken_texture_view_formats = is_amd || is_intel;
has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2;
has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory;
+ has_debugging_tool_attached = IsDebugToolAttached(extensions);
// At the moment of writing this, only Nvidia's driver optimizes BufferSubData on exclusive
// uniform buffers as "push constants"
@@ -235,10 +249,13 @@ Device::Device()
GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2;
use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue();
+ use_driver_cache = is_nvidia;
LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi);
LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug);
LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug);
+ LOG_INFO(Render_OpenGL, "Renderer_BrokenTextureViewFormats: {}",
+ has_broken_texture_view_formats);
if (Settings::values.use_assembly_shaders.GetValue() && !use_assembly_shaders) {
LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported");
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index 8a4b6b9fc..ee053776d 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -10,11 +10,9 @@
namespace OpenGL {
-static constexpr u32 EmulationUniformBlockBinding = 0;
-
-class Device final {
+class Device {
public:
- struct BaseBindings final {
+ struct BaseBindings {
u32 uniform_buffer{};
u32 shader_storage_buffer{};
u32 sampler{};
@@ -36,11 +34,11 @@ public:
return GetBaseBindings(static_cast<std::size_t>(shader_type));
}
- std::size_t GetUniformBufferAlignment() const {
+ size_t GetUniformBufferAlignment() const {
return uniform_buffer_alignment;
}
- std::size_t GetShaderStorageBufferAlignment() const {
+ size_t GetShaderStorageBufferAlignment() const {
return shader_storage_alignment;
}
@@ -96,6 +94,10 @@ public:
return has_precise_bug;
}
+ bool HasBrokenTextureViewFormats() const {
+ return has_broken_texture_view_formats;
+ }
+
bool HasFastBufferSubData() const {
return has_fast_buffer_sub_data;
}
@@ -104,6 +106,10 @@ public:
return has_nv_viewport_array2;
}
+ bool HasDebuggingToolAttached() const {
+ return has_debugging_tool_attached;
+ }
+
bool UseAssemblyShaders() const {
return use_assembly_shaders;
}
@@ -112,14 +118,18 @@ public:
return use_asynchronous_shaders;
}
+ bool UseDriverCache() const {
+ return use_driver_cache;
+ }
+
private:
static bool TestVariableAoffi();
static bool TestPreciseBug();
std::array<u32, Tegra::Engines::MaxShaderTypes> max_uniform_buffers{};
std::array<BaseBindings, Tegra::Engines::MaxShaderTypes> base_bindings{};
- std::size_t uniform_buffer_alignment{};
- std::size_t shader_storage_alignment{};
+ size_t uniform_buffer_alignment{};
+ size_t shader_storage_alignment{};
u32 max_vertex_attributes{};
u32 max_varyings{};
u32 max_compute_shared_memory_size{};
@@ -133,10 +143,13 @@ private:
bool has_variable_aoffi{};
bool has_component_indexing_bug{};
bool has_precise_bug{};
+ bool has_broken_texture_view_formats{};
bool has_fast_buffer_sub_data{};
bool has_nv_viewport_array2{};
+ bool has_debugging_tool_attached{};
bool use_assembly_shaders{};
bool use_asynchronous_shaders{};
+ bool use_driver_cache{};
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.cpp b/src/video_core/renderer_opengl/gl_fence_manager.cpp
index 3d2588dd2..151290101 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_fence_manager.cpp
@@ -11,10 +11,10 @@
namespace OpenGL {
-GLInnerFence::GLInnerFence(u32 payload, bool is_stubbed) : FenceBase(payload, is_stubbed) {}
+GLInnerFence::GLInnerFence(u32 payload_, bool is_stubbed_) : FenceBase{payload_, is_stubbed_} {}
-GLInnerFence::GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed)
- : FenceBase(address, payload, is_stubbed) {}
+GLInnerFence::GLInnerFence(GPUVAddr address_, u32 payload_, bool is_stubbed_)
+ : FenceBase{address_, payload_, is_stubbed_} {}
GLInnerFence::~GLInnerFence() = default;
@@ -45,11 +45,10 @@ void GLInnerFence::Wait() {
glClientWaitSync(sync_object.handle, 0, GL_TIMEOUT_IGNORED);
}
-FenceManagerOpenGL::FenceManagerOpenGL(Core::System& system,
- VideoCore::RasterizerInterface& rasterizer,
- TextureCacheOpenGL& texture_cache,
- OGLBufferCache& buffer_cache, QueryCache& query_cache)
- : GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, query_cache) {}
+FenceManagerOpenGL::FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::GPU& gpu_, TextureCache& texture_cache_,
+ BufferCache& buffer_cache_, QueryCache& query_cache_)
+ : GenericFenceManager{rasterizer_, gpu_, texture_cache_, buffer_cache_, query_cache_} {}
Fence FenceManagerOpenGL::CreateFence(u32 value, bool is_stubbed) {
return std::make_shared<GLInnerFence>(value, is_stubbed);
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.h b/src/video_core/renderer_opengl/gl_fence_manager.h
index 1686cf5c8..e714aa115 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.h
+++ b/src/video_core/renderer_opengl/gl_fence_manager.h
@@ -17,8 +17,8 @@ namespace OpenGL {
class GLInnerFence : public VideoCommon::FenceBase {
public:
- GLInnerFence(u32 payload, bool is_stubbed);
- GLInnerFence(GPUVAddr address, u32 payload, bool is_stubbed);
+ explicit GLInnerFence(u32 payload_, bool is_stubbed_);
+ explicit GLInnerFence(GPUVAddr address_, u32 payload_, bool is_stubbed_);
~GLInnerFence();
void Queue();
@@ -32,14 +32,13 @@ private:
};
using Fence = std::shared_ptr<GLInnerFence>;
-using GenericFenceManager =
- VideoCommon::FenceManager<Fence, TextureCacheOpenGL, OGLBufferCache, QueryCache>;
+using GenericFenceManager = VideoCommon::FenceManager<Fence, TextureCache, BufferCache, QueryCache>;
class FenceManagerOpenGL final : public GenericFenceManager {
public:
- FenceManagerOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache,
- QueryCache& query_cache);
+ explicit FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ TextureCache& texture_cache, BufferCache& buffer_cache,
+ QueryCache& query_cache);
protected:
Fence CreateFence(u32 value, bool is_stubbed) override;
diff --git a/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp b/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp
deleted file mode 100644
index b8a512cb6..000000000
--- a/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <tuple>
-#include <unordered_map>
-#include <utility>
-
-#include <glad/glad.h>
-
-#include "common/common_types.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/renderer_opengl/gl_framebuffer_cache.h"
-
-namespace OpenGL {
-
-using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-using VideoCore::Surface::SurfaceType;
-
-FramebufferCacheOpenGL::FramebufferCacheOpenGL() = default;
-
-FramebufferCacheOpenGL::~FramebufferCacheOpenGL() = default;
-
-GLuint FramebufferCacheOpenGL::GetFramebuffer(const FramebufferCacheKey& key) {
- const auto [entry, is_cache_miss] = cache.try_emplace(key);
- auto& framebuffer{entry->second};
- if (is_cache_miss) {
- framebuffer = CreateFramebuffer(key);
- }
- return framebuffer.handle;
-}
-
-OGLFramebuffer FramebufferCacheOpenGL::CreateFramebuffer(const FramebufferCacheKey& key) {
- OGLFramebuffer framebuffer;
- framebuffer.Create();
-
- // TODO(Rodrigo): Use DSA here after Nvidia fixes their framebuffer DSA bugs.
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.handle);
-
- if (key.zeta) {
- const bool stencil = key.zeta->GetSurfaceParams().type == SurfaceType::DepthStencil;
- const GLenum attach_target = stencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
- key.zeta->Attach(attach_target, GL_DRAW_FRAMEBUFFER);
- }
-
- std::size_t num_buffers = 0;
- std::array<GLenum, Maxwell::NumRenderTargets> targets;
-
- for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
- if (!key.colors[index]) {
- targets[index] = GL_NONE;
- continue;
- }
- const GLenum attach_target = GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index);
- key.colors[index]->Attach(attach_target, GL_DRAW_FRAMEBUFFER);
-
- const u32 attachment = (key.color_attachments >> (BitsPerAttachment * index)) & 0b1111;
- targets[index] = GL_COLOR_ATTACHMENT0 + attachment;
- num_buffers = index + 1;
- }
-
- if (num_buffers > 0) {
- glDrawBuffers(static_cast<GLsizei>(num_buffers), std::data(targets));
- } else {
- glDrawBuffer(GL_NONE);
- }
-
- return framebuffer;
-}
-
-std::size_t FramebufferCacheKey::Hash() const noexcept {
- std::size_t hash = std::hash<View>{}(zeta);
- for (const auto& color : colors) {
- hash ^= std::hash<View>{}(color);
- }
- hash ^= static_cast<std::size_t>(color_attachments) << 16;
- return hash;
-}
-
-bool FramebufferCacheKey::operator==(const FramebufferCacheKey& rhs) const noexcept {
- return std::tie(colors, zeta, color_attachments) ==
- std::tie(rhs.colors, rhs.zeta, rhs.color_attachments);
-}
-
-} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_framebuffer_cache.h b/src/video_core/renderer_opengl/gl_framebuffer_cache.h
deleted file mode 100644
index 8f698fee0..000000000
--- a/src/video_core/renderer_opengl/gl_framebuffer_cache.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <cstddef>
-#include <unordered_map>
-
-#include <glad/glad.h>
-
-#include "common/common_types.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/renderer_opengl/gl_resource_manager.h"
-#include "video_core/renderer_opengl/gl_texture_cache.h"
-
-namespace OpenGL {
-
-constexpr std::size_t BitsPerAttachment = 4;
-
-struct FramebufferCacheKey {
- View zeta;
- std::array<View, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors;
- u32 color_attachments = 0;
-
- std::size_t Hash() const noexcept;
-
- bool operator==(const FramebufferCacheKey& rhs) const noexcept;
-
- bool operator!=(const FramebufferCacheKey& rhs) const noexcept {
- return !operator==(rhs);
- }
-
- void SetAttachment(std::size_t index, u32 attachment) {
- color_attachments |= attachment << (BitsPerAttachment * index);
- }
-};
-
-} // namespace OpenGL
-
-namespace std {
-
-template <>
-struct hash<OpenGL::FramebufferCacheKey> {
- std::size_t operator()(const OpenGL::FramebufferCacheKey& k) const noexcept {
- return k.Hash();
- }
-};
-
-} // namespace std
-
-namespace OpenGL {
-
-class FramebufferCacheOpenGL {
-public:
- FramebufferCacheOpenGL();
- ~FramebufferCacheOpenGL();
-
- GLuint GetFramebuffer(const FramebufferCacheKey& key);
-
-private:
- OGLFramebuffer CreateFramebuffer(const FramebufferCacheKey& key);
-
- std::unordered_map<FramebufferCacheKey, OGLFramebuffer> cache;
-};
-
-} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index d7ba57aca..acebbf5f4 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -30,12 +30,9 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
} // Anonymous namespace
-QueryCache::QueryCache(Core::System& system, RasterizerOpenGL& gl_rasterizer)
- : VideoCommon::QueryCacheBase<
- QueryCache, CachedQuery, CounterStream, HostCounter,
- std::vector<OGLQuery>>{system,
- static_cast<VideoCore::RasterizerInterface&>(gl_rasterizer)},
- gl_rasterizer{gl_rasterizer} {}
+QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::MemoryManager& gpu_memory_)
+ : QueryCacheBase(rasterizer_, maxwell3d_, gpu_memory_), gl_rasterizer{rasterizer_} {}
QueryCache::~QueryCache() = default;
@@ -60,10 +57,11 @@ bool QueryCache::AnyCommandQueued() const noexcept {
return gl_rasterizer.AnyCommandQueued();
}
-HostCounter::HostCounter(QueryCache& cache, std::shared_ptr<HostCounter> dependency,
- VideoCore::QueryType type)
- : VideoCommon::HostCounterBase<QueryCache, HostCounter>{std::move(dependency)}, cache{cache},
- type{type}, query{cache.AllocateQuery(type)} {
+HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
+ VideoCore::QueryType type_)
+ : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_}, query{
+ cache.AllocateQuery(
+ type)} {
glBeginQuery(GetTarget(type), query.handle);
}
@@ -87,11 +85,14 @@ u64 HostCounter::BlockingQuery() const {
return static_cast<u64>(value);
}
-CachedQuery::CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr)
- : VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr}, cache{&cache}, type{type} {}
+CachedQuery::CachedQuery(QueryCache& cache_, VideoCore::QueryType type_, VAddr cpu_addr_,
+ u8* host_ptr_)
+ : CachedQueryBase{cpu_addr_, host_ptr_}, cache{&cache_}, type{type_} {}
+
+CachedQuery::~CachedQuery() = default;
CachedQuery::CachedQuery(CachedQuery&& rhs) noexcept
- : VideoCommon::CachedQueryBase<HostCounter>(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {}
+ : CachedQueryBase(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {}
CachedQuery& CachedQuery::operator=(CachedQuery&& rhs) noexcept {
cache = rhs.cache;
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index d8e7052a1..7bbe5cfe9 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -26,10 +26,11 @@ class RasterizerOpenGL;
using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
-class QueryCache final : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream,
- HostCounter, std::vector<OGLQuery>> {
+class QueryCache final
+ : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
- explicit QueryCache(Core::System& system, RasterizerOpenGL& rasterizer);
+ explicit QueryCache(RasterizerOpenGL& rasterizer_, Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::MemoryManager& gpu_memory_);
~QueryCache();
OGLQuery AllocateQuery(VideoCore::QueryType type);
@@ -40,12 +41,13 @@ public:
private:
RasterizerOpenGL& gl_rasterizer;
+ std::array<std::vector<OGLQuery>, VideoCore::NumQueryTypes> query_pools;
};
class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> {
public:
- explicit HostCounter(QueryCache& cache, std::shared_ptr<HostCounter> dependency,
- VideoCore::QueryType type);
+ explicit HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
+ VideoCore::QueryType type_);
~HostCounter();
void EndQuery();
@@ -60,12 +62,14 @@ private:
class CachedQuery final : public VideoCommon::CachedQueryBase<HostCounter> {
public:
- explicit CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr,
- u8* host_ptr);
- CachedQuery(CachedQuery&& rhs) noexcept;
- CachedQuery(const CachedQuery&) = delete;
+ explicit CachedQuery(QueryCache& cache_, VideoCore::QueryType type_, VAddr cpu_addr_,
+ u8* host_ptr_);
+ ~CachedQuery() override;
+ CachedQuery(CachedQuery&& rhs) noexcept;
CachedQuery& operator=(CachedQuery&& rhs) noexcept;
+
+ CachedQuery(const CachedQuery&) = delete;
CachedQuery& operator=(const CachedQuery&) = delete;
void Flush() override;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 4af5824cd..418644108 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -25,48 +25,49 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/shader_type.h"
#include "video_core/memory_manager.h"
+#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_query_cache.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
+#include "video_core/renderer_opengl/gl_texture_cache.h"
#include "video_core/renderer_opengl/maxwell_to_gl.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/shader_cache.h"
+#include "video_core/texture_cache/texture_cache.h"
namespace OpenGL {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using GLvec4 = std::array<GLfloat, 4>;
using Tegra::Engines::ShaderType;
using VideoCore::Surface::PixelFormat;
using VideoCore::Surface::SurfaceTarget;
using VideoCore::Surface::SurfaceType;
-MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Format Setup", MP_RGB(128, 128, 192));
-MICROPROFILE_DEFINE(OpenGL_VB, "OpenGL", "Vertex Buffer Setup", MP_RGB(128, 128, 192));
-MICROPROFILE_DEFINE(OpenGL_Shader, "OpenGL", "Shader Setup", MP_RGB(128, 128, 192));
-MICROPROFILE_DEFINE(OpenGL_UBO, "OpenGL", "Const Buffer Setup", MP_RGB(128, 128, 192));
-MICROPROFILE_DEFINE(OpenGL_Index, "OpenGL", "Index Buffer Setup", MP_RGB(128, 128, 192));
-MICROPROFILE_DEFINE(OpenGL_Texture, "OpenGL", "Texture Setup", MP_RGB(128, 128, 192));
-MICROPROFILE_DEFINE(OpenGL_Framebuffer, "OpenGL", "Framebuffer Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
+MICROPROFILE_DEFINE(OpenGL_Clears, "OpenGL", "Clears", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192));
-MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
-MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100));
+MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Management", MP_RGB(100, 255, 100));
namespace {
-constexpr std::size_t NUM_CONST_BUFFERS_PER_STAGE = 18;
-constexpr std::size_t NUM_CONST_BUFFERS_BYTES_PER_STAGE =
- NUM_CONST_BUFFERS_PER_STAGE * Maxwell::MaxConstBufferSize;
-constexpr std::size_t TOTAL_CONST_BUFFER_BYTES =
- NUM_CONST_BUFFERS_BYTES_PER_STAGE * Maxwell::MaxShaderStage;
+constexpr size_t NUM_SUPPORTED_VERTEX_ATTRIBUTES = 16;
-constexpr std::size_t NUM_SUPPORTED_VERTEX_ATTRIBUTES = 16;
-constexpr std::size_t NUM_SUPPORTED_VERTEX_BINDINGS = 16;
+struct TextureHandle {
+ constexpr TextureHandle(u32 data, bool via_header_index) {
+ const Tegra::Texture::TextureHandle handle{data};
+ image = handle.tic_id;
+ sampler = via_header_index ? image : handle.tsc_id.Value();
+ }
+
+ u32 image;
+ u32 sampler;
+};
template <typename Engine, typename Entry>
-Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry,
- ShaderType shader_type, std::size_t index = 0) {
+TextureHandle GetTextureInfo(const Engine& engine, bool via_header_index, const Entry& entry,
+ ShaderType shader_type, size_t index = 0) {
if constexpr (std::is_same_v<Entry, SamplerEntry>) {
if (entry.is_separated) {
const u32 buffer_1 = entry.buffer;
@@ -75,36 +76,16 @@ Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry
const u32 offset_2 = entry.secondary_offset;
const u32 handle_1 = engine.AccessConstBuffer32(shader_type, buffer_1, offset_1);
const u32 handle_2 = engine.AccessConstBuffer32(shader_type, buffer_2, offset_2);
- return engine.GetTextureInfo(handle_1 | handle_2);
+ return TextureHandle(handle_1 | handle_2, via_header_index);
}
}
if (entry.is_bindless) {
- const u32 handle = engine.AccessConstBuffer32(shader_type, entry.buffer, entry.offset);
- return engine.GetTextureInfo(handle);
- }
-
- const auto& gpu_profile = engine.AccessGuestDriverProfile();
- const u32 offset = entry.offset + static_cast<u32>(index * gpu_profile.GetTextureHandlerSize());
- if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) {
- return engine.GetStageTexture(shader_type, offset);
- } else {
- return engine.GetTexture(offset);
- }
-}
-
-std::size_t GetConstBufferSize(const Tegra::Engines::ConstBufferInfo& buffer,
- const ConstBufferEntry& entry) {
- if (!entry.IsIndirect()) {
- return entry.GetSize();
+ const u32 raw = engine.AccessConstBuffer32(shader_type, entry.buffer, entry.offset);
+ return TextureHandle(raw, via_header_index);
}
-
- if (buffer.size > Maxwell::MaxConstBufferSize) {
- LOG_WARNING(Render_OpenGL, "Indirect constbuffer size {} exceeds maximum {}", buffer.size,
- Maxwell::MaxConstBufferSize);
- return Maxwell::MaxConstBufferSize;
- }
-
- return buffer.size;
+ const u32 buffer = engine.GetBoundBuffer();
+ const u64 offset = (entry.offset + index) * sizeof(u32);
+ return TextureHandle(engine.AccessConstBuffer32(shader_type, buffer, offset), via_header_index);
}
/// Translates hardware transform feedback indices
@@ -131,7 +112,7 @@ std::pair<GLint, GLint> TransformFeedbackEnum(u8 location) {
case 43:
return {GL_BACK_SECONDARY_COLOR_NV, 0};
}
- UNIMPLEMENTED_MSG("index={}", static_cast<int>(index));
+ UNIMPLEMENTED_MSG("index={}", index);
return {GL_POSITION, 0};
}
@@ -139,72 +120,74 @@ void oglEnable(GLenum cap, bool state) {
(state ? glEnable : glDisable)(cap);
}
-void UpdateBindlessPointers(GLenum target, GLuint64EXT* pointers, std::size_t num_entries) {
- if (num_entries == 0) {
- return;
+ImageViewType ImageViewTypeFromEntry(const SamplerEntry& entry) {
+ if (entry.is_buffer) {
+ return ImageViewType::Buffer;
}
- if (num_entries % 2 == 1) {
- pointers[num_entries] = 0;
+ switch (entry.type) {
+ case Tegra::Shader::TextureType::Texture1D:
+ return entry.is_array ? ImageViewType::e1DArray : ImageViewType::e1D;
+ case Tegra::Shader::TextureType::Texture2D:
+ return entry.is_array ? ImageViewType::e2DArray : ImageViewType::e2D;
+ case Tegra::Shader::TextureType::Texture3D:
+ return ImageViewType::e3D;
+ case Tegra::Shader::TextureType::TextureCube:
+ return entry.is_array ? ImageViewType::CubeArray : ImageViewType::Cube;
}
- const GLsizei num_vectors = static_cast<GLsizei>((num_entries + 1) / 2);
- glProgramLocalParametersI4uivNV(target, 0, num_vectors,
- reinterpret_cast<const GLuint*>(pointers));
+ UNREACHABLE();
+ return ImageViewType::e2D;
}
-} // Anonymous namespace
-
-RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
- const Device& device, ScreenInfo& info,
- ProgramManager& program_manager, StateTracker& state_tracker)
- : RasterizerAccelerated{system.Memory()}, device{device}, texture_cache{system, *this, device,
- state_tracker},
- shader_cache{*this, system, emu_window, device}, query_cache{system, *this},
- buffer_cache{*this, system, device, STREAM_BUFFER_SIZE},
- fence_manager{system, *this, texture_cache, buffer_cache, query_cache}, system{system},
- screen_info{info}, program_manager{program_manager}, state_tracker{state_tracker},
- async_shaders{emu_window} {
- CheckExtensions();
-
- unified_uniform_buffer.Create();
- glNamedBufferStorage(unified_uniform_buffer.handle, TOTAL_CONST_BUFFER_BYTES, nullptr, 0);
-
- if (device.UseAssemblyShaders()) {
- glCreateBuffers(static_cast<GLsizei>(staging_cbufs.size()), staging_cbufs.data());
- for (const GLuint cbuf : staging_cbufs) {
- glNamedBufferStorage(cbuf, static_cast<GLsizeiptr>(Maxwell::MaxConstBufferSize),
- nullptr, 0);
- }
+ImageViewType ImageViewTypeFromEntry(const ImageEntry& entry) {
+ switch (entry.type) {
+ case Tegra::Shader::ImageType::Texture1D:
+ return ImageViewType::e1D;
+ case Tegra::Shader::ImageType::Texture1DArray:
+ return ImageViewType::e1DArray;
+ case Tegra::Shader::ImageType::Texture2D:
+ return ImageViewType::e2D;
+ case Tegra::Shader::ImageType::Texture2DArray:
+ return ImageViewType::e2DArray;
+ case Tegra::Shader::ImageType::Texture3D:
+ return ImageViewType::e3D;
+ case Tegra::Shader::ImageType::TextureBuffer:
+ return ImageViewType::Buffer;
}
+ UNREACHABLE();
+ return ImageViewType::e2D;
+}
+
+} // Anonymous namespace
+RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
+ Core::Memory::Memory& cpu_memory_, const Device& device_,
+ ScreenInfo& screen_info_, ProgramManager& program_manager_,
+ StateTracker& state_tracker_)
+ : RasterizerAccelerated(cpu_memory_), gpu(gpu_), maxwell3d(gpu.Maxwell3D()),
+ kepler_compute(gpu.KeplerCompute()), gpu_memory(gpu.MemoryManager()), device(device_),
+ screen_info(screen_info_), program_manager(program_manager_), state_tracker(state_tracker_),
+ texture_cache_runtime(device, program_manager, state_tracker),
+ texture_cache(texture_cache_runtime, *this, maxwell3d, kepler_compute, gpu_memory),
+ buffer_cache_runtime(device),
+ buffer_cache(*this, maxwell3d, kepler_compute, gpu_memory, cpu_memory_, buffer_cache_runtime),
+ shader_cache(*this, emu_window_, gpu, maxwell3d, kepler_compute, gpu_memory, device),
+ query_cache(*this, maxwell3d, gpu_memory),
+ fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache),
+ async_shaders(emu_window_) {
if (device.UseAsynchronousShaders()) {
async_shaders.AllocateWorkers();
}
}
-RasterizerOpenGL::~RasterizerOpenGL() {
- if (device.UseAssemblyShaders()) {
- glDeleteBuffers(static_cast<GLsizei>(staging_cbufs.size()), staging_cbufs.data());
- }
-}
-
-void RasterizerOpenGL::CheckExtensions() {
- if (!GLAD_GL_ARB_texture_filter_anisotropic && !GLAD_GL_EXT_texture_filter_anisotropic) {
- LOG_WARNING(
- Render_OpenGL,
- "Anisotropic filter is not supported! This can cause graphical issues in some games.");
- }
-}
+RasterizerOpenGL::~RasterizerOpenGL() = default;
-void RasterizerOpenGL::SetupVertexFormat() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+void RasterizerOpenGL::SyncVertexFormats() {
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::VertexFormats]) {
return;
}
flags[Dirty::VertexFormats] = false;
- MICROPROFILE_SCOPE(OpenGL_VAO);
-
// Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. Enables
// the first 16 vertex attributes always, as we don't know which ones are actually used until
// shader time. Note, Tegra technically supports 32, but we're capping this to 16 for now to
@@ -217,7 +200,7 @@ void RasterizerOpenGL::SetupVertexFormat() {
}
flags[Dirty::VertexFormat0 + index] = false;
- const auto attrib = gpu.regs.vertex_attrib_format[index];
+ const auto attrib = maxwell3d.regs.vertex_attrib_format[index];
const auto gl_index = static_cast<GLuint>(index);
// Disable constant attributes.
@@ -240,64 +223,14 @@ void RasterizerOpenGL::SetupVertexFormat() {
}
}
-void RasterizerOpenGL::SetupVertexBuffer() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- if (!flags[Dirty::VertexBuffers]) {
- return;
- }
- flags[Dirty::VertexBuffers] = false;
-
- MICROPROFILE_SCOPE(OpenGL_VB);
-
- const bool use_unified_memory = device.HasVertexBufferUnifiedMemory();
-
- // Upload all guest vertex arrays sequentially to our buffer
- const auto& regs = gpu.regs;
- for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_BINDINGS; ++index) {
- if (!flags[Dirty::VertexBuffer0 + index]) {
- continue;
- }
- flags[Dirty::VertexBuffer0 + index] = false;
-
- const auto& vertex_array = regs.vertex_array[index];
- if (!vertex_array.IsEnabled()) {
- continue;
- }
-
- const GPUVAddr start = vertex_array.StartAddress();
- const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
- ASSERT(end >= start);
-
- const GLuint gl_index = static_cast<GLuint>(index);
- const u64 size = end - start;
- if (size == 0) {
- glBindVertexBuffer(gl_index, 0, 0, vertex_array.stride);
- if (use_unified_memory) {
- glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, gl_index, 0, 0);
- }
- continue;
- }
- const auto info = buffer_cache.UploadMemory(start, size);
- if (use_unified_memory) {
- glBindVertexBuffer(gl_index, 0, 0, vertex_array.stride);
- glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, gl_index,
- info.address + info.offset, size);
- } else {
- glBindVertexBuffer(gl_index, info.handle, info.offset, vertex_array.stride);
- }
- }
-}
-
-void RasterizerOpenGL::SetupVertexInstances() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+void RasterizerOpenGL::SyncVertexInstances() {
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::VertexInstances]) {
return;
}
flags[Dirty::VertexInstances] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_ATTRIBUTES; ++index) {
if (!flags[Dirty::VertexInstance0 + index]) {
continue;
@@ -311,26 +244,21 @@ void RasterizerOpenGL::SetupVertexInstances() {
}
}
-GLintptr RasterizerOpenGL::SetupIndexBuffer() {
- MICROPROFILE_SCOPE(OpenGL_Index);
- const auto& regs = system.GPU().Maxwell3D().regs;
- const std::size_t size = CalculateIndexBufferSize();
- const auto info = buffer_cache.UploadMemory(regs.index_array.IndexStart(), size);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, info.handle);
- return info.offset;
-}
-
-void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
- MICROPROFILE_SCOPE(OpenGL_Shader);
- auto& gpu = system.GPU().Maxwell3D();
+void RasterizerOpenGL::SetupShaders(bool is_indexed) {
u32 clip_distances = 0;
+ std::array<Shader*, Maxwell::MaxShaderStage> shaders{};
+ image_view_indices.clear();
+ sampler_handles.clear();
+
+ texture_cache.SynchronizeGraphicsDescriptors();
+
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
- const auto& shader_config = gpu.regs.shader_config[index];
+ const auto& shader_config = maxwell3d.regs.shader_config[index];
const auto program{static_cast<Maxwell::ShaderProgram>(index)};
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
switch (program) {
case Maxwell::ShaderProgram::Geometry:
program_manager.UseGeometryShader(0);
@@ -343,7 +271,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
}
continue;
}
-
// Currently this stages are not supported in the OpenGL backend.
// TODO(Blinkhawk): Port tesselation shaders from Vulkan to OpenGL
if (program == Maxwell::ShaderProgram::TesselationControl ||
@@ -352,7 +279,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
}
Shader* const shader = shader_cache.GetStageProgram(program, async_shaders);
-
const GLuint program_handle = shader->IsBuilt() ? shader->GetHandle() : 0;
switch (program) {
case Maxwell::ShaderProgram::VertexA:
@@ -368,14 +294,25 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
default:
UNIMPLEMENTED_MSG("Unimplemented shader index={}, enable={}, offset=0x{:08X}", index,
shader_config.enable.Value(), shader_config.offset);
+ break;
}
// Stage indices are 0 - 5
- const std::size_t stage = index == 0 ? 0 : index - 1;
- SetupDrawConstBuffers(stage, shader);
- SetupDrawGlobalMemory(stage, shader);
- SetupDrawTextures(stage, shader);
- SetupDrawImages(stage, shader);
+ const size_t stage = index == 0 ? 0 : index - 1;
+ shaders[stage] = shader;
+
+ SetupDrawTextures(shader, stage);
+ SetupDrawImages(shader, stage);
+
+ buffer_cache.SetEnabledUniformBuffers(stage, shader->GetEntries().enabled_uniform_buffers);
+
+ buffer_cache.UnbindGraphicsStorageBuffers(stage);
+ u32 ssbo_index = 0;
+ for (const auto& buffer : shader->GetEntries().global_memory_entries) {
+ buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, buffer.cbuf_index,
+ buffer.cbuf_offset, buffer.is_written);
+ ++ssbo_index;
+ }
// Workaround for Intel drivers.
// When a clip distance is enabled but not set in the shader it crops parts of the screen
@@ -389,146 +326,43 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
++index;
}
}
-
SyncClipEnabled(clip_distances);
- gpu.dirty.flags[Dirty::Shaders] = false;
-}
-
-std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
-
- std::size_t size = 0;
- for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
- if (!regs.vertex_array[index].IsEnabled())
- continue;
-
- const GPUVAddr start = regs.vertex_array[index].StartAddress();
- const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
+ maxwell3d.dirty.flags[Dirty::Shaders] = false;
- size += end - start;
- ASSERT(end >= start);
- }
-
- return size;
-}
-
-std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
-
- return static_cast<std::size_t>(regs.index_array.count) *
- static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
-}
-
-void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading,
- const VideoCore::DiskResourceLoadCallback& callback) {
- shader_cache.LoadDiskCache(stop_loading, callback);
-}
-
-void RasterizerOpenGL::SetupDirtyFlags() {
- state_tracker.Initialize();
-}
-
-void RasterizerOpenGL::ConfigureFramebuffers() {
- MICROPROFILE_SCOPE(OpenGL_Framebuffer);
- auto& gpu = system.GPU().Maxwell3D();
- if (!gpu.dirty.flags[VideoCommon::Dirty::RenderTargets]) {
- return;
- }
- gpu.dirty.flags[VideoCommon::Dirty::RenderTargets] = false;
+ buffer_cache.UpdateGraphicsBuffers(is_indexed);
- texture_cache.GuardRenderTargets(true);
+ const std::span indices_span(image_view_indices.data(), image_view_indices.size());
+ texture_cache.FillGraphicsImageViews(indices_span, image_view_ids);
- View depth_surface = texture_cache.GetDepthBufferSurface(true);
+ buffer_cache.BindHostGeometryBuffers(is_indexed);
- const auto& regs = gpu.regs;
- UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0);
-
- // Bind the framebuffer surfaces
- FramebufferCacheKey key;
- const auto colors_count = static_cast<std::size_t>(regs.rt_control.count);
- for (std::size_t index = 0; index < colors_count; ++index) {
- View color_surface{texture_cache.GetColorBufferSurface(index, true)};
- if (!color_surface) {
+ size_t image_view_index = 0;
+ size_t texture_index = 0;
+ size_t image_index = 0;
+ for (size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) {
+ const Shader* const shader = shaders[stage];
+ if (!shader) {
continue;
}
- // Assume that a surface will be written to if it is used as a framebuffer, even
- // if the shader doesn't actually write to it.
- texture_cache.MarkColorBufferInUse(index);
-
- key.SetAttachment(index, regs.rt_control.GetMap(index));
- key.colors[index] = std::move(color_surface);
+ buffer_cache.BindHostStageBuffers(stage);
+ const auto& base = device.GetBaseBindings(stage);
+ BindTextures(shader->GetEntries(), base.sampler, base.image, image_view_index,
+ texture_index, image_index);
}
-
- if (depth_surface) {
- // Assume that a surface will be written to if it is used as a framebuffer, even if
- // the shader doesn't actually write to it.
- texture_cache.MarkDepthBufferInUse();
- key.zeta = std::move(depth_surface);
- }
-
- texture_cache.GuardRenderTargets(false);
-
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_cache.GetFramebuffer(key));
}
-void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil) {
- auto& gpu = system.GPU().Maxwell3D();
- const auto& regs = gpu.regs;
-
- texture_cache.GuardRenderTargets(true);
- View color_surface;
-
- if (using_color) {
- // Determine if we have to preserve the contents.
- // First we have to make sure all clear masks are enabled.
- bool preserve_contents = !regs.clear_buffers.R || !regs.clear_buffers.G ||
- !regs.clear_buffers.B || !regs.clear_buffers.A;
- const std::size_t index = regs.clear_buffers.RT;
- if (regs.clear_flags.scissor) {
- // Then we have to confirm scissor testing clears the whole image.
- const auto& scissor = regs.scissor_test[0];
- preserve_contents |= scissor.min_x > 0;
- preserve_contents |= scissor.min_y > 0;
- preserve_contents |= scissor.max_x < regs.rt[index].width;
- preserve_contents |= scissor.max_y < regs.rt[index].height;
- }
-
- color_surface = texture_cache.GetColorBufferSurface(index, preserve_contents);
- texture_cache.MarkColorBufferInUse(index);
- }
-
- View depth_surface;
- if (using_depth_stencil) {
- bool preserve_contents = false;
- if (regs.clear_flags.scissor) {
- // For depth stencil clears we only have to confirm scissor test covers the whole image.
- const auto& scissor = regs.scissor_test[0];
- preserve_contents |= scissor.min_x > 0;
- preserve_contents |= scissor.min_y > 0;
- preserve_contents |= scissor.max_x < regs.zeta_width;
- preserve_contents |= scissor.max_y < regs.zeta_height;
- }
-
- depth_surface = texture_cache.GetDepthBufferSurface(preserve_contents);
- texture_cache.MarkDepthBufferInUse();
- }
- texture_cache.GuardRenderTargets(false);
-
- FramebufferCacheKey key;
- key.colors[0] = std::move(color_surface);
- key.zeta = std::move(depth_surface);
-
- state_tracker.NotifyFramebuffer();
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_cache.GetFramebuffer(key));
+void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
+ const VideoCore::DiskResourceLoadCallback& callback) {
+ shader_cache.LoadDiskCache(title_id, stop_loading, callback);
}
void RasterizerOpenGL::Clear() {
- const auto& gpu = system.GPU().Maxwell3D();
- if (!gpu.ShouldExecute()) {
+ MICROPROFILE_SCOPE(OpenGL_Clears);
+ if (!maxwell3d.ShouldExecute()) {
return;
}
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
bool use_color{};
bool use_depth{};
bool use_stencil{};
@@ -537,8 +371,9 @@ void RasterizerOpenGL::Clear() {
regs.clear_buffers.A) {
use_color = true;
- state_tracker.NotifyColorMask0();
- glColorMaski(0, regs.clear_buffers.R != 0, regs.clear_buffers.G != 0,
+ const GLuint index = regs.clear_buffers.RT;
+ state_tracker.NotifyColorMask(index);
+ glColorMaski(index, regs.clear_buffers.R != 0, regs.clear_buffers.G != 0,
regs.clear_buffers.B != 0, regs.clear_buffers.A != 0);
// TODO(Rodrigo): Determine if clamping is used on clears
@@ -571,15 +406,15 @@ void RasterizerOpenGL::Clear() {
state_tracker.NotifyScissor0();
glDisablei(GL_SCISSOR_TEST, 0);
}
-
UNIMPLEMENTED_IF(regs.clear_flags.viewport);
- ConfigureClearFramebuffer(use_color, use_depth || use_stencil);
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.UpdateRenderTargets(true);
+ state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle());
if (use_color) {
- glClearBufferfv(GL_COLOR, 0, regs.clear_color);
+ glClearBufferfv(GL_COLOR, regs.clear_buffers.RT, regs.clear_color);
}
-
if (use_depth && use_stencil) {
glClearBufferfi(GL_DEPTH_STENCIL, 0, regs.clear_depth, regs.clear_stencil);
} else if (use_depth) {
@@ -587,115 +422,35 @@ void RasterizerOpenGL::Clear() {
} else if (use_stencil) {
glClearBufferiv(GL_STENCIL, 0, &regs.clear_stencil);
}
-
++num_queued_commands;
}
void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
MICROPROFILE_SCOPE(OpenGL_Drawing);
- auto& gpu = system.GPU().Maxwell3D();
query_cache.UpdateCounters();
- SyncViewport();
- SyncRasterizeEnable();
- SyncPolygonModes();
- SyncColorMask();
- SyncFragmentColorClampState();
- SyncMultiSampleState();
- SyncDepthTestState();
- SyncDepthClamp();
- SyncStencilTestState();
- SyncBlendState();
- SyncLogicOpState();
- SyncCullMode();
- SyncPrimitiveRestart();
- SyncScissorTest();
- SyncPointState();
- SyncLineState();
- SyncPolygonOffset();
- SyncAlphaTest();
- SyncFramebufferSRGB();
-
- buffer_cache.Acquire();
- current_cbuf = 0;
-
- std::size_t buffer_size = CalculateVertexArraysSize();
-
- // Add space for index buffer
- if (is_indexed) {
- buffer_size = Common::AlignUp(buffer_size, 4) + CalculateIndexBufferSize();
- }
-
- // Uniform space for the 5 shader stages
- buffer_size =
- Common::AlignUp<std::size_t>(buffer_size, 4) +
- (sizeof(MaxwellUniformData) + device.GetUniformBufferAlignment()) * Maxwell::MaxShaderStage;
-
- // Add space for at least 18 constant buffers
- buffer_size += Maxwell::MaxConstBuffers *
- (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment());
-
- // Prepare the vertex array.
- const bool invalidated = buffer_cache.Map(buffer_size);
-
- if (invalidated) {
- // When the stream buffer has been invalidated, we have to consider vertex buffers as dirty
- auto& dirty = gpu.dirty.flags;
- dirty[Dirty::VertexBuffers] = true;
- for (int index = Dirty::VertexBuffer0; index <= Dirty::VertexBuffer31; ++index) {
- dirty[index] = true;
- }
- }
-
- // Prepare vertex array format.
- SetupVertexFormat();
-
- // Upload vertex and index data.
- SetupVertexBuffer();
- SetupVertexInstances();
- GLintptr index_buffer_offset = 0;
- if (is_indexed) {
- index_buffer_offset = SetupIndexBuffer();
- }
-
- // Setup emulation uniform buffer.
- if (!device.UseAssemblyShaders()) {
- MaxwellUniformData ubo;
- ubo.SetFromRegs(gpu);
- const auto info =
- buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment());
- glBindBufferRange(GL_UNIFORM_BUFFER, EmulationUniformBlockBinding, info.handle, info.offset,
- static_cast<GLsizeiptr>(sizeof(ubo)));
- }
+ SyncState();
// Setup shaders and their used resources.
- texture_cache.GuardSamplers(true);
- const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology);
- SetupShaders(primitive_mode);
- texture_cache.GuardSamplers(false);
-
- ConfigureFramebuffers();
-
- // Signal the buffer cache that we are not going to upload more things.
- buffer_cache.Unmap();
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ SetupShaders(is_indexed);
+ texture_cache.UpdateRenderTargets(false);
+ state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle());
program_manager.BindGraphicsPipeline();
- if (texture_cache.TextureBarrier()) {
- glTextureBarrier();
- }
-
+ const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d.regs.draw.topology);
BeginTransformFeedback(primitive_mode);
- const GLuint base_instance = static_cast<GLuint>(gpu.regs.vb_base_instance);
+ const GLuint base_instance = static_cast<GLuint>(maxwell3d.regs.vb_base_instance);
const GLsizei num_instances =
- static_cast<GLsizei>(is_instanced ? gpu.mme_draw.instance_count : 1);
+ static_cast<GLsizei>(is_instanced ? maxwell3d.mme_draw.instance_count : 1);
if (is_indexed) {
- const GLint base_vertex = static_cast<GLint>(gpu.regs.vb_element_base);
- const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.index_array.count);
- const GLvoid* offset = reinterpret_cast<const GLvoid*>(index_buffer_offset);
- const GLenum format = MaxwellToGL::IndexFormat(gpu.regs.index_array.format);
+ const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vb_element_base);
+ const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.index_array.count);
+ const GLvoid* const offset = buffer_cache_runtime.IndexOffset();
+ const GLenum format = MaxwellToGL::IndexFormat(maxwell3d.regs.index_array.format);
if (num_instances == 1 && base_instance == 0 && base_vertex == 0) {
glDrawElements(primitive_mode, num_vertices, format, offset);
} else if (num_instances == 1 && base_instance == 0) {
@@ -714,8 +469,8 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
base_instance);
}
} else {
- const GLint base_vertex = static_cast<GLint>(gpu.regs.vertex_buffer.first);
- const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.vertex_buffer.count);
+ const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vertex_buffer.first);
+ const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.vertex_buffer.count);
if (num_instances == 1 && base_instance == 0) {
glDrawArrays(primitive_mode, base_vertex, num_vertices);
} else if (base_instance == 0) {
@@ -730,30 +485,28 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
++num_queued_commands;
- system.GPU().TickWork();
+ gpu.TickWork();
}
void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
- buffer_cache.Acquire();
- current_cbuf = 0;
-
- auto kernel = shader_cache.GetComputeKernel(code_addr);
- program_manager.BindCompute(kernel->GetHandle());
-
- SetupComputeTextures(kernel);
- SetupComputeImages(kernel);
+ Shader* const kernel = shader_cache.GetComputeKernel(code_addr);
- const std::size_t buffer_size =
- Tegra::Engines::KeplerCompute::NumConstBuffers *
- (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment());
- buffer_cache.Map(buffer_size);
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
+ BindComputeTextures(kernel);
- SetupComputeConstBuffers(kernel);
- SetupComputeGlobalMemory(kernel);
-
- buffer_cache.Unmap();
-
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& entries = kernel->GetEntries();
+ buffer_cache.SetEnabledComputeUniformBuffers(entries.enabled_uniform_buffers);
+ buffer_cache.UnbindComputeStorageBuffers();
+ u32 ssbo_index = 0;
+ for (const auto& buffer : entries.global_memory_entries) {
+ buffer_cache.BindComputeStorageBuffer(ssbo_index, buffer.cbuf_index, buffer.cbuf_offset,
+ buffer.is_written);
+ ++ssbo_index;
+ }
+ buffer_cache.UpdateComputeBuffers();
+ buffer_cache.BindHostComputeBuffers();
+
+ const auto& launch_desc = kepler_compute.launch_description;
glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z);
++num_queued_commands;
}
@@ -767,6 +520,12 @@ void RasterizerOpenGL::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
query_cache.Query(gpu_addr, type, timestamp);
}
+void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
+ u32 size) {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.BindGraphicsUniformBuffer(stage, index, gpu_addr, size);
+}
+
void RasterizerOpenGL::FlushAll() {}
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
@@ -774,16 +533,24 @@ void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
if (addr == 0 || size == 0) {
return;
}
- texture_cache.FlushRegion(addr, size);
- buffer_cache.FlushRegion(addr, size);
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.DownloadMemory(addr, size);
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.DownloadMemory(addr, size);
+ }
query_cache.FlushRegion(addr, size);
}
bool RasterizerOpenGL::MustFlushRegion(VAddr addr, u64 size) {
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
if (!Settings::IsGPULevelHigh()) {
- return buffer_cache.MustFlushRegion(addr, size);
+ return buffer_cache.IsRegionGpuModified(addr, size);
}
- return texture_cache.MustFlushRegion(addr, size) || buffer_cache.MustFlushRegion(addr, size);
+ return texture_cache.IsRegionGpuModified(addr, size) ||
+ buffer_cache.IsRegionGpuModified(addr, size);
}
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
@@ -791,9 +558,15 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
if (addr == 0 || size == 0) {
return;
}
- texture_cache.InvalidateRegion(addr, size);
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.WriteMemory(addr, size);
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.WriteMemory(addr, size);
+ }
shader_cache.InvalidateRegion(addr, size);
- buffer_cache.InvalidateRegion(addr, size);
query_cache.InvalidateRegion(addr, size);
}
@@ -802,30 +575,47 @@ void RasterizerOpenGL::OnCPUWrite(VAddr addr, u64 size) {
if (addr == 0 || size == 0) {
return;
}
- texture_cache.OnCPUWrite(addr, size);
shader_cache.OnCPUWrite(addr, size);
- buffer_cache.OnCPUWrite(addr, size);
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.WriteMemory(addr, size);
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.CachedWriteMemory(addr, size);
+ }
}
void RasterizerOpenGL::SyncGuestHost() {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
- texture_cache.SyncGuestHost();
- buffer_cache.SyncGuestHost();
shader_cache.SyncGuestHost();
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.FlushCachedWrites();
+ }
+}
+
+void RasterizerOpenGL::UnmapMemory(VAddr addr, u64 size) {
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.UnmapMemory(addr, size);
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.WriteMemory(addr, size);
+ }
+ shader_cache.OnCPUWrite(addr, size);
}
void RasterizerOpenGL::SignalSemaphore(GPUVAddr addr, u32 value) {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
- auto& memory_manager{gpu.MemoryManager()};
- memory_manager.Write<u32>(addr, value);
+ gpu_memory.Write<u32>(addr, value);
return;
}
fence_manager.SignalSemaphore(addr, value);
}
void RasterizerOpenGL::SignalSyncPoint(u32 value) {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
gpu.IncrementSyncPoint(value);
return;
@@ -834,7 +624,6 @@ void RasterizerOpenGL::SignalSyncPoint(u32 value) {
}
void RasterizerOpenGL::ReleaseFences() {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
return;
}
@@ -849,14 +638,15 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
}
void RasterizerOpenGL::WaitForIdle() {
- // Place a barrier on everything that is not framebuffer related.
- // This is related to another flag that is not currently implemented.
- glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_ELEMENT_ARRAY_BARRIER_BIT |
- GL_UNIFORM_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT |
- GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT |
- GL_PIXEL_BUFFER_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT |
- GL_BUFFER_UPDATE_BARRIER_BIT | GL_TRANSFORM_FEEDBACK_BARRIER_BIT |
- GL_SHADER_STORAGE_BARRIER_BIT | GL_QUERY_BUFFER_BARRIER_BIT);
+ glMemoryBarrier(GL_ALL_BARRIER_BITS);
+}
+
+void RasterizerOpenGL::FragmentBarrier() {
+ glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
+}
+
+void RasterizerOpenGL::TiledCacheBarrier() {
+ glTextureBarrier();
}
void RasterizerOpenGL::FlushCommands() {
@@ -872,288 +662,174 @@ void RasterizerOpenGL::TickFrame() {
// Ticking a frame means that buffers will be swapped, calling glFlush implicitly.
num_queued_commands = 0;
- buffer_cache.TickFrame();
+ fence_manager.TickFrame();
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.TickFrame();
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.TickFrame();
+ }
}
-bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
- const Tegra::Engines::Fermi2D::Regs::Surface& dst,
+bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
+ const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Config& copy_config) {
MICROPROFILE_SCOPE(OpenGL_Blits);
- texture_cache.DoFermiCopy(src, dst, copy_config);
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.BlitImage(dst, src, copy_config);
return true;
}
bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
VAddr framebuffer_addr, u32 pixel_stride) {
- if (!framebuffer_addr) {
- return {};
+ if (framebuffer_addr == 0) {
+ return false;
}
-
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
- const auto surface{texture_cache.TryFindFramebufferSurface(framebuffer_addr)};
- if (!surface) {
- return {};
+ std::scoped_lock lock{texture_cache.mutex};
+ ImageView* const image_view{texture_cache.TryFindFramebufferImageView(framebuffer_addr)};
+ if (!image_view) {
+ return false;
}
-
// Verify that the cached surface is the same size and format as the requested framebuffer
- const auto& params{surface->GetSurfaceParams()};
- const auto& pixel_format{
- VideoCore::Surface::PixelFormatFromGPUPixelFormat(config.pixel_format)};
- ASSERT_MSG(params.width == config.width, "Framebuffer width is different");
- ASSERT_MSG(params.height == config.height, "Framebuffer height is different");
-
- if (params.pixel_format != pixel_format) {
- LOG_DEBUG(Render_OpenGL, "Framebuffer pixel_format is different");
- }
-
- screen_info.display_texture = surface->GetTexture();
- screen_info.display_srgb = surface->GetSurfaceParams().srgb_conversion;
+ // ASSERT_MSG(image_view->size.width == config.width, "Framebuffer width is different");
+ // ASSERT_MSG(image_view->size.height == config.height, "Framebuffer height is different");
+ screen_info.display_texture = image_view->Handle(ImageViewType::e2D);
+ screen_info.display_srgb = VideoCore::Surface::IsPixelFormatSRGB(image_view->format);
return true;
}
-void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* shader) {
- static constexpr std::array PARAMETER_LUT = {
- GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
- GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV, GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV,
- GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV};
-
- MICROPROFILE_SCOPE(OpenGL_UBO);
- const auto& stages = system.GPU().Maxwell3D().state.shader_stages;
- const auto& shader_stage = stages[stage_index];
- const auto& entries = shader->GetEntries();
- const bool use_unified = entries.use_unified_uniforms;
- const std::size_t base_unified_offset = stage_index * NUM_CONST_BUFFERS_BYTES_PER_STAGE;
-
- const auto base_bindings = device.GetBaseBindings(stage_index);
- u32 binding = device.UseAssemblyShaders() ? 0 : base_bindings.uniform_buffer;
- for (const auto& entry : entries.const_buffers) {
- const u32 index = entry.GetIndex();
- const auto& buffer = shader_stage.const_buffers[index];
- SetupConstBuffer(PARAMETER_LUT[stage_index], binding, buffer, entry, use_unified,
- base_unified_offset + index * Maxwell::MaxConstBufferSize);
- ++binding;
- }
- if (use_unified) {
- const u32 index = static_cast<u32>(base_bindings.shader_storage_buffer +
- entries.global_memory_entries.size());
- glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index, unified_uniform_buffer.handle,
- base_unified_offset, NUM_CONST_BUFFERS_BYTES_PER_STAGE);
- }
-}
-
-void RasterizerOpenGL::SetupComputeConstBuffers(Shader* kernel) {
- MICROPROFILE_SCOPE(OpenGL_UBO);
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
- const auto& entries = kernel->GetEntries();
- const bool use_unified = entries.use_unified_uniforms;
-
- u32 binding = 0;
- for (const auto& entry : entries.const_buffers) {
- const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
- const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
- Tegra::Engines::ConstBufferInfo buffer;
- buffer.address = config.Address();
- buffer.size = config.size;
- buffer.enabled = mask[entry.GetIndex()];
- SetupConstBuffer(GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV, binding, buffer, entry,
- use_unified, entry.GetIndex() * Maxwell::MaxConstBufferSize);
- ++binding;
- }
- if (use_unified) {
- const GLuint index = static_cast<GLuint>(entries.global_memory_entries.size());
- glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index, unified_uniform_buffer.handle, 0,
- NUM_CONST_BUFFERS_BYTES_PER_STAGE);
- }
-}
-
-void RasterizerOpenGL::SetupConstBuffer(GLenum stage, u32 binding,
- const Tegra::Engines::ConstBufferInfo& buffer,
- const ConstBufferEntry& entry, bool use_unified,
- std::size_t unified_offset) {
- if (!buffer.enabled) {
- // Set values to zero to unbind buffers
- if (device.UseAssemblyShaders()) {
- glBindBufferRangeNV(stage, entry.GetIndex(), 0, 0, 0);
- } else {
- glBindBufferRange(GL_UNIFORM_BUFFER, binding, 0, 0, sizeof(float));
- }
- return;
- }
+void RasterizerOpenGL::BindComputeTextures(Shader* kernel) {
+ image_view_indices.clear();
+ sampler_handles.clear();
- // Align the actual size so it ends up being a multiple of vec4 to meet the OpenGL std140
- // UBO alignment requirements.
- const std::size_t size = Common::AlignUp(GetConstBufferSize(buffer, entry), sizeof(GLvec4));
+ texture_cache.SynchronizeComputeDescriptors();
- const bool fast_upload = !use_unified && device.HasFastBufferSubData();
+ SetupComputeTextures(kernel);
+ SetupComputeImages(kernel);
- const std::size_t alignment = use_unified ? 4 : device.GetUniformBufferAlignment();
- const GPUVAddr gpu_addr = buffer.address;
- auto info = buffer_cache.UploadMemory(gpu_addr, size, alignment, false, fast_upload);
+ const std::span indices_span(image_view_indices.data(), image_view_indices.size());
+ texture_cache.FillComputeImageViews(indices_span, image_view_ids);
- if (device.UseAssemblyShaders()) {
- UNIMPLEMENTED_IF(use_unified);
- if (info.offset != 0) {
- const GLuint staging_cbuf = staging_cbufs[current_cbuf++];
- glCopyNamedBufferSubData(info.handle, staging_cbuf, info.offset, 0, size);
- info.handle = staging_cbuf;
- info.offset = 0;
+ program_manager.BindCompute(kernel->GetHandle());
+ size_t image_view_index = 0;
+ size_t texture_index = 0;
+ size_t image_index = 0;
+ BindTextures(kernel->GetEntries(), 0, 0, image_view_index, texture_index, image_index);
+}
+
+void RasterizerOpenGL::BindTextures(const ShaderEntries& entries, GLuint base_texture,
+ GLuint base_image, size_t& image_view_index,
+ size_t& texture_index, size_t& image_index) {
+ const GLuint* const samplers = sampler_handles.data() + texture_index;
+ const GLuint* const textures = texture_handles.data() + texture_index;
+ const GLuint* const images = image_handles.data() + image_index;
+
+ const size_t num_samplers = entries.samplers.size();
+ for (const auto& sampler : entries.samplers) {
+ for (size_t i = 0; i < sampler.size; ++i) {
+ const ImageViewId image_view_id = image_view_ids[image_view_index++];
+ const ImageView& image_view = texture_cache.GetImageView(image_view_id);
+ const GLuint handle = image_view.Handle(ImageViewTypeFromEntry(sampler));
+ texture_handles[texture_index++] = handle;
}
- glBindBufferRangeNV(stage, binding, info.handle, info.offset, size);
- return;
}
-
- if (use_unified) {
- glCopyNamedBufferSubData(info.handle, unified_uniform_buffer.handle, info.offset,
- unified_offset, size);
- } else {
- glBindBufferRange(GL_UNIFORM_BUFFER, binding, info.handle, info.offset, size);
+ const size_t num_images = entries.images.size();
+ for (size_t unit = 0; unit < num_images; ++unit) {
+ // TODO: Mark as modified
+ const ImageViewId image_view_id = image_view_ids[image_view_index++];
+ const ImageView& image_view = texture_cache.GetImageView(image_view_id);
+ const GLuint handle = image_view.Handle(ImageViewTypeFromEntry(entries.images[unit]));
+ image_handles[image_index] = handle;
+ ++image_index;
}
-}
-
-void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* shader) {
- static constexpr std::array TARGET_LUT = {
- GL_VERTEX_PROGRAM_NV, GL_TESS_CONTROL_PROGRAM_NV, GL_TESS_EVALUATION_PROGRAM_NV,
- GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV,
- };
-
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
- const auto& cbufs{gpu.Maxwell3D().state.shader_stages[stage_index]};
- const auto& entries{shader->GetEntries().global_memory_entries};
-
- std::array<GLuint64EXT, 32> pointers;
- ASSERT(entries.size() < pointers.size());
-
- const bool assembly_shaders = device.UseAssemblyShaders();
- u32 binding = assembly_shaders ? 0 : device.GetBaseBindings(stage_index).shader_storage_buffer;
- for (const auto& entry : entries) {
- const GPUVAddr addr{cbufs.const_buffers[entry.cbuf_index].address + entry.cbuf_offset};
- const GPUVAddr gpu_addr{memory_manager.Read<u64>(addr)};
- const u32 size{memory_manager.Read<u32>(addr + 8)};
- SetupGlobalMemory(binding, entry, gpu_addr, size, &pointers[binding]);
- ++binding;
- }
- if (assembly_shaders) {
- UpdateBindlessPointers(TARGET_LUT[stage_index], pointers.data(), entries.size());
- }
-}
-
-void RasterizerOpenGL::SetupComputeGlobalMemory(Shader* kernel) {
- auto& gpu{system.GPU()};
- auto& memory_manager{gpu.MemoryManager()};
- const auto& cbufs{gpu.KeplerCompute().launch_description.const_buffer_config};
- const auto& entries{kernel->GetEntries().global_memory_entries};
-
- std::array<GLuint64EXT, 32> pointers;
- ASSERT(entries.size() < pointers.size());
-
- u32 binding = 0;
- for (const auto& entry : entries) {
- const GPUVAddr addr{cbufs[entry.cbuf_index].Address() + entry.cbuf_offset};
- const GPUVAddr gpu_addr{memory_manager.Read<u64>(addr)};
- const u32 size{memory_manager.Read<u32>(addr + 8)};
- SetupGlobalMemory(binding, entry, gpu_addr, size, &pointers[binding]);
- ++binding;
- }
- if (device.UseAssemblyShaders()) {
- UpdateBindlessPointers(GL_COMPUTE_PROGRAM_NV, pointers.data(), entries.size());
+ if (num_samplers > 0) {
+ glBindSamplers(base_texture, static_cast<GLsizei>(num_samplers), samplers);
+ glBindTextures(base_texture, static_cast<GLsizei>(num_samplers), textures);
}
-}
-
-void RasterizerOpenGL::SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry,
- GPUVAddr gpu_addr, std::size_t size,
- GLuint64EXT* pointer) {
- const std::size_t alignment{device.GetShaderStorageBufferAlignment()};
- const auto info = buffer_cache.UploadMemory(gpu_addr, size, alignment, entry.is_written);
- if (device.UseAssemblyShaders()) {
- *pointer = info.address + info.offset;
- } else {
- glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, info.handle, info.offset,
- static_cast<GLsizeiptr>(size));
+ if (num_images > 0) {
+ glBindImageTextures(base_image, static_cast<GLsizei>(num_images), images);
}
}
-void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, Shader* shader) {
- MICROPROFILE_SCOPE(OpenGL_Texture);
- const auto& maxwell3d = system.GPU().Maxwell3D();
- u32 binding = device.GetBaseBindings(stage_index).sampler;
+void RasterizerOpenGL::SetupDrawTextures(const Shader* shader, size_t stage_index) {
+ const bool via_header_index =
+ maxwell3d.regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex;
for (const auto& entry : shader->GetEntries().samplers) {
const auto shader_type = static_cast<ShaderType>(stage_index);
- for (std::size_t i = 0; i < entry.size; ++i) {
- const auto texture = GetTextureInfo(maxwell3d, entry, shader_type, i);
- SetupTexture(binding++, texture, entry);
+ for (size_t index = 0; index < entry.size; ++index) {
+ const auto handle =
+ GetTextureInfo(maxwell3d, via_header_index, entry, shader_type, index);
+ const Sampler* const sampler = texture_cache.GetGraphicsSampler(handle.sampler);
+ sampler_handles.push_back(sampler->Handle());
+ image_view_indices.push_back(handle.image);
}
}
}
-void RasterizerOpenGL::SetupComputeTextures(Shader* kernel) {
- MICROPROFILE_SCOPE(OpenGL_Texture);
- const auto& compute = system.GPU().KeplerCompute();
- u32 binding = 0;
+void RasterizerOpenGL::SetupComputeTextures(const Shader* kernel) {
+ const bool via_header_index = kepler_compute.launch_description.linked_tsc;
for (const auto& entry : kernel->GetEntries().samplers) {
- for (std::size_t i = 0; i < entry.size; ++i) {
- const auto texture = GetTextureInfo(compute, entry, ShaderType::Compute, i);
- SetupTexture(binding++, texture, entry);
+ for (size_t i = 0; i < entry.size; ++i) {
+ const auto handle =
+ GetTextureInfo(kepler_compute, via_header_index, entry, ShaderType::Compute, i);
+ const Sampler* const sampler = texture_cache.GetComputeSampler(handle.sampler);
+ sampler_handles.push_back(sampler->Handle());
+ image_view_indices.push_back(handle.image);
}
}
}
-void RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
- const SamplerEntry& entry) {
- const auto view = texture_cache.GetTextureSurface(texture.tic, entry);
- if (!view) {
- // Can occur when texture addr is null or its memory is unmapped/invalid
- glBindSampler(binding, 0);
- glBindTextureUnit(binding, 0);
- return;
- }
- const GLuint handle = view->GetTexture(texture.tic.x_source, texture.tic.y_source,
- texture.tic.z_source, texture.tic.w_source);
- glBindTextureUnit(binding, handle);
- if (!view->GetSurfaceParams().IsBuffer()) {
- glBindSampler(binding, sampler_cache.GetSampler(texture.tsc));
- }
-}
-
-void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, Shader* shader) {
- const auto& maxwell3d = system.GPU().Maxwell3D();
- u32 binding = device.GetBaseBindings(stage_index).image;
+void RasterizerOpenGL::SetupDrawImages(const Shader* shader, size_t stage_index) {
+ const bool via_header_index =
+ maxwell3d.regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex;
for (const auto& entry : shader->GetEntries().images) {
- const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index);
- const auto tic = GetTextureInfo(maxwell3d, entry, shader_type).tic;
- SetupImage(binding++, tic, entry);
+ const auto shader_type = static_cast<ShaderType>(stage_index);
+ const auto handle = GetTextureInfo(maxwell3d, via_header_index, entry, shader_type);
+ image_view_indices.push_back(handle.image);
}
}
-void RasterizerOpenGL::SetupComputeImages(Shader* shader) {
- const auto& compute = system.GPU().KeplerCompute();
- u32 binding = 0;
+void RasterizerOpenGL::SetupComputeImages(const Shader* shader) {
+ const bool via_header_index = kepler_compute.launch_description.linked_tsc;
for (const auto& entry : shader->GetEntries().images) {
- const auto tic = GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute).tic;
- SetupImage(binding++, tic, entry);
+ const auto handle =
+ GetTextureInfo(kepler_compute, via_header_index, entry, ShaderType::Compute);
+ image_view_indices.push_back(handle.image);
}
}
-void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic,
- const ImageEntry& entry) {
- const auto view = texture_cache.GetImageSurface(tic, entry);
- if (!view) {
- glBindImageTexture(binding, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8);
- return;
- }
- if (entry.is_written) {
- view->MarkAsModified(texture_cache.Tick());
- }
- const GLuint handle = view->GetTexture(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
- glBindImageTexture(binding, handle, 0, GL_TRUE, 0, GL_READ_WRITE, view->GetFormat());
+void RasterizerOpenGL::SyncState() {
+ SyncViewport();
+ SyncRasterizeEnable();
+ SyncPolygonModes();
+ SyncColorMask();
+ SyncFragmentColorClampState();
+ SyncMultiSampleState();
+ SyncDepthTestState();
+ SyncDepthClamp();
+ SyncStencilTestState();
+ SyncBlendState();
+ SyncLogicOpState();
+ SyncCullMode();
+ SyncPrimitiveRestart();
+ SyncScissorTest();
+ SyncPointState();
+ SyncLineState();
+ SyncPolygonOffset();
+ SyncAlphaTest();
+ SyncFramebufferSRGB();
+ SyncVertexFormats();
+ SyncVertexInstances();
}
void RasterizerOpenGL::SyncViewport() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
const bool dirty_viewport = flags[Dirty::Viewports];
const bool dirty_clip_control = flags[Dirty::ClipControl];
@@ -1180,15 +856,17 @@ void RasterizerOpenGL::SyncViewport() {
flags[Dirty::ClipControl] = false;
bool flip_y = false;
- if (regs.viewport_transform[0].scale_y < 0.0) {
+ if (regs.viewport_transform[0].scale_y < 0.0f) {
flip_y = !flip_y;
}
if (regs.screen_y_control.y_negate != 0) {
flip_y = !flip_y;
}
- glClipControl(flip_y ? GL_UPPER_LEFT : GL_LOWER_LEFT,
- regs.depth_mode == Maxwell::DepthMode::ZeroToOne ? GL_ZERO_TO_ONE
- : GL_NEGATIVE_ONE_TO_ONE);
+ const bool is_zero_to_one = regs.depth_mode == Maxwell::DepthMode::ZeroToOne;
+ const GLenum origin = flip_y ? GL_UPPER_LEFT : GL_LOWER_LEFT;
+ const GLenum depth = is_zero_to_one ? GL_ZERO_TO_ONE : GL_NEGATIVE_ONE_TO_ONE;
+ state_tracker.ClipControl(origin, depth);
+ state_tracker.SetYNegate(regs.screen_y_control.y_negate != 0);
}
if (dirty_viewport) {
@@ -1225,25 +903,23 @@ void RasterizerOpenGL::SyncViewport() {
}
void RasterizerOpenGL::SyncDepthClamp() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::DepthClampEnabled]) {
return;
}
flags[Dirty::DepthClampEnabled] = false;
- oglEnable(GL_DEPTH_CLAMP, gpu.regs.view_volume_clip_control.depth_clamp_disabled == 0);
+ oglEnable(GL_DEPTH_CLAMP, maxwell3d.regs.view_volume_clip_control.depth_clamp_disabled == 0);
}
void RasterizerOpenGL::SyncClipEnabled(u32 clip_mask) {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::ClipDistances] && !flags[Dirty::Shaders]) {
return;
}
flags[Dirty::ClipDistances] = false;
- clip_mask &= gpu.regs.clip_distance_enabled;
+ clip_mask &= maxwell3d.regs.clip_distance_enabled;
if (clip_mask == last_clip_distance_mask) {
return;
}
@@ -1259,9 +935,8 @@ void RasterizerOpenGL::SyncClipCoef() {
}
void RasterizerOpenGL::SyncCullMode() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
if (flags[Dirty::CullTest]) {
flags[Dirty::CullTest] = false;
@@ -1276,26 +951,24 @@ void RasterizerOpenGL::SyncCullMode() {
}
void RasterizerOpenGL::SyncPrimitiveRestart() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PrimitiveRestart]) {
return;
}
flags[Dirty::PrimitiveRestart] = false;
- if (gpu.regs.primitive_restart.enabled) {
+ if (maxwell3d.regs.primitive_restart.enabled) {
glEnable(GL_PRIMITIVE_RESTART);
- glPrimitiveRestartIndex(gpu.regs.primitive_restart.index);
+ glPrimitiveRestartIndex(maxwell3d.regs.primitive_restart.index);
} else {
glDisable(GL_PRIMITIVE_RESTART);
}
}
void RasterizerOpenGL::SyncDepthTestState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
- const auto& regs = gpu.regs;
if (flags[Dirty::DepthMask]) {
flags[Dirty::DepthMask] = false;
glDepthMask(regs.depth_write_enabled ? GL_TRUE : GL_FALSE);
@@ -1313,14 +986,13 @@ void RasterizerOpenGL::SyncDepthTestState() {
}
void RasterizerOpenGL::SyncStencilTestState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::StencilTest]) {
return;
}
flags[Dirty::StencilTest] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_STENCIL_TEST, regs.stencil_enable);
glStencilFuncSeparate(GL_FRONT, MaxwellToGL::ComparisonOp(regs.stencil_front_func_func),
@@ -1345,25 +1017,24 @@ void RasterizerOpenGL::SyncStencilTestState() {
}
void RasterizerOpenGL::SyncRasterizeEnable() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::RasterizeEnable]) {
return;
}
flags[Dirty::RasterizeEnable] = false;
- oglEnable(GL_RASTERIZER_DISCARD, gpu.regs.rasterize_enable == 0);
+ oglEnable(GL_RASTERIZER_DISCARD, maxwell3d.regs.rasterize_enable == 0);
}
void RasterizerOpenGL::SyncPolygonModes() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PolygonModes]) {
return;
}
flags[Dirty::PolygonModes] = false;
- if (gpu.regs.fill_rectangle) {
+ const auto& regs = maxwell3d.regs;
+ if (regs.fill_rectangle) {
if (!GLAD_GL_NV_fill_rectangle) {
LOG_ERROR(Render_OpenGL, "GL_NV_fill_rectangle used and not supported");
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
@@ -1376,27 +1047,26 @@ void RasterizerOpenGL::SyncPolygonModes() {
return;
}
- if (gpu.regs.polygon_mode_front == gpu.regs.polygon_mode_back) {
+ if (regs.polygon_mode_front == regs.polygon_mode_back) {
flags[Dirty::PolygonModeFront] = false;
flags[Dirty::PolygonModeBack] = false;
- glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front));
+ glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_front));
return;
}
if (flags[Dirty::PolygonModeFront]) {
flags[Dirty::PolygonModeFront] = false;
- glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front));
+ glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(regs.polygon_mode_front));
}
if (flags[Dirty::PolygonModeBack]) {
flags[Dirty::PolygonModeBack] = false;
- glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_back));
+ glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_back));
}
}
void RasterizerOpenGL::SyncColorMask() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::ColorMasks]) {
return;
}
@@ -1405,7 +1075,7 @@ void RasterizerOpenGL::SyncColorMask() {
const bool force = flags[Dirty::ColorMaskCommon];
flags[Dirty::ColorMaskCommon] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
if (regs.color_mask_common) {
if (!force && !flags[Dirty::ColorMask0]) {
return;
@@ -1430,33 +1100,30 @@ void RasterizerOpenGL::SyncColorMask() {
}
void RasterizerOpenGL::SyncMultiSampleState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::MultisampleControl]) {
return;
}
flags[Dirty::MultisampleControl] = false;
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE, regs.multisample_control.alpha_to_coverage);
oglEnable(GL_SAMPLE_ALPHA_TO_ONE, regs.multisample_control.alpha_to_one);
}
void RasterizerOpenGL::SyncFragmentColorClampState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::FragmentClampColor]) {
return;
}
flags[Dirty::FragmentClampColor] = false;
- glClampColor(GL_CLAMP_FRAGMENT_COLOR, gpu.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
+ glClampColor(GL_CLAMP_FRAGMENT_COLOR, maxwell3d.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
}
void RasterizerOpenGL::SyncBlendState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
- const auto& regs = gpu.regs;
+ auto& flags = maxwell3d.dirty.flags;
+ const auto& regs = maxwell3d.regs;
if (flags[Dirty::BlendColor]) {
flags[Dirty::BlendColor] = false;
@@ -1513,14 +1180,13 @@ void RasterizerOpenGL::SyncBlendState() {
}
void RasterizerOpenGL::SyncLogicOpState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::LogicOp]) {
return;
}
flags[Dirty::LogicOp] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
if (regs.logic_op.enable) {
glEnable(GL_COLOR_LOGIC_OP);
glLogicOp(MaxwellToGL::LogicOp(regs.logic_op.operation));
@@ -1530,14 +1196,13 @@ void RasterizerOpenGL::SyncLogicOpState() {
}
void RasterizerOpenGL::SyncScissorTest() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::Scissors]) {
return;
}
flags[Dirty::Scissors] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
for (std::size_t index = 0; index < Maxwell::NumViewports; ++index) {
if (!flags[Dirty::Scissor0 + index]) {
continue;
@@ -1556,49 +1221,38 @@ void RasterizerOpenGL::SyncScissorTest() {
}
void RasterizerOpenGL::SyncPointState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PointSize]) {
return;
}
flags[Dirty::PointSize] = false;
- oglEnable(GL_POINT_SPRITE, gpu.regs.point_sprite_enable);
-
- if (gpu.regs.vp_point_size.enable) {
- // By definition of GL_POINT_SIZE, it only matters if GL_PROGRAM_POINT_SIZE is disabled.
- glEnable(GL_PROGRAM_POINT_SIZE);
- return;
- }
+ oglEnable(GL_POINT_SPRITE, maxwell3d.regs.point_sprite_enable);
+ oglEnable(GL_PROGRAM_POINT_SIZE, maxwell3d.regs.vp_point_size.enable);
- // Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid
- // in OpenGL).
- glPointSize(std::max(1.0f, gpu.regs.point_size));
- glDisable(GL_PROGRAM_POINT_SIZE);
+ glPointSize(std::max(1.0f, maxwell3d.regs.point_size));
}
void RasterizerOpenGL::SyncLineState() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::LineWidth]) {
return;
}
flags[Dirty::LineWidth] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_LINE_SMOOTH, regs.line_smooth_enable);
glLineWidth(regs.line_smooth_enable ? regs.line_width_smooth : regs.line_width_aliased);
}
void RasterizerOpenGL::SyncPolygonOffset() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::PolygonOffset]) {
return;
}
flags[Dirty::PolygonOffset] = false;
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
oglEnable(GL_POLYGON_OFFSET_FILL, regs.polygon_offset_fill_enable);
oglEnable(GL_POLYGON_OFFSET_LINE, regs.polygon_offset_line_enable);
oglEnable(GL_POLYGON_OFFSET_POINT, regs.polygon_offset_point_enable);
@@ -1612,18 +1266,13 @@ void RasterizerOpenGL::SyncPolygonOffset() {
}
void RasterizerOpenGL::SyncAlphaTest() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::AlphaTest]) {
return;
}
flags[Dirty::AlphaTest] = false;
- const auto& regs = gpu.regs;
- if (regs.alpha_test_enabled && regs.rt_control.count > 1) {
- LOG_WARNING(Render_OpenGL, "Alpha testing with more than one render target is not tested");
- }
-
+ const auto& regs = maxwell3d.regs;
if (regs.alpha_test_enabled) {
glEnable(GL_ALPHA_TEST);
glAlphaFunc(MaxwellToGL::ComparisonOp(regs.alpha_test_func), regs.alpha_test_ref);
@@ -1633,20 +1282,19 @@ void RasterizerOpenGL::SyncAlphaTest() {
}
void RasterizerOpenGL::SyncFramebufferSRGB() {
- auto& gpu = system.GPU().Maxwell3D();
- auto& flags = gpu.dirty.flags;
+ auto& flags = maxwell3d.dirty.flags;
if (!flags[Dirty::FramebufferSRGB]) {
return;
}
flags[Dirty::FramebufferSRGB] = false;
- oglEnable(GL_FRAMEBUFFER_SRGB, gpu.regs.framebuffer_srgb);
+ oglEnable(GL_FRAMEBUFFER_SRGB, maxwell3d.regs.framebuffer_srgb);
}
void RasterizerOpenGL::SyncTransformFeedback() {
// TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal
// when this is required.
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
static constexpr std::size_t STRIDE = 3;
std::array<GLint, 128 * STRIDE * Maxwell::NumTransformFeedbackBuffers> attribs;
@@ -1698,40 +1346,17 @@ void RasterizerOpenGL::SyncTransformFeedback() {
}
void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
-
if (device.UseAssemblyShaders()) {
SyncTransformFeedback();
}
-
UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationControl) ||
regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationEval) ||
regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::Geometry));
-
- for (std::size_t index = 0; index < Maxwell::NumTransformFeedbackBuffers; ++index) {
- const auto& binding = regs.tfb_bindings[index];
- if (!binding.buffer_enable) {
- if (enabled_transform_feedback_buffers[index]) {
- glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(index), 0, 0,
- 0);
- }
- enabled_transform_feedback_buffers[index] = false;
- continue;
- }
- enabled_transform_feedback_buffers[index] = true;
-
- auto& tfb_buffer = transform_feedback_buffers[index];
- tfb_buffer.Create();
-
- const GLuint handle = tfb_buffer.handle;
- const std::size_t size = binding.buffer_size;
- glNamedBufferData(handle, static_cast<GLsizeiptr>(size), nullptr, GL_STREAM_COPY);
- glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(index), handle, 0,
- static_cast<GLsizeiptr>(size));
- }
+ UNIMPLEMENTED_IF(primitive_mode != GL_POINTS);
// We may have to call BeginTransformFeedbackNV here since they seem to call different
// implementations on Nvidia's driver (the pointer is different) but we are using
@@ -1741,27 +1366,11 @@ void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
}
void RasterizerOpenGL::EndTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
-
glEndTransformFeedback();
-
- for (std::size_t index = 0; index < Maxwell::NumTransformFeedbackBuffers; ++index) {
- const auto& binding = regs.tfb_bindings[index];
- if (!binding.buffer_enable) {
- continue;
- }
- UNIMPLEMENTED_IF(binding.buffer_offset != 0);
-
- const GLuint handle = transform_feedback_buffers[index].handle;
- const GPUVAddr gpu_addr = binding.Address();
- const std::size_t size = binding.buffer_size;
- const auto info = buffer_cache.UploadMemory(gpu_addr, size, 4, true);
- glCopyNamedBufferSubData(handle, info.handle, 0, info.offset,
- static_cast<GLsizeiptr>(size));
- }
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index ccc6f50f6..3745cf637 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -7,12 +7,13 @@
#include <array>
#include <atomic>
#include <cstddef>
-#include <map>
#include <memory>
#include <optional>
#include <tuple>
#include <utility>
+#include <boost/container/static_vector.hpp>
+
#include <glad/glad.h>
#include "common/common_types.h"
@@ -23,21 +24,18 @@
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_fence_manager.h"
-#include "video_core/renderer_opengl/gl_framebuffer_cache.h"
#include "video_core/renderer_opengl/gl_query_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
-#include "video_core/renderer_opengl/gl_sampler_cache.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"
-#include "video_core/renderer_opengl/utils.h"
#include "video_core/shader/async_shaders.h"
#include "video_core/textures/texture.h"
-namespace Core {
-class System;
+namespace Core::Memory {
+class Memory;
}
namespace Core::Frontend {
@@ -51,13 +49,21 @@ class MemoryManager;
namespace OpenGL {
struct ScreenInfo;
-struct DrawParameters;
+struct ShaderEntries;
+
+struct BindlessSSBO {
+ GLuint64EXT address;
+ GLsizei length;
+ GLsizei padding;
+};
+static_assert(sizeof(BindlessSSBO) * CHAR_BIT == 128);
class RasterizerOpenGL : public VideoCore::RasterizerAccelerated {
public:
- explicit RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
- const Device& device, ScreenInfo& info,
- ProgramManager& program_manager, StateTracker& state_tracker);
+ explicit RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
+ Core::Memory::Memory& cpu_memory_, const Device& device_,
+ ScreenInfo& screen_info_, ProgramManager& program_manager_,
+ StateTracker& state_tracker_);
~RasterizerOpenGL() override;
void Draw(bool is_indexed, bool is_instanced) override;
@@ -65,27 +71,30 @@ public:
void DispatchCompute(GPUVAddr code_addr) override;
void ResetCounter(VideoCore::QueryType type) override;
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
+ void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
void FlushAll() override;
void FlushRegion(VAddr addr, u64 size) override;
bool MustFlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void OnCPUWrite(VAddr addr, u64 size) override;
void SyncGuestHost() override;
+ void UnmapMemory(VAddr addr, u64 size) override;
void SignalSemaphore(GPUVAddr addr, u32 value) override;
void SignalSyncPoint(u32 value) override;
void ReleaseFences() override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
void WaitForIdle() override;
+ void FragmentBarrier() override;
+ void TiledCacheBarrier() override;
void FlushCommands() override;
void TickFrame() override;
- bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
- const Tegra::Engines::Fermi2D::Regs::Surface& dst,
+ bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
+ const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Config& copy_config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
- void LoadDiskResources(const std::atomic_bool& stop_loading,
+ void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
- void SetupDirtyFlags() override;
/// Returns true when there are commands queued to the OpenGL server.
bool AnyCommandQueued() const {
@@ -101,51 +110,29 @@ public:
}
private:
- /// Configures the color and depth framebuffer states.
- void ConfigureFramebuffers();
-
- /// Configures the color and depth framebuffer for clearing.
- void ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil);
-
- /// Configures the current constbuffers to use for the draw command.
- void SetupDrawConstBuffers(std::size_t stage_index, Shader* shader);
-
- /// Configures the current constbuffers to use for the kernel invocation.
- void SetupComputeConstBuffers(Shader* kernel);
-
- /// Configures a constant buffer.
- void SetupConstBuffer(GLenum stage, u32 binding, const Tegra::Engines::ConstBufferInfo& buffer,
- const ConstBufferEntry& entry, bool use_unified,
- std::size_t unified_offset);
+ static constexpr size_t MAX_TEXTURES = 192;
+ static constexpr size_t MAX_IMAGES = 48;
+ static constexpr size_t MAX_IMAGE_VIEWS = MAX_TEXTURES + MAX_IMAGES;
- /// Configures the current global memory entries to use for the draw command.
- void SetupDrawGlobalMemory(std::size_t stage_index, Shader* shader);
+ void BindComputeTextures(Shader* kernel);
- /// Configures the current global memory entries to use for the kernel invocation.
- void SetupComputeGlobalMemory(Shader* kernel);
-
- /// Configures a global memory buffer.
- void SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& entry, GPUVAddr gpu_addr,
- std::size_t size, GLuint64EXT* pointer);
+ void BindTextures(const ShaderEntries& entries, GLuint base_texture, GLuint base_image,
+ size_t& image_view_index, size_t& texture_index, size_t& image_index);
/// Configures the current textures to use for the draw command.
- void SetupDrawTextures(std::size_t stage_index, Shader* shader);
+ void SetupDrawTextures(const Shader* shader, size_t stage_index);
/// Configures the textures used in a compute shader.
- void SetupComputeTextures(Shader* kernel);
-
- /// Configures a texture.
- void SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
- const SamplerEntry& entry);
+ void SetupComputeTextures(const Shader* kernel);
/// Configures images in a graphics shader.
- void SetupDrawImages(std::size_t stage_index, Shader* shader);
+ void SetupDrawImages(const Shader* shader, size_t stage_index);
/// Configures images in a compute shader.
- void SetupComputeImages(Shader* shader);
+ void SetupComputeImages(const Shader* shader);
- /// Configures an image.
- void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, const ImageEntry& entry);
+ /// Syncs state to match guest's
+ void SyncState();
/// Syncs the viewport and depth range to match the guest state
void SyncViewport();
@@ -210,6 +197,12 @@ private:
/// Syncs the framebuffer sRGB state to match the guest state
void SyncFramebufferSRGB();
+ /// Syncs vertex formats to match the guest state
+ void SyncVertexFormats();
+
+ /// Syncs vertex instances to match the guest state
+ void SyncVertexInstances();
+
/// Syncs transform feedback state to match guest state
/// @note Only valid on assembly shaders
void SyncTransformFeedback();
@@ -220,56 +213,35 @@ private:
/// End a transform feedback
void EndTransformFeedback();
- /// Check for extension that are not strictly required but are needed for correct emulation
- void CheckExtensions();
-
- std::size_t CalculateVertexArraysSize() const;
-
- std::size_t CalculateIndexBufferSize() const;
+ void SetupShaders(bool is_indexed);
- /// Updates the current vertex format
- void SetupVertexFormat();
-
- void SetupVertexBuffer();
- void SetupVertexInstances();
-
- GLintptr SetupIndexBuffer();
-
- void SetupShaders(GLenum primitive_mode);
+ Tegra::GPU& gpu;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
const Device& device;
+ ScreenInfo& screen_info;
+ ProgramManager& program_manager;
+ StateTracker& state_tracker;
- TextureCacheOpenGL texture_cache;
+ TextureCacheRuntime texture_cache_runtime;
+ TextureCache texture_cache;
+ BufferCacheRuntime buffer_cache_runtime;
+ BufferCache buffer_cache;
ShaderCacheOpenGL shader_cache;
- SamplerCacheOpenGL sampler_cache;
- FramebufferCacheOpenGL framebuffer_cache;
QueryCache query_cache;
- OGLBufferCache buffer_cache;
FenceManagerOpenGL fence_manager;
- Core::System& system;
- ScreenInfo& screen_info;
- ProgramManager& program_manager;
- StateTracker& state_tracker;
VideoCommon::Shader::AsyncShaders async_shaders;
- static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
-
- GLint vertex_binding = 0;
-
- std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::NumTransformFeedbackBuffers>
- transform_feedback_buffers;
- std::bitset<Tegra::Engines::Maxwell3D::Regs::NumTransformFeedbackBuffers>
- enabled_transform_feedback_buffers;
-
- static constexpr std::size_t NUM_CONSTANT_BUFFERS =
- Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers *
- Tegra::Engines::Maxwell3D::Regs::MaxShaderProgram;
- std::array<GLuint, NUM_CONSTANT_BUFFERS> staging_cbufs{};
- std::size_t current_cbuf = 0;
- OGLBuffer unified_uniform_buffer;
+ boost::container::static_vector<u32, MAX_IMAGE_VIEWS> image_view_indices;
+ std::array<ImageViewId, MAX_IMAGE_VIEWS> image_view_ids;
+ boost::container::static_vector<GLuint, MAX_TEXTURES> sampler_handles;
+ std::array<GLuint, MAX_TEXTURES> texture_handles{};
+ std::array<GLuint, MAX_IMAGES> image_handles{};
- /// Number of commands queued to the OpenGL driver. Reseted on flush.
+ /// Number of commands queued to the OpenGL driver. Resetted on flush.
std::size_t num_queued_commands = 0;
u32 last_clip_distance_mask = 0;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp
index 0ebcec427..3428e5e21 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp
@@ -71,7 +71,7 @@ void OGLSampler::Create() {
return;
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
- glGenSamplers(1, &handle);
+ glCreateSamplers(1, &handle);
}
void OGLSampler::Release() {
@@ -171,12 +171,6 @@ void OGLBuffer::Release() {
handle = 0;
}
-void OGLBuffer::MakeStreamCopy(std::size_t buffer_size) {
- ASSERT_OR_EXECUTE((handle != 0 && buffer_size != 0), { return; });
-
- glNamedBufferData(handle, buffer_size, nullptr, GL_STREAM_COPY);
-}
-
void OGLSync::Create() {
if (handle != 0)
return;
diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h
index f48398669..552d79db4 100644
--- a/src/video_core/renderer_opengl/gl_resource_manager.h
+++ b/src/video_core/renderer_opengl/gl_resource_manager.h
@@ -234,9 +234,6 @@ public:
/// Deletes the internal OpenGL resource
void Release();
- // Converts the buffer into a stream copy buffer with a fixed size
- void MakeStreamCopy(std::size_t buffer_size);
-
GLuint handle = 0;
};
diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.cpp b/src/video_core/renderer_opengl/gl_sampler_cache.cpp
deleted file mode 100644
index 5c174879a..000000000
--- a/src/video_core/renderer_opengl/gl_sampler_cache.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/logging/log.h"
-#include "video_core/renderer_opengl/gl_resource_manager.h"
-#include "video_core/renderer_opengl/gl_sampler_cache.h"
-#include "video_core/renderer_opengl/maxwell_to_gl.h"
-
-namespace OpenGL {
-
-SamplerCacheOpenGL::SamplerCacheOpenGL() = default;
-
-SamplerCacheOpenGL::~SamplerCacheOpenGL() = default;
-
-OGLSampler SamplerCacheOpenGL::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const {
- OGLSampler sampler;
- sampler.Create();
-
- const GLuint sampler_id{sampler.handle};
- glSamplerParameteri(
- sampler_id, GL_TEXTURE_MAG_FILTER,
- MaxwellToGL::TextureFilterMode(tsc.mag_filter, Tegra::Texture::TextureMipmapFilter::None));
- glSamplerParameteri(sampler_id, GL_TEXTURE_MIN_FILTER,
- MaxwellToGL::TextureFilterMode(tsc.min_filter, tsc.mipmap_filter));
- glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(tsc.wrap_u));
- glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(tsc.wrap_v));
- glSamplerParameteri(sampler_id, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(tsc.wrap_p));
- glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_MODE,
- tsc.depth_compare_enabled == 1 ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE);
- glSamplerParameteri(sampler_id, GL_TEXTURE_COMPARE_FUNC,
- MaxwellToGL::DepthCompareFunc(tsc.depth_compare_func));
- glSamplerParameterfv(sampler_id, GL_TEXTURE_BORDER_COLOR, tsc.GetBorderColor().data());
- glSamplerParameterf(sampler_id, GL_TEXTURE_MIN_LOD, tsc.GetMinLod());
- glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_LOD, tsc.GetMaxLod());
- glSamplerParameterf(sampler_id, GL_TEXTURE_LOD_BIAS, tsc.GetLodBias());
- if (GLAD_GL_ARB_texture_filter_anisotropic) {
- glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY, tsc.GetMaxAnisotropy());
- } else if (GLAD_GL_EXT_texture_filter_anisotropic) {
- glSamplerParameterf(sampler_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, tsc.GetMaxAnisotropy());
- } else {
- LOG_WARNING(Render_OpenGL, "Anisotropy not supported by host GPU driver");
- }
-
- return sampler;
-}
-
-GLuint SamplerCacheOpenGL::ToSamplerType(const OGLSampler& sampler) const {
- return sampler.handle;
-}
-
-} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.h b/src/video_core/renderer_opengl/gl_sampler_cache.h
deleted file mode 100644
index 34ee37f00..000000000
--- a/src/video_core/renderer_opengl/gl_sampler_cache.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <glad/glad.h>
-
-#include "video_core/renderer_opengl/gl_resource_manager.h"
-#include "video_core/sampler_cache.h"
-
-namespace OpenGL {
-
-class SamplerCacheOpenGL final : public VideoCommon::SamplerCache<GLuint, OGLSampler> {
-public:
- explicit SamplerCacheOpenGL();
- ~SamplerCacheOpenGL();
-
-protected:
- OGLSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const override;
-
- GLuint ToSamplerType(const OGLSampler& sampler) const override;
-};
-
-} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index a07d56ef0..529570ff0 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -27,7 +27,6 @@
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
-#include "video_core/renderer_opengl/utils.h"
#include "video_core/shader/memory_util.h"
#include "video_core/shader/registry.h"
#include "video_core/shader/shader_ir.h"
@@ -160,6 +159,10 @@ std::unordered_set<GLenum> GetSupportedFormats() {
ProgramSharedPtr BuildShader(const Device& device, ShaderType shader_type, u64 unique_identifier,
const ShaderIR& ir, const Registry& registry, bool hint_retrievable) {
+ if (device.UseDriverCache()) {
+ // Ignore hint retrievable if we are using the driver cache
+ hint_retrievable = false;
+ }
const std::string shader_id = MakeShaderID(unique_identifier, shader_type);
LOG_INFO(Render_OpenGL, "{}", shader_id);
@@ -198,10 +201,10 @@ ProgramSharedPtr BuildShader(const Device& device, ShaderType shader_type, u64 u
return program;
}
-Shader::Shader(std::shared_ptr<VideoCommon::Shader::Registry> registry_, ShaderEntries entries_,
- ProgramSharedPtr program_, bool is_built)
+Shader::Shader(std::shared_ptr<Registry> registry_, ShaderEntries entries_,
+ ProgramSharedPtr program_, bool is_built_)
: registry{std::move(registry_)}, entries{std::move(entries_)}, program{std::move(program_)},
- is_built(is_built) {
+ is_built{is_built_} {
handle = program->assembly_program.handle;
if (handle == 0) {
handle = program->source_program.handle;
@@ -239,12 +242,11 @@ std::unique_ptr<Shader> Shader::CreateStageFromMemory(
ProgramCode code_b, VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr) {
const auto shader_type = GetShaderType(program_type);
- auto& gpu = params.system.GPU();
+ auto& gpu = params.gpu;
gpu.ShaderNotify().MarkSharderBuilding();
auto registry = std::make_shared<Registry>(shader_type, gpu.Maxwell3D());
- if (!async_shaders.IsShaderAsync(params.system.GPU()) ||
- !params.device.UseAsynchronousShaders()) {
+ if (!async_shaders.IsShaderAsync(gpu) || !params.device.UseAsynchronousShaders()) {
const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
// TODO(Rodrigo): Handle VertexA shaders
// std::optional<ShaderIR> ir_b;
@@ -287,11 +289,10 @@ std::unique_ptr<Shader> Shader::CreateStageFromMemory(
std::unique_ptr<Shader> Shader::CreateKernelFromMemory(const ShaderParameters& params,
ProgramCode code) {
- auto& gpu = params.system.GPU();
+ auto& gpu = params.gpu;
gpu.ShaderNotify().MarkSharderBuilding();
- auto& engine = gpu.KeplerCompute();
- auto registry = std::make_shared<Registry>(ShaderType::Compute, engine);
+ auto registry = std::make_shared<Registry>(ShaderType::Compute, params.engine);
const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
const u64 uid = params.unique_identifier;
auto program = BuildShader(params.device, ShaderType::Compute, uid, ir, *registry);
@@ -320,22 +321,26 @@ std::unique_ptr<Shader> Shader::CreateFromCache(const ShaderParameters& params,
precompiled_shader.registry, precompiled_shader.entries, precompiled_shader.program));
}
-ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
- Core::Frontend::EmuWindow& emu_window, const Device& device)
- : VideoCommon::ShaderCache<Shader>{rasterizer}, system{system},
- emu_window{emu_window}, device{device}, disk_cache{system} {}
+ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_,
+ Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, const Device& device_)
+ : ShaderCache{rasterizer_}, emu_window{emu_window_}, gpu{gpu_}, gpu_memory{gpu_memory_},
+ maxwell3d{maxwell3d_}, kepler_compute{kepler_compute_}, device{device_} {}
ShaderCacheOpenGL::~ShaderCacheOpenGL() = default;
-void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
+void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
+ disk_cache.BindTitleID(title_id);
const std::optional transferable = disk_cache.LoadTransferable();
if (!transferable) {
return;
}
std::vector<ShaderDiskCachePrecompiled> gl_cache;
- if (!device.UseAssemblyShaders()) {
+ if (!device.UseAssemblyShaders() && !device.UseDriverCache()) {
// Only load precompiled cache when we are not using assembly shaders
gl_cache = disk_cache.LoadPrecompiled();
}
@@ -355,8 +360,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
std::atomic_bool gl_cache_failed = false;
const auto find_precompiled = [&gl_cache](u64 id) {
- return std::find_if(gl_cache.begin(), gl_cache.end(),
- [id](const auto& entry) { return entry.unique_identifier == id; });
+ return std::ranges::find(gl_cache, id, &ShaderDiskCachePrecompiled::unique_identifier);
};
const auto worker = [&](Core::Frontend::GraphicsContext* context, std::size_t begin,
@@ -431,8 +435,8 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
return;
}
- if (device.UseAssemblyShaders()) {
- // Don't store precompiled binaries for assembly shaders.
+ if (device.UseAssemblyShaders() || device.UseDriverCache()) {
+ // Don't store precompiled binaries for assembly shaders or when using the driver cache
return;
}
@@ -457,7 +461,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram(
const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry,
const std::unordered_set<GLenum>& supported_formats) {
- if (supported_formats.find(precompiled_entry.binary_format) == supported_formats.end()) {
+ if (!supported_formats.contains(precompiled_entry.binary_format)) {
LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format, removing");
return {};
}
@@ -481,21 +485,19 @@ ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram(
Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
VideoCommon::Shader::AsyncShaders& async_shaders) {
- if (!system.GPU().Maxwell3D().dirty.flags[Dirty::Shaders]) {
+ if (!maxwell3d.dirty.flags[Dirty::Shaders]) {
auto* last_shader = last_shaders[static_cast<std::size_t>(program)];
if (last_shader->IsBuilt()) {
return last_shader;
}
}
- auto& memory_manager{system.GPU().MemoryManager()};
- const GPUVAddr address{GetShaderAddress(system, program)};
+ const GPUVAddr address{GetShaderAddress(maxwell3d, program)};
if (device.UseAsynchronousShaders() && async_shaders.HasCompletedWork()) {
auto completed_work = async_shaders.GetCompletedWork();
for (auto& work : completed_work) {
Shader* shader = TryGet(work.cpu_address);
- auto& gpu = system.GPU();
gpu.ShaderNotify().MarkShaderComplete();
if (shader == nullptr) {
continue;
@@ -507,14 +509,13 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
shader->AsyncGLASMBuilt(std::move(work.program.glasm));
}
+ auto& registry = shader->GetRegistry();
+
ShaderDiskCacheEntry entry;
entry.type = work.shader_type;
entry.code = std::move(work.code);
entry.code_b = std::move(work.code_b);
entry.unique_identifier = work.uid;
-
- auto& registry = shader->GetRegistry();
-
entry.bound_buffer = registry.GetBoundBuffer();
entry.graphics_info = registry.GetGraphicsInfo();
entry.keys = registry.GetKeys();
@@ -525,28 +526,28 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
}
// Look up shader in the cache based on address
- const auto cpu_addr{memory_manager.GpuToCpuAddress(address)};
+ const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(address)};
if (Shader* const shader{cpu_addr ? TryGet(*cpu_addr) : null_shader.get()}) {
return last_shaders[static_cast<std::size_t>(program)] = shader;
}
- const auto host_ptr{memory_manager.GetPointer(address)};
+ const u8* const host_ptr{gpu_memory.GetPointer(address)};
// No shader found - create a new one
- ProgramCode code{GetShaderCode(memory_manager, address, host_ptr, false)};
+ ProgramCode code{GetShaderCode(gpu_memory, address, host_ptr, false)};
ProgramCode code_b;
if (program == Maxwell::ShaderProgram::VertexA) {
- const GPUVAddr address_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)};
- const u8* host_ptr_b = memory_manager.GetPointer(address_b);
- code_b = GetShaderCode(memory_manager, address_b, host_ptr_b, false);
+ const GPUVAddr address_b{GetShaderAddress(maxwell3d, Maxwell::ShaderProgram::VertexB)};
+ const u8* host_ptr_b = gpu_memory.GetPointer(address_b);
+ code_b = GetShaderCode(gpu_memory, address_b, host_ptr_b, false);
}
const std::size_t code_size = code.size() * sizeof(u64);
const u64 unique_identifier = GetUniqueIdentifier(
GetShaderType(program), program == Maxwell::ShaderProgram::VertexA, code, code_b);
- const ShaderParameters params{system, disk_cache, device,
- *cpu_addr, host_ptr, unique_identifier};
+ const ShaderParameters params{gpu, maxwell3d, disk_cache, device,
+ *cpu_addr, host_ptr, unique_identifier};
std::unique_ptr<Shader> shader;
const auto found = runtime_cache.find(unique_identifier);
@@ -568,21 +569,20 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
}
Shader* ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) {
- auto& memory_manager{system.GPU().MemoryManager()};
- const auto cpu_addr{memory_manager.GpuToCpuAddress(code_addr)};
+ const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(code_addr)};
if (Shader* const kernel = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get()) {
return kernel;
}
- const auto host_ptr{memory_manager.GetPointer(code_addr)};
// No kernel found, create a new one
- ProgramCode code{GetShaderCode(memory_manager, code_addr, host_ptr, true)};
+ const u8* host_ptr{gpu_memory.GetPointer(code_addr)};
+ ProgramCode code{GetShaderCode(gpu_memory, code_addr, host_ptr, true)};
const std::size_t code_size{code.size() * sizeof(u64)};
const u64 unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code)};
- const ShaderParameters params{system, disk_cache, device,
- *cpu_addr, host_ptr, unique_identifier};
+ const ShaderParameters params{gpu, kepler_compute, disk_cache, device,
+ *cpu_addr, host_ptr, unique_identifier};
std::unique_ptr<Shader> kernel;
const auto found = runtime_cache.find(unique_identifier);
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 7528ac686..2aed0697e 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -25,8 +25,8 @@
#include "video_core/shader/shader_ir.h"
#include "video_core/shader_cache.h"
-namespace Core {
-class System;
+namespace Tegra {
+class MemoryManager;
}
namespace Core::Frontend {
@@ -57,11 +57,12 @@ struct PrecompiledShader {
};
struct ShaderParameters {
- Core::System& system;
+ Tegra::GPU& gpu;
+ Tegra::Engines::ConstBufferEngineInterface& engine;
ShaderDiskCacheOpenGL& disk_cache;
const Device& device;
VAddr cpu_addr;
- u8* host_ptr;
+ const u8* host_ptr;
u64 unique_identifier;
};
@@ -107,7 +108,7 @@ public:
private:
explicit Shader(std::shared_ptr<VideoCommon::Shader::Registry> registry, ShaderEntries entries,
- ProgramSharedPtr program, bool is_built = true);
+ ProgramSharedPtr program, bool is_built_ = true);
std::shared_ptr<VideoCommon::Shader::Registry> registry;
ShaderEntries entries;
@@ -118,12 +119,15 @@ private:
class ShaderCacheOpenGL final : public VideoCommon::ShaderCache<Shader> {
public:
- explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
- Core::Frontend::EmuWindow& emu_window, const Device& device);
+ explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer_,
+ Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, const Device& device_);
~ShaderCacheOpenGL() override;
/// Loads disk cache for the current game
- void LoadDiskCache(const std::atomic_bool& stop_loading,
+ void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
/// Gets the current specified shader stage program
@@ -138,9 +142,13 @@ private:
const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry,
const std::unordered_set<GLenum>& supported_formats);
- Core::System& system;
Core::Frontend::EmuWindow& emu_window;
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
const Device& device;
+
ShaderDiskCacheOpenGL disk_cache;
std::unordered_map<u64, PrecompiledShader> runtime_cache;
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 3f75fcd2b..ac78d344c 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -14,6 +14,7 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/div_ceil.h"
#include "common/logging/log.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/shader_type.h"
@@ -38,11 +39,9 @@ using Tegra::Shader::IpaSampleMode;
using Tegra::Shader::PixelImap;
using Tegra::Shader::Register;
using Tegra::Shader::TextureType;
-using VideoCommon::Shader::BuildTransformFeedback;
-using VideoCommon::Shader::Registry;
-using namespace std::string_literals;
using namespace VideoCommon::Shader;
+using namespace std::string_literals;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
using Operation = const OperationNode&;
@@ -65,7 +64,7 @@ using TextureIR = std::variant<TextureOffset, TextureDerivates, TextureArgument>
constexpr u32 MAX_CONSTBUFFER_SCALARS = static_cast<u32>(Maxwell::MaxConstBufferSize) / sizeof(u32);
constexpr u32 MAX_CONSTBUFFER_ELEMENTS = MAX_CONSTBUFFER_SCALARS / sizeof(u32);
-constexpr std::string_view CommonDeclarations = R"(#define ftoi floatBitsToInt
+constexpr std::string_view COMMON_DECLARATIONS = R"(#define ftoi floatBitsToInt
#define ftou floatBitsToUint
#define itof intBitsToFloat
#define utof uintBitsToFloat
@@ -78,10 +77,6 @@ bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{
const float fswzadd_modifiers_a[] = float[4](-1.0f, 1.0f, -1.0f, 0.0f );
const float fswzadd_modifiers_b[] = float[4](-1.0f, -1.0f, 1.0f, -1.0f );
-
-layout (std140, binding = {}) uniform vs_config {{
- float y_direction;
-}};
)";
class ShaderWriter final {
@@ -131,7 +126,7 @@ private:
class Expression final {
public:
- Expression(std::string code, Type type) : code{std::move(code)}, type{type} {
+ Expression(std::string code_, Type type_) : code{std::move(code_)}, type{type_} {
ASSERT(type != Type::Void);
}
Expression() : type{Type::Void} {}
@@ -148,8 +143,8 @@ public:
ASSERT(type == Type::Void);
}
- std::string As(Type type) const {
- switch (type) {
+ std::string As(Type type_) const {
+ switch (type_) {
case Type::Bool:
return AsBool();
case Type::Bool2:
@@ -316,7 +311,7 @@ std::pair<const char*, u32> GetPrimitiveDescription(Maxwell::PrimitiveTopology t
case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
return {"triangles_adjacency", 6};
default:
- UNIMPLEMENTED_MSG("topology={}", static_cast<int>(topology));
+ UNIMPLEMENTED_MSG("topology={}", topology);
return {"points", 1};
}
}
@@ -342,7 +337,7 @@ std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
case Tegra::Shader::OutputTopology::TriangleStrip:
return "triangle_strip";
default:
- UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast<u32>(topology));
+ UNIMPLEMENTED_MSG("Unknown output topology: {}", topology);
return "points";
}
}
@@ -403,13 +398,6 @@ std::string FlowStackTopName(MetaStackClass stack) {
return fmt::format("{}_flow_stack_top", GetFlowStackPrefix(stack));
}
-bool UseUnifiedUniforms(const Device& device, const ShaderIR& ir, ShaderType stage) {
- const u32 num_ubos = static_cast<u32>(ir.GetConstantBuffers().size());
- // We waste one UBO for emulation
- const u32 num_available_ubos = device.GetMaxUniformBuffers(stage) - 1;
- return num_ubos > num_available_ubos;
-}
-
struct GenericVaryingDescription {
std::string name;
u8 first_element = 0;
@@ -418,11 +406,11 @@ struct GenericVaryingDescription {
class GLSLDecompiler final {
public:
- explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, const Registry& registry,
- ShaderType stage, std::string_view identifier, std::string_view suffix)
- : device{device}, ir{ir}, registry{registry}, stage{stage}, identifier{identifier},
- suffix{suffix}, header{ir.GetHeader()}, use_unified_uniforms{
- UseUnifiedUniforms(device, ir, stage)} {
+ explicit GLSLDecompiler(const Device& device_, const ShaderIR& ir_, const Registry& registry_,
+ ShaderType stage_, std::string_view identifier_,
+ std::string_view suffix_)
+ : device{device_}, ir{ir_}, registry{registry_}, stage{stage_},
+ identifier{identifier_}, suffix{suffix_}, header{ir.GetHeader()} {
if (stage != ShaderType::Compute) {
transform_feedback = BuildTransformFeedback(registry.GetGraphicsInfo());
}
@@ -516,7 +504,8 @@ private:
if (!identifier.empty()) {
code.AddLine("// {}", identifier);
}
- code.AddLine("#version 440 {}", ir.UsesLegacyVaryings() ? "compatibility" : "core");
+ const bool use_compatibility = ir.UsesLegacyVaryings() || ir.UsesYNegate();
+ code.AddLine("#version 440 {}", use_compatibility ? "compatibility" : "core");
code.AddLine("#extension GL_ARB_separate_shader_objects : enable");
if (device.HasShaderBallot()) {
code.AddLine("#extension GL_ARB_shader_ballot : require");
@@ -542,7 +531,7 @@ private:
code.AddNewLine();
- code.AddLine(CommonDeclarations, EmulationUniformBlockBinding);
+ code.AddLine(COMMON_DECLARATIONS);
}
void DeclareVertex() {
@@ -744,7 +733,7 @@ private:
case PixelImap::Unused:
break;
}
- UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<int>(attribute));
+ UNIMPLEMENTED_MSG("Unknown attribute usage index={}", attribute);
return {};
}
@@ -777,16 +766,16 @@ private:
name = "gs_" + name + "[]";
}
- std::string suffix;
+ std::string suffix_;
if (stage == ShaderType::Fragment) {
const auto input_mode{header.ps.GetPixelImap(location)};
if (input_mode == PixelImap::Unused) {
return;
}
- suffix = GetInputFlags(input_mode);
+ suffix_ = GetInputFlags(input_mode);
}
- code.AddLine("layout (location = {}) {} in vec4 {};", location, suffix, name);
+ code.AddLine("layout (location = {}) {} in vec4 {};", location, suffix_, name);
}
void DeclareOutputAttributes() {
@@ -813,7 +802,7 @@ private:
const u8 location = static_cast<u8>(static_cast<u32>(index) * 4 + element);
const auto it = transform_feedback.find(location);
if (it == transform_feedback.end()) {
- return {};
+ return std::nullopt;
}
return it->second.components;
}
@@ -865,20 +854,9 @@ private:
}
void DeclareConstantBuffers() {
- if (use_unified_uniforms) {
- const u32 binding = device.GetBaseBindings(stage).shader_storage_buffer +
- static_cast<u32>(ir.GetGlobalMemory().size());
- code.AddLine("layout (std430, binding = {}) readonly buffer UnifiedUniforms {{",
- binding);
- code.AddLine(" uint cbufs[];");
- code.AddLine("}};");
- code.AddNewLine();
- return;
- }
-
u32 binding = device.GetBaseBindings(stage).uniform_buffer;
- for (const auto [index, info] : ir.GetConstantBuffers()) {
- const u32 num_elements = Common::AlignUp(info.GetSize(), 4) / 4;
+ for (const auto& [index, info] : ir.GetConstantBuffers()) {
+ const u32 num_elements = Common::DivCeil(info.GetSize(), 4 * sizeof(u32));
const u32 size = info.IsIndirect() ? MAX_CONSTBUFFER_ELEMENTS : num_elements;
code.AddLine("layout (std140, binding = {}) uniform {} {{", binding++,
GetConstBufferBlock(index));
@@ -1081,29 +1059,17 @@ private:
if (const auto cbuf = std::get_if<CbufNode>(&*node)) {
const Node offset = cbuf->GetOffset();
- const u32 base_unified_offset = cbuf->GetIndex() * MAX_CONSTBUFFER_SCALARS;
if (const auto immediate = std::get_if<ImmediateNode>(&*offset)) {
// Direct access
const u32 offset_imm = immediate->GetValue();
ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access");
- if (use_unified_uniforms) {
- return {fmt::format("cbufs[{}]", base_unified_offset + offset_imm / 4),
- Type::Uint};
- } else {
- return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
- offset_imm / (4 * 4), (offset_imm / 4) % 4),
- Type::Uint};
- }
- }
-
- // Indirect access
- if (use_unified_uniforms) {
- return {fmt::format("cbufs[{} + ({} >> 2)]", base_unified_offset,
- Visit(offset).AsUint()),
+ return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
+ offset_imm / (4 * 4), (offset_imm / 4) % 4),
Type::Uint};
}
+ // Indirect access
const std::string final_offset = code.GenerateTemporary();
code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint());
@@ -1251,7 +1217,7 @@ private:
}
break;
}
- UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
+ UNIMPLEMENTED_MSG("Unhandled input attribute: {}", attribute);
return {"0", Type::Int};
}
@@ -1295,21 +1261,21 @@ private:
switch (element) {
case 0:
UNIMPLEMENTED();
- return {};
+ return std::nullopt;
case 1:
if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) {
- return {};
+ return std::nullopt;
}
return {{"gl_Layer", Type::Int}};
case 2:
if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) {
- return {};
+ return std::nullopt;
}
return {{"gl_ViewportIndex", Type::Int}};
case 3:
return {{"gl_PointSize", Type::Float}};
}
- return {};
+ return std::nullopt;
case Attribute::Index::FrontColor:
return {{"gl_FrontColor"s + GetSwizzle(element), Type::Float}};
case Attribute::Index::FrontSecondaryColor:
@@ -1331,8 +1297,8 @@ private:
GetSwizzle(element)),
Type::Float}};
}
- UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute));
- return {};
+ UNIMPLEMENTED_MSG("Unhandled output attribute: {}", attribute);
+ return std::nullopt;
}
}
@@ -1443,8 +1409,10 @@ private:
return expr + ", vec2(0.0), vec2(0.0))";
case TextureType::TextureCube:
return expr + ", vec3(0.0), vec3(0.0))";
+ default:
+ UNREACHABLE();
+ break;
}
- UNREACHABLE();
}
for (const auto& variant : extras) {
@@ -2054,15 +2022,19 @@ private:
}
Expression Texture(Operation operation) {
- const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
- ASSERT(meta);
-
- std::string expr = GenerateTexture(
- operation, "", {TextureOffset{}, TextureArgument{Type::Float, meta->bias}});
- if (meta->sampler.is_shadow) {
- expr = "vec4(" + expr + ')';
+ const auto meta = std::get<MetaTexture>(operation.GetMeta());
+ const bool separate_dc = meta.sampler.type == TextureType::TextureCube &&
+ meta.sampler.is_array && meta.sampler.is_shadow;
+ // TODO: Replace this with an array and make GenerateTexture use C++20 std::span
+ const std::vector<TextureIR> extras{
+ TextureOffset{},
+ TextureArgument{Type::Float, meta.bias},
+ };
+ std::string expr = GenerateTexture(operation, "", extras, separate_dc);
+ if (meta.sampler.is_shadow) {
+ expr = fmt::format("vec4({})", expr);
}
- return {expr + GetSwizzle(meta->element), Type::Float};
+ return {expr + GetSwizzle(meta.element), Type::Float};
}
Expression TextureLod(Operation operation) {
@@ -2094,13 +2066,13 @@ private:
const auto type = meta.sampler.is_shadow ? Type::Float : Type::Int;
const bool separate_dc = meta.sampler.is_shadow;
- std::vector<TextureIR> ir;
+ std::vector<TextureIR> ir_;
if (meta.sampler.is_shadow) {
- ir = {TextureOffset{}};
+ ir_ = {TextureOffset{}};
} else {
- ir = {TextureOffset{}, TextureArgument{type, meta.component}};
+ ir_ = {TextureOffset{}, TextureArgument{type, meta.component}};
}
- return {GenerateTexture(operation, "Gather", ir, separate_dc) + GetSwizzle(meta.element),
+ return {GenerateTexture(operation, "Gather", ir_, separate_dc) + GetSwizzle(meta.element),
Type::Float};
}
@@ -2287,7 +2259,6 @@ private:
}
}
}
-
if (header.ps.omap.depth) {
// The depth output is always 2 registers after the last color output, and current_reg
// already contains one past the last color register.
@@ -2331,7 +2302,8 @@ private:
}
Expression YNegate(Operation operation) {
- return {"y_direction", Type::Float};
+ // Y_NEGATE is mapped to this uniform value
+ return {"gl_FrontMaterial.ambient.a", Type::Float};
}
template <u32 element>
@@ -2746,11 +2718,11 @@ private:
}
}
- std::string GetSampler(const Sampler& sampler) const {
+ std::string GetSampler(const SamplerEntry& sampler) const {
return AppendSuffix(sampler.index, "sampler");
}
- std::string GetImage(const Image& image) const {
+ std::string GetImage(const ImageEntry& image) const {
return AppendSuffix(image.index, "image");
}
@@ -2781,7 +2753,6 @@ private:
const std::string_view identifier;
const std::string_view suffix;
const Header header;
- const bool use_unified_uniforms;
std::unordered_map<u8, VaryingTFB> transform_feedback;
ShaderWriter code;
@@ -2795,7 +2766,7 @@ std::string GetFlowVariable(u32 index) {
class ExprDecompiler {
public:
- explicit ExprDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
+ explicit ExprDecompiler(GLSLDecompiler& decomp_) : decomp{decomp_} {}
void operator()(const ExprAnd& expr) {
inner += '(';
@@ -2850,7 +2821,7 @@ private:
class ASTDecompiler {
public:
- explicit ASTDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
+ explicit ASTDecompiler(GLSLDecompiler& decomp_) : decomp{decomp_} {}
void operator()(const ASTProgram& ast) {
ASTNode current = ast.nodes.GetFirst();
@@ -2997,8 +2968,10 @@ ShaderEntries MakeEntries(const Device& device, const ShaderIR& ir, ShaderType s
for (std::size_t i = 0; i < std::size(clip_distances); ++i) {
entries.clip_distances = (clip_distances[i] ? 1U : 0U) << i;
}
+ for (const auto& buffer : entries.const_buffers) {
+ entries.enabled_uniform_buffers |= 1U << buffer.GetIndex();
+ }
entries.shader_length = ir.GetLength();
- entries.use_unified_uniforms = UseUnifiedUniforms(device, ir, stage);
return entries;
}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index 451c9689a..0397a000c 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -20,13 +20,13 @@ namespace OpenGL {
class Device;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-using SamplerEntry = VideoCommon::Shader::Sampler;
-using ImageEntry = VideoCommon::Shader::Image;
+using SamplerEntry = VideoCommon::Shader::SamplerEntry;
+using ImageEntry = VideoCommon::Shader::ImageEntry;
class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer {
public:
- explicit ConstBufferEntry(u32 max_offset, bool is_indirect, u32 index)
- : VideoCommon::Shader::ConstBuffer{max_offset, is_indirect}, index{index} {}
+ explicit ConstBufferEntry(u32 max_offset_, bool is_indirect_, u32 index_)
+ : ConstBuffer{max_offset_, is_indirect_}, index{index_} {}
u32 GetIndex() const {
return index;
@@ -37,10 +37,10 @@ private:
};
struct GlobalMemoryEntry {
- constexpr explicit GlobalMemoryEntry(u32 cbuf_index, u32 cbuf_offset, bool is_read,
- bool is_written)
- : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset}, is_read{is_read}, is_written{
- is_written} {}
+ constexpr explicit GlobalMemoryEntry(u32 cbuf_index_, u32 cbuf_offset_, bool is_read_,
+ bool is_written_)
+ : cbuf_index{cbuf_index_}, cbuf_offset{cbuf_offset_}, is_read{is_read_}, is_written{
+ is_written_} {}
u32 cbuf_index = 0;
u32 cbuf_offset = 0;
@@ -55,7 +55,7 @@ struct ShaderEntries {
std::vector<ImageEntry> images;
std::size_t shader_length{};
u32 clip_distances{};
- bool use_unified_uniforms{};
+ u32 enabled_uniform_buffers{};
};
ShaderEntries MakeEntries(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 40c0877c1..955b2abc4 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -206,13 +206,17 @@ bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const {
flat_bindless_samplers.size();
}
-ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {}
+ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default;
ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default;
+void ShaderDiskCacheOpenGL::BindTitleID(u64 title_id_) {
+ title_id = title_id_;
+}
+
std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTransferable() {
// Skip games without title id
- const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0;
+ const bool has_title_id = title_id != 0;
if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) {
return std::nullopt;
}
@@ -313,8 +317,7 @@ std::optional<std::vector<ShaderDiskCachePrecompiled>> ShaderDiskCacheOpenGL::Lo
return std::nullopt;
}
}
-
- return std::move(entries);
+ return entries;
}
void ShaderDiskCacheOpenGL::InvalidateTransferable() {
@@ -340,7 +343,7 @@ void ShaderDiskCacheOpenGL::SaveEntry(const ShaderDiskCacheEntry& entry) {
}
const u64 id = entry.unique_identifier;
- if (stored_transferable.find(id) != stored_transferable.end()) {
+ if (stored_transferable.contains(id)) {
// The shader already exists
return;
}
@@ -474,7 +477,7 @@ std::string ShaderDiskCacheOpenGL::GetBaseDir() const {
}
std::string ShaderDiskCacheOpenGL::GetTitleID() const {
- return fmt::format("{:016X}", system.CurrentProcess()->GetTitleID());
+ return fmt::format("{:016X}", title_id);
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
index db2bb73bc..aef841c1d 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h
@@ -21,10 +21,6 @@
#include "video_core/engines/shader_type.h"
#include "video_core/shader/registry.h"
-namespace Core {
-class System;
-}
-
namespace Common::FS {
class IOFile;
}
@@ -70,9 +66,12 @@ struct ShaderDiskCachePrecompiled {
class ShaderDiskCacheOpenGL {
public:
- explicit ShaderDiskCacheOpenGL(Core::System& system);
+ explicit ShaderDiskCacheOpenGL();
~ShaderDiskCacheOpenGL();
+ /// Binds a title ID for all future operations.
+ void BindTitleID(u64 title_id);
+
/// Loads transferable cache. If file has a old version or on failure, it deletes the file.
std::optional<std::vector<ShaderDiskCacheEntry>> LoadTransferable();
@@ -157,8 +156,6 @@ private:
return LoadArrayFromPrecompiled(&object, 1);
}
- Core::System& system;
-
// Stores whole precompiled cache which will be read from or saved to the precompiled chache
// file
FileSys::VectorVfsFile precompiled_cache_virtual_file;
@@ -168,8 +165,11 @@ private:
// Stored transferable shaders
std::unordered_set<u64> stored_transferable;
+ /// Title ID to operate on
+ u64 title_id = 0;
+
// The cache has been loaded at boot
- bool is_usable{};
+ bool is_usable = false;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 691c6c79b..553e6e8d6 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -83,6 +83,21 @@ void ProgramManager::RestoreGuestPipeline() {
}
}
+void ProgramManager::BindHostCompute(GLuint program) {
+ if (use_assembly_programs) {
+ glDisable(GL_COMPUTE_PROGRAM_NV);
+ }
+ glUseProgram(program);
+ is_graphics_bound = false;
+}
+
+void ProgramManager::RestoreGuestCompute() {
+ if (use_assembly_programs) {
+ glEnable(GL_COMPUTE_PROGRAM_NV);
+ glUseProgram(0);
+ }
+}
+
void ProgramManager::UseVertexShader(GLuint program) {
if (use_assembly_programs) {
BindProgram(GL_VERTEX_PROGRAM_NV, program, current_state.vertex, vertex_enabled);
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index 950e0dfcb..ad42cce74 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -45,6 +45,12 @@ public:
/// Rewinds BindHostPipeline state changes.
void RestoreGuestPipeline();
+ /// Binds an OpenGL GLSL program object unsynchronized with the guest state.
+ void BindHostCompute(GLuint program);
+
+ /// Rewinds BindHostCompute state changes.
+ void RestoreGuestCompute();
+
void UseVertexShader(GLuint program);
void UseGeometryShader(GLuint program);
void UseFragmentShader(GLuint program);
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp
index d24fad3de..dbdf5230f 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.cpp
+++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp
@@ -13,7 +13,7 @@
#include "video_core/renderer_opengl/gl_state_tracker.h"
#define OFF(field_name) MAXWELL3D_REG_INDEX(field_name)
-#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / sizeof(u32))
+#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32)))
namespace OpenGL {
@@ -36,16 +36,10 @@ void SetupDirtyColorMasks(Tables& tables) {
FillBlock(tables[1], OFF(color_mask), NUM(color_mask), ColorMasks);
}
-void SetupDirtyVertexArrays(Tables& tables) {
- static constexpr std::size_t num_array = 3;
+void SetupDirtyVertexInstances(Tables& tables) {
static constexpr std::size_t instance_base_offset = 3;
for (std::size_t i = 0; i < Regs::NumVertexArrays; ++i) {
const std::size_t array_offset = OFF(vertex_array) + i * NUM(vertex_array[0]);
- const std::size_t limit_offset = OFF(vertex_array_limit) + i * NUM(vertex_array_limit[0]);
-
- FillBlock(tables, array_offset, num_array, VertexBuffer0 + i, VertexBuffers);
- FillBlock(tables, limit_offset, NUM(vertex_array_limit), VertexBuffer0 + i, VertexBuffers);
-
const std::size_t instance_array_offset = array_offset + instance_base_offset;
tables[0][instance_array_offset] = static_cast<u8>(VertexInstance0 + i);
tables[1][instance_array_offset] = VertexInstances;
@@ -214,16 +208,14 @@ void SetupDirtyMisc(Tables& tables) {
} // Anonymous namespace
-StateTracker::StateTracker(Core::System& system) : system{system} {}
-
-void StateTracker::Initialize() {
- auto& dirty = system.GPU().Maxwell3D().dirty;
+StateTracker::StateTracker(Tegra::GPU& gpu) : flags{gpu.Maxwell3D().dirty.flags} {
+ auto& dirty = gpu.Maxwell3D().dirty;
auto& tables = dirty.tables;
- SetupDirtyRenderTargets(tables);
+ SetupDirtyFlags(tables);
SetupDirtyColorMasks(tables);
SetupDirtyViewports(tables);
SetupDirtyScissors(tables);
- SetupDirtyVertexArrays(tables);
+ SetupDirtyVertexInstances(tables);
SetupDirtyVertexFormat(tables);
SetupDirtyShaders(tables);
SetupDirtyPolygonModes(tables);
@@ -243,12 +235,6 @@ void StateTracker::Initialize() {
SetupDirtyClipControl(tables);
SetupDirtyDepthClampEnabled(tables);
SetupDirtyMisc(tables);
-
- auto& store = dirty.on_write_stores;
- store[VertexBuffers] = true;
- for (std::size_t i = 0; i < Regs::NumVertexArrays; ++i) {
- store[VertexBuffer0 + i] = true;
- }
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.h b/src/video_core/renderer_opengl/gl_state_tracker.h
index 0f823288e..94c905116 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.h
+++ b/src/video_core/renderer_opengl/gl_state_tracker.h
@@ -13,8 +13,8 @@
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"
-namespace Core {
-class System;
+namespace Tegra {
+class GPU;
}
namespace OpenGL {
@@ -28,10 +28,6 @@ enum : u8 {
VertexFormat0,
VertexFormat31 = VertexFormat0 + 31,
- VertexBuffers,
- VertexBuffer0,
- VertexBuffer31 = VertexBuffer0 + 31,
-
VertexInstances,
VertexInstance0,
VertexInstance31 = VertexInstance0 + 31,
@@ -90,9 +86,7 @@ static_assert(Last <= std::numeric_limits<u8>::max());
class StateTracker {
public:
- explicit StateTracker(Core::System& system);
-
- void Initialize();
+ explicit StateTracker(Tegra::GPU& gpu);
void BindIndexBuffer(GLuint new_index_buffer) {
if (index_buffer == new_index_buffer) {
@@ -102,14 +96,40 @@ public:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, new_index_buffer);
}
+ void BindFramebuffer(GLuint new_framebuffer) {
+ if (framebuffer == new_framebuffer) {
+ return;
+ }
+ framebuffer = new_framebuffer;
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
+ }
+
+ void ClipControl(GLenum new_origin, GLenum new_depth) {
+ if (new_origin == origin && new_depth == depth) {
+ return;
+ }
+ origin = new_origin;
+ depth = new_depth;
+ glClipControl(origin, depth);
+ }
+
+ void SetYNegate(bool new_y_negate) {
+ if (new_y_negate == y_negate) {
+ return;
+ }
+ // Y_NEGATE is mapped to gl_FrontMaterial.ambient.a
+ y_negate = new_y_negate;
+ const std::array ambient{0.0f, 0.0f, 0.0f, y_negate ? -1.0f : 1.0f};
+ glMaterialfv(GL_FRONT, GL_AMBIENT, ambient.data());
+ }
+
void NotifyScreenDrawVertexArray() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::VertexFormats] = true;
flags[OpenGL::Dirty::VertexFormat0 + 0] = true;
flags[OpenGL::Dirty::VertexFormat0 + 1] = true;
- flags[OpenGL::Dirty::VertexBuffers] = true;
- flags[OpenGL::Dirty::VertexBuffer0] = true;
+ flags[VideoCommon::Dirty::VertexBuffers] = true;
+ flags[VideoCommon::Dirty::VertexBuffer0] = true;
flags[OpenGL::Dirty::VertexInstances] = true;
flags[OpenGL::Dirty::VertexInstance0 + 0] = true;
@@ -117,100 +137,87 @@ public:
}
void NotifyPolygonModes() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::PolygonModes] = true;
flags[OpenGL::Dirty::PolygonModeFront] = true;
flags[OpenGL::Dirty::PolygonModeBack] = true;
}
void NotifyViewport0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::Viewports] = true;
flags[OpenGL::Dirty::Viewport0] = true;
}
void NotifyScissor0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::Scissors] = true;
flags[OpenGL::Dirty::Scissor0] = true;
}
- void NotifyColorMask0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
+ void NotifyColorMask(size_t index) {
flags[OpenGL::Dirty::ColorMasks] = true;
- flags[OpenGL::Dirty::ColorMask0] = true;
+ flags[OpenGL::Dirty::ColorMask0 + index] = true;
}
void NotifyBlend0() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::BlendStates] = true;
flags[OpenGL::Dirty::BlendState0] = true;
}
void NotifyFramebuffer() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[VideoCommon::Dirty::RenderTargets] = true;
}
void NotifyFrontFace() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::FrontFace] = true;
}
void NotifyCullTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::CullTest] = true;
}
void NotifyDepthMask() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::DepthMask] = true;
}
void NotifyDepthTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::DepthTest] = true;
}
void NotifyStencilTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::StencilTest] = true;
}
void NotifyPolygonOffset() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::PolygonOffset] = true;
}
void NotifyRasterizeEnable() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::RasterizeEnable] = true;
}
void NotifyFramebufferSRGB() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::FramebufferSRGB] = true;
}
void NotifyLogicOp() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::LogicOp] = true;
}
void NotifyClipControl() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::ClipControl] = true;
}
void NotifyAlphaTest() {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
flags[OpenGL::Dirty::AlphaTest] = true;
}
private:
- Core::System& system;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
+ GLuint framebuffer = 0;
GLuint index_buffer = 0;
+ GLenum origin = GL_LOWER_LEFT;
+ GLenum depth = GL_NEGATIVE_ONE_TO_ONE;
+ bool y_negate = false;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
index 887995cf4..77b3ee0fe 100644
--- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp
@@ -1,80 +1,64 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <tuple>
-#include <vector>
+#include <array>
+#include <memory>
+#include <span>
+
+#include <glad/glad.h>
#include "common/alignment.h"
#include "common/assert.h"
-#include "common/microprofile.h"
-#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
-MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning",
- MP_RGB(128, 128, 192));
-
namespace OpenGL {
-OGLStreamBuffer::OGLStreamBuffer(const Device& device, GLsizeiptr size, bool vertex_data_usage)
- : buffer_size(size) {
- gl_buffer.Create();
-
- GLsizeiptr allocate_size = size;
- if (vertex_data_usage) {
- // On AMD GPU there is a strange crash in indexed drawing. The crash happens when the buffer
- // read position is near the end and is an out-of-bound access to the vertex buffer. This is
- // probably a bug in the driver and is related to the usage of vec3<byte> attributes in the
- // vertex array. Doubling the allocation size for the vertex buffer seems to avoid the
- // crash.
- allocate_size *= 2;
- }
-
- static constexpr GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT;
- glNamedBufferStorage(gl_buffer.handle, allocate_size, nullptr, flags);
- mapped_ptr = static_cast<u8*>(
- glMapNamedBufferRange(gl_buffer.handle, 0, buffer_size, flags | GL_MAP_FLUSH_EXPLICIT_BIT));
-
- if (device.UseAssemblyShaders() || device.HasVertexBufferUnifiedMemory()) {
- glMakeNamedBufferResidentNV(gl_buffer.handle, GL_READ_ONLY);
- glGetNamedBufferParameterui64vNV(gl_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &gpu_address);
+StreamBuffer::StreamBuffer() {
+ static constexpr GLenum flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
+ buffer.Create();
+ glObjectLabel(GL_BUFFER, buffer.handle, -1, "Stream Buffer");
+ glNamedBufferStorage(buffer.handle, STREAM_BUFFER_SIZE, nullptr, flags);
+ mapped_pointer =
+ static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, STREAM_BUFFER_SIZE, flags));
+ for (OGLSync& sync : fences) {
+ sync.Create();
}
}
-OGLStreamBuffer::~OGLStreamBuffer() {
- glUnmapNamedBuffer(gl_buffer.handle);
- gl_buffer.Release();
-}
-
-std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr alignment) {
- ASSERT(size <= buffer_size);
- ASSERT(alignment <= buffer_size);
- mapped_size = size;
-
- if (alignment > 0) {
- buffer_pos = Common::AlignUp<std::size_t>(buffer_pos, alignment);
+std::pair<std::span<u8>, size_t> StreamBuffer::Request(size_t size) noexcept {
+ ASSERT(size < REGION_SIZE);
+ for (size_t region = Region(used_iterator), region_end = Region(iterator); region < region_end;
+ ++region) {
+ fences[region].Create();
}
+ used_iterator = iterator;
- bool invalidate = false;
- if (buffer_pos + size > buffer_size) {
- MICROPROFILE_SCOPE(OpenGL_StreamBuffer);
- glInvalidateBufferData(gl_buffer.handle);
-
- buffer_pos = 0;
- invalidate = true;
+ for (size_t region = Region(free_iterator) + 1,
+ region_end = std::min(Region(iterator + size) + 1, NUM_SYNCS);
+ region < region_end; ++region) {
+ glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED);
+ fences[region].Release();
}
-
- return std::make_tuple(mapped_ptr + buffer_pos, buffer_pos, invalidate);
-}
-
-void OGLStreamBuffer::Unmap(GLsizeiptr size) {
- ASSERT(size <= mapped_size);
-
- if (size > 0) {
- glFlushMappedNamedBufferRange(gl_buffer.handle, buffer_pos, size);
+ if (iterator + size >= free_iterator) {
+ free_iterator = iterator + size;
}
-
- buffer_pos += size;
+ if (iterator + size > STREAM_BUFFER_SIZE) {
+ for (size_t region = Region(used_iterator); region < NUM_SYNCS; ++region) {
+ fences[region].Create();
+ }
+ used_iterator = 0;
+ iterator = 0;
+ free_iterator = size;
+
+ for (size_t region = 0, region_end = Region(size); region <= region_end; ++region) {
+ glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED);
+ fences[region].Release();
+ }
+ }
+ const size_t offset = iterator;
+ iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT);
+ return {std::span(mapped_pointer + offset, size), offset};
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h
index 307a67113..6dbb6bfba 100644
--- a/src/video_core/renderer_opengl/gl_stream_buffer.h
+++ b/src/video_core/renderer_opengl/gl_stream_buffer.h
@@ -1,55 +1,50 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
-#include <tuple>
+#include <array>
+#include <memory>
+#include <span>
+#include <utility>
+
#include <glad/glad.h>
+
#include "common/common_types.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
-class Device;
+class StreamBuffer {
+ static constexpr size_t STREAM_BUFFER_SIZE = 64 * 1024 * 1024;
+ static constexpr size_t NUM_SYNCS = 16;
+ static constexpr size_t REGION_SIZE = STREAM_BUFFER_SIZE / NUM_SYNCS;
+ static constexpr size_t MAX_ALIGNMENT = 256;
+ static_assert(STREAM_BUFFER_SIZE % MAX_ALIGNMENT == 0);
+ static_assert(STREAM_BUFFER_SIZE % NUM_SYNCS == 0);
+ static_assert(REGION_SIZE % MAX_ALIGNMENT == 0);
-class OGLStreamBuffer : private NonCopyable {
public:
- explicit OGLStreamBuffer(const Device& device, GLsizeiptr size, bool vertex_data_usage);
- ~OGLStreamBuffer();
-
- /*
- * Allocates a linear chunk of memory in the GPU buffer with at least "size" bytes
- * and the optional alignment requirement.
- * If the buffer is full, the whole buffer is reallocated which invalidates old chunks.
- * The return values are the pointer to the new chunk, the offset within the buffer,
- * and the invalidation flag for previous chunks.
- * The actual used size must be specified on unmapping the chunk.
- */
- std::tuple<u8*, GLintptr, bool> Map(GLsizeiptr size, GLintptr alignment = 0);
-
- void Unmap(GLsizeiptr size);
-
- GLuint Handle() const {
- return gl_buffer.handle;
- }
+ explicit StreamBuffer();
- u64 Address() const {
- return gpu_address;
- }
+ [[nodiscard]] std::pair<std::span<u8>, size_t> Request(size_t size) noexcept;
- GLsizeiptr Size() const noexcept {
- return buffer_size;
+ [[nodiscard]] GLuint Handle() const noexcept {
+ return buffer.handle;
}
private:
- OGLBuffer gl_buffer;
+ [[nodiscard]] static size_t Region(size_t offset) noexcept {
+ return offset / REGION_SIZE;
+ }
- GLuint64EXT gpu_address = 0;
- GLintptr buffer_pos = 0;
- GLsizeiptr buffer_size = 0;
- GLsizeiptr mapped_size = 0;
- u8* mapped_ptr = nullptr;
+ size_t iterator = 0;
+ size_t used_iterator = 0;
+ size_t free_iterator = 0;
+ u8* mapped_pointer = nullptr;
+ OGLBuffer buffer;
+ std::array<OGLSync, NUM_SYNCS> fences;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index f403f388a..12434db67 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -2,37 +2,60 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/assert.h"
-#include "common/bit_util.h"
-#include "common/common_types.h"
-#include "common/microprofile.h"
-#include "common/scope_exit.h"
-#include "core/core.h"
-#include "video_core/morton.h"
-#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <string>
+
+#include <glad/glad.h>
+
+#include "video_core/renderer_opengl/gl_device.h"
+#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"
-#include "video_core/renderer_opengl/utils.h"
-#include "video_core/texture_cache/surface_base.h"
+#include "video_core/renderer_opengl/maxwell_to_gl.h"
+#include "video_core/renderer_opengl/util_shaders.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/format_lookup_table.h"
+#include "video_core/texture_cache/samples_helper.h"
#include "video_core/texture_cache/texture_cache.h"
-#include "video_core/textures/convert.h"
-#include "video_core/textures/texture.h"
+#include "video_core/textures/decoders.h"
namespace OpenGL {
-using Tegra::Texture::SwizzleSource;
-using VideoCore::MortonSwizzleMode;
+namespace {
+using Tegra::Texture::SwizzleSource;
+using Tegra::Texture::TextureMipmapFilter;
+using Tegra::Texture::TextureType;
+using Tegra::Texture::TICEntry;
+using Tegra::Texture::TSCEntry;
+using VideoCommon::CalculateLevelStrideAlignment;
+using VideoCommon::ImageCopy;
+using VideoCommon::ImageFlagBits;
+using VideoCommon::ImageType;
+using VideoCommon::NUM_RT;
+using VideoCommon::SamplesLog2;
+using VideoCommon::SwizzleParameters;
+using VideoCore::Surface::BytesPerBlock;
+using VideoCore::Surface::IsPixelFormatASTC;
+using VideoCore::Surface::IsPixelFormatSRGB;
+using VideoCore::Surface::MaxPixelFormat;
using VideoCore::Surface::PixelFormat;
-using VideoCore::Surface::SurfaceTarget;
using VideoCore::Surface::SurfaceType;
-MICROPROFILE_DEFINE(OpenGL_Texture_Upload, "OpenGL", "Texture Upload", MP_RGB(128, 192, 128));
-MICROPROFILE_DEFINE(OpenGL_Texture_Download, "OpenGL", "Texture Download", MP_RGB(128, 192, 128));
-MICROPROFILE_DEFINE(OpenGL_Texture_Buffer_Copy, "OpenGL", "Texture Buffer Copy",
- MP_RGB(128, 192, 128));
+struct CopyOrigin {
+ GLint level;
+ GLint x;
+ GLint y;
+ GLint z;
+};
-namespace {
+struct CopyRegion {
+ GLsizei width;
+ GLsizei height;
+ GLsizei depth;
+};
struct FormatTuple {
GLenum internal_format;
@@ -40,7 +63,7 @@ struct FormatTuple {
GLenum type = GL_NONE;
};
-constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{
+constexpr std::array<FormatTuple, MaxPixelFormat> FORMAT_TABLE = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // A8B8G8R8_UNORM
{GL_RGBA8_SNORM, GL_RGBA, GL_BYTE}, // A8B8G8R8_SNORM
{GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE}, // A8B8G8R8_SINT
@@ -103,72 +126,113 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format
{GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5_UNORM
{GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4_UNORM
{GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM
- // Compressed sRGB formats
- {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB
- {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB
- {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB
- {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7_SRGB
- {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // A4B4G4R4_UNORM
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, // ASTC_2D_4X4_SRGB
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, // ASTC_2D_8X8_SRGB
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, // ASTC_2D_8X5_SRGB
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}, // ASTC_2D_5X4_SRGB
- {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}, // ASTC_2D_5X5_UNORM
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}, // ASTC_2D_5X5_SRGB
- {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}, // ASTC_2D_10X8_UNORM
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}, // ASTC_2D_10X8_SRGB
- {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB
- {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB
- {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // ASTC_2D_12X12_SRGB
- {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6_UNORM
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}, // ASTC_2D_8X6_SRGB
- {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}, // ASTC_2D_6X5_UNORM
- {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}, // ASTC_2D_6X5_SRGB
- {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
-
- // Depth formats
- {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
- {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
-
- // DepthStencil formats
- {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
- {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7_SRGB
+ {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // A4B4G4R4_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, // ASTC_2D_4X4_SRGB
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, // ASTC_2D_8X8_SRGB
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, // ASTC_2D_8X5_SRGB
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}, // ASTC_2D_5X4_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_5x5_KHR}, // ASTC_2D_5X5_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}, // ASTC_2D_5X5_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_10x8_KHR}, // ASTC_2D_10X8_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}, // ASTC_2D_10X8_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // ASTC_2D_12X12_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}, // ASTC_2D_8X6_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_6x5_KHR}, // ASTC_2D_6X5_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}, // ASTC_2D_6X5_SRGB
+ {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
+ {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
+ {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
+ {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
+ {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
{GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL,
GL_FLOAT_32_UNSIGNED_INT_24_8_REV}, // D32_FLOAT_S8_UINT
}};
+constexpr std::array ACCELERATED_FORMATS{
+ GL_RGBA32F, GL_RGBA16F, GL_RG32F, GL_RG16F, GL_R11F_G11F_B10F, GL_R32F,
+ GL_R16F, GL_RGBA32UI, GL_RGBA16UI, GL_RGB10_A2UI, GL_RGBA8UI, GL_RG32UI,
+ GL_RG16UI, GL_RG8UI, GL_R32UI, GL_R16UI, GL_R8UI, GL_RGBA32I,
+ GL_RGBA16I, GL_RGBA8I, GL_RG32I, GL_RG16I, GL_RG8I, GL_R32I,
+ GL_R16I, GL_R8I, GL_RGBA16, GL_RGB10_A2, GL_RGBA8, GL_RG16,
+ GL_RG8, GL_R16, GL_R8, GL_RGBA16_SNORM, GL_RGBA8_SNORM, GL_RG16_SNORM,
+ GL_RG8_SNORM, GL_R16_SNORM, GL_R8_SNORM,
+};
+
const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
- ASSERT(static_cast<std::size_t>(pixel_format) < tex_format_tuples.size());
- return tex_format_tuples[static_cast<std::size_t>(pixel_format)];
+ ASSERT(static_cast<size_t>(pixel_format) < FORMAT_TABLE.size());
+ return FORMAT_TABLE[static_cast<size_t>(pixel_format)];
}
-GLenum GetTextureTarget(const SurfaceTarget& target) {
- switch (target) {
- case SurfaceTarget::TextureBuffer:
+GLenum ImageTarget(const VideoCommon::ImageInfo& info) {
+ switch (info.type) {
+ case ImageType::e1D:
+ return GL_TEXTURE_1D_ARRAY;
+ case ImageType::e2D:
+ if (info.num_samples > 1) {
+ return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
+ }
+ return GL_TEXTURE_2D_ARRAY;
+ case ImageType::e3D:
+ return GL_TEXTURE_3D;
+ case ImageType::Linear:
+ return GL_TEXTURE_2D_ARRAY;
+ case ImageType::Buffer:
return GL_TEXTURE_BUFFER;
- case SurfaceTarget::Texture1D:
+ }
+ UNREACHABLE_MSG("Invalid image type={}", info.type);
+ return GL_NONE;
+}
+
+GLenum ImageTarget(ImageViewType type, int num_samples = 1) {
+ const bool is_multisampled = num_samples > 1;
+ switch (type) {
+ case ImageViewType::e1D:
return GL_TEXTURE_1D;
- case SurfaceTarget::Texture2D:
- return GL_TEXTURE_2D;
- case SurfaceTarget::Texture3D:
+ case ImageViewType::e2D:
+ return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
+ case ImageViewType::Cube:
+ return GL_TEXTURE_CUBE_MAP;
+ case ImageViewType::e3D:
return GL_TEXTURE_3D;
- case SurfaceTarget::Texture1DArray:
+ case ImageViewType::e1DArray:
return GL_TEXTURE_1D_ARRAY;
- case SurfaceTarget::Texture2DArray:
- return GL_TEXTURE_2D_ARRAY;
- case SurfaceTarget::TextureCubemap:
- return GL_TEXTURE_CUBE_MAP;
- case SurfaceTarget::TextureCubeArray:
+ case ImageViewType::e2DArray:
+ return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_ARRAY;
+ case ImageViewType::CubeArray:
return GL_TEXTURE_CUBE_MAP_ARRAY;
+ case ImageViewType::Rect:
+ return GL_TEXTURE_RECTANGLE;
+ case ImageViewType::Buffer:
+ return GL_TEXTURE_BUFFER;
}
- UNREACHABLE();
- return {};
+ UNREACHABLE_MSG("Invalid image view type={}", type);
+ return GL_NONE;
}
-GLint GetSwizzleSource(SwizzleSource source) {
+GLenum TextureMode(PixelFormat format, bool is_first) {
+ switch (format) {
+ case PixelFormat::D24_UNORM_S8_UINT:
+ case PixelFormat::D32_FLOAT_S8_UINT:
+ return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX;
+ case PixelFormat::S8_UINT_D24_UNORM:
+ return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT;
+ default:
+ UNREACHABLE();
+ return GL_DEPTH_COMPONENT;
+ }
+}
+
+GLint Swizzle(SwizzleSource source) {
switch (source) {
case SwizzleSource::Zero:
return GL_ZERO;
@@ -184,529 +248,844 @@ GLint GetSwizzleSource(SwizzleSource source) {
case SwizzleSource::OneFloat:
return GL_ONE;
}
- UNREACHABLE();
+ UNREACHABLE_MSG("Invalid swizzle source={}", source);
return GL_NONE;
}
-GLenum GetComponent(PixelFormat format, bool is_first) {
- switch (format) {
- case PixelFormat::D24_UNORM_S8_UINT:
- case PixelFormat::D32_FLOAT_S8_UINT:
- return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX;
- case PixelFormat::S8_UINT_D24_UNORM:
- return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT;
+GLenum AttachmentType(PixelFormat format) {
+ switch (const SurfaceType type = VideoCore::Surface::GetFormatType(format); type) {
+ case SurfaceType::Depth:
+ return GL_DEPTH_ATTACHMENT;
+ case SurfaceType::DepthStencil:
+ return GL_DEPTH_STENCIL_ATTACHMENT;
default:
- UNREACHABLE();
- return GL_DEPTH_COMPONENT;
+ UNIMPLEMENTED_MSG("Unimplemented type={}", type);
+ return GL_NONE;
}
}
-void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) {
- if (params.IsBuffer()) {
- return;
+[[nodiscard]] bool IsConverted(const Device& device, PixelFormat format, ImageType type) {
+ if (!device.HasASTC() && IsPixelFormatASTC(format)) {
+ return true;
}
- glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, static_cast<GLint>(params.num_levels - 1));
- if (params.num_levels == 1) {
- glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0f);
+ switch (format) {
+ case PixelFormat::BC4_UNORM:
+ case PixelFormat::BC5_UNORM:
+ return type == ImageType::e3D;
+ default:
+ break;
}
+ return false;
}
-OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum internal_format,
- OGLBuffer& texture_buffer) {
- OGLTexture texture;
- texture.Create(target);
+[[nodiscard]] constexpr SwizzleSource ConvertGreenRed(SwizzleSource value) {
+ switch (value) {
+ case SwizzleSource::G:
+ return SwizzleSource::R;
+ default:
+ return value;
+ }
+}
- switch (params.target) {
- case SurfaceTarget::Texture1D:
- glTextureStorage1D(texture.handle, params.emulated_levels, internal_format, params.width);
- break;
- case SurfaceTarget::TextureBuffer:
- texture_buffer.Create();
- glNamedBufferStorage(texture_buffer.handle, params.width * params.GetBytesPerPixel(),
- nullptr, GL_DYNAMIC_STORAGE_BIT);
- glTextureBuffer(texture.handle, internal_format, texture_buffer.handle);
+void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4> swizzle) {
+ switch (format) {
+ case PixelFormat::D24_UNORM_S8_UINT:
+ case PixelFormat::D32_FLOAT_S8_UINT:
+ case PixelFormat::S8_UINT_D24_UNORM:
+ UNIMPLEMENTED_IF(swizzle[0] != SwizzleSource::R && swizzle[0] != SwizzleSource::G);
+ glTextureParameteri(handle, GL_DEPTH_STENCIL_TEXTURE_MODE,
+ TextureMode(format, swizzle[0] == SwizzleSource::R));
+ std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
break;
- case SurfaceTarget::Texture2D:
- case SurfaceTarget::TextureCubemap:
- glTextureStorage2D(texture.handle, params.emulated_levels, internal_format, params.width,
- params.height);
+ default:
break;
- case SurfaceTarget::Texture3D:
- case SurfaceTarget::Texture2DArray:
- case SurfaceTarget::TextureCubeArray:
- glTextureStorage3D(texture.handle, params.emulated_levels, internal_format, params.width,
- params.height, params.depth);
+ }
+ std::array<GLint, 4> gl_swizzle;
+ std::ranges::transform(swizzle, gl_swizzle.begin(), Swizzle);
+ glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
+}
+
+[[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime,
+ const VideoCommon::ImageInfo& info) {
+ // Disable accelerated uploads for now as they don't implement swizzled uploads
+ return false;
+ switch (info.type) {
+ case ImageType::e2D:
+ case ImageType::e3D:
+ case ImageType::Linear:
break;
default:
- UNREACHABLE();
+ return false;
+ }
+ const GLenum internal_format = GetFormatTuple(info.format).internal_format;
+ const auto& format_info = runtime.FormatInfo(info.type, internal_format);
+ if (format_info.is_compressed) {
+ return false;
+ }
+ if (std::ranges::find(ACCELERATED_FORMATS, internal_format) == ACCELERATED_FORMATS.end()) {
+ return false;
}
+ if (format_info.compatibility_by_size) {
+ return true;
+ }
+ const GLenum store_format = StoreFormat(BytesPerBlock(info.format));
+ const GLenum store_class = runtime.FormatInfo(info.type, store_format).compatibility_class;
+ return format_info.compatibility_class == store_class;
+}
- ApplyTextureDefaults(params, texture.handle);
+[[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset,
+ VideoCommon::SubresourceLayers subresource, GLenum target) {
+ switch (target) {
+ case GL_TEXTURE_2D_ARRAY:
+ case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
+ return CopyOrigin{
+ .level = static_cast<GLint>(subresource.base_level),
+ .x = static_cast<GLint>(offset.x),
+ .y = static_cast<GLint>(offset.y),
+ .z = static_cast<GLint>(subresource.base_layer),
+ };
+ case GL_TEXTURE_3D:
+ return CopyOrigin{
+ .level = static_cast<GLint>(subresource.base_level),
+ .x = static_cast<GLint>(offset.x),
+ .y = static_cast<GLint>(offset.y),
+ .z = static_cast<GLint>(offset.z),
+ };
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented copy target={}", target);
+ return CopyOrigin{.level = 0, .x = 0, .y = 0, .z = 0};
+ }
+}
- return texture;
+[[nodiscard]] CopyRegion MakeCopyRegion(VideoCommon::Extent3D extent,
+ VideoCommon::SubresourceLayers dst_subresource,
+ GLenum target) {
+ switch (target) {
+ case GL_TEXTURE_2D_ARRAY:
+ case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
+ return CopyRegion{
+ .width = static_cast<GLsizei>(extent.width),
+ .height = static_cast<GLsizei>(extent.height),
+ .depth = static_cast<GLsizei>(dst_subresource.num_layers),
+ };
+ case GL_TEXTURE_3D:
+ return CopyRegion{
+ .width = static_cast<GLsizei>(extent.width),
+ .height = static_cast<GLsizei>(extent.height),
+ .depth = static_cast<GLsizei>(extent.depth),
+ };
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented copy target={}", target);
+ return CopyRegion{.width = 0, .height = 0, .depth = 0};
+ }
}
-constexpr u32 EncodeSwizzle(SwizzleSource x_source, SwizzleSource y_source, SwizzleSource z_source,
- SwizzleSource w_source) {
- return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) |
- (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source);
+void AttachTexture(GLuint fbo, GLenum attachment, const ImageView* image_view) {
+ if (False(image_view->flags & VideoCommon::ImageViewFlagBits::Slice)) {
+ const GLuint texture = image_view->DefaultHandle();
+ glNamedFramebufferTexture(fbo, attachment, texture, 0);
+ return;
+ }
+ const GLuint texture = image_view->Handle(ImageViewType::e3D);
+ if (image_view->range.extent.layers > 1) {
+ // TODO: OpenGL doesn't support rendering to a fixed number of slices
+ glNamedFramebufferTexture(fbo, attachment, texture, 0);
+ } else {
+ const u32 slice = image_view->range.base.layer;
+ glNamedFramebufferTextureLayer(fbo, attachment, texture, 0, slice);
+ }
}
} // Anonymous namespace
-CachedSurface::CachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params,
- bool is_astc_supported)
- : VideoCommon::SurfaceBase<View>(gpu_addr, params, is_astc_supported) {
- if (is_converted) {
- internal_format = params.srgb_conversion ? GL_SRGB8_ALPHA8 : GL_RGBA8;
- format = GL_RGBA;
- type = GL_UNSIGNED_BYTE;
- } else {
- const auto& tuple{GetFormatTuple(params.pixel_format)};
- internal_format = tuple.internal_format;
- format = tuple.format;
- type = tuple.type;
- is_compressed = params.IsCompressed();
+ImageBufferMap::~ImageBufferMap() {
+ if (sync) {
+ sync->Create();
}
- target = GetTextureTarget(params.target);
- texture = CreateTexture(params, target, internal_format, texture_buffer);
- DecorateSurfaceName();
+}
- u32 num_layers = 1;
- if (params.is_layered || params.target == SurfaceTarget::Texture3D) {
- num_layers = params.depth;
+TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager& program_manager,
+ StateTracker& state_tracker_)
+ : device{device_}, state_tracker{state_tracker_}, util_shaders(program_manager) {
+ static constexpr std::array TARGETS{GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D};
+ for (size_t i = 0; i < TARGETS.size(); ++i) {
+ const GLenum target = TARGETS[i];
+ for (const FormatTuple& tuple : FORMAT_TABLE) {
+ const GLenum format = tuple.internal_format;
+ GLint compat_class;
+ GLint compat_type;
+ GLint is_compressed;
+ glGetInternalformativ(target, format, GL_IMAGE_COMPATIBILITY_CLASS, 1, &compat_class);
+ glGetInternalformativ(target, format, GL_IMAGE_FORMAT_COMPATIBILITY_TYPE, 1,
+ &compat_type);
+ glGetInternalformativ(target, format, GL_TEXTURE_COMPRESSED, 1, &is_compressed);
+ const FormatProperties properties{
+ .compatibility_class = static_cast<GLenum>(compat_class),
+ .compatibility_by_size = compat_type == GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE,
+ .is_compressed = is_compressed == GL_TRUE,
+ };
+ format_properties[i].emplace(format, properties);
+ }
}
-
- main_view =
- CreateViewInner(ViewParams(params.target, 0, num_layers, 0, params.num_levels), true);
+ has_broken_texture_view_formats = device.HasBrokenTextureViewFormats();
+
+ null_image_1d_array.Create(GL_TEXTURE_1D_ARRAY);
+ null_image_cube_array.Create(GL_TEXTURE_CUBE_MAP_ARRAY);
+ null_image_3d.Create(GL_TEXTURE_3D);
+ null_image_rect.Create(GL_TEXTURE_RECTANGLE);
+ glTextureStorage2D(null_image_1d_array.handle, 1, GL_R8, 1, 1);
+ glTextureStorage3D(null_image_cube_array.handle, 1, GL_R8, 1, 1, 6);
+ glTextureStorage3D(null_image_3d.handle, 1, GL_R8, 1, 1, 1);
+ glTextureStorage2D(null_image_rect.handle, 1, GL_R8, 1, 1);
+
+ std::array<GLuint, 4> new_handles;
+ glGenTextures(static_cast<GLsizei>(new_handles.size()), new_handles.data());
+ null_image_view_1d.handle = new_handles[0];
+ null_image_view_2d.handle = new_handles[1];
+ null_image_view_2d_array.handle = new_handles[2];
+ null_image_view_cube.handle = new_handles[3];
+ glTextureView(null_image_view_1d.handle, GL_TEXTURE_1D, null_image_1d_array.handle, GL_R8, 0, 1,
+ 0, 1);
+ glTextureView(null_image_view_2d.handle, GL_TEXTURE_2D, null_image_cube_array.handle, GL_R8, 0,
+ 1, 0, 1);
+ glTextureView(null_image_view_2d_array.handle, GL_TEXTURE_2D_ARRAY,
+ null_image_cube_array.handle, GL_R8, 0, 1, 0, 1);
+ glTextureView(null_image_view_cube.handle, GL_TEXTURE_CUBE_MAP, null_image_cube_array.handle,
+ GL_R8, 0, 1, 0, 6);
+ const std::array texture_handles{
+ null_image_1d_array.handle, null_image_cube_array.handle, null_image_3d.handle,
+ null_image_rect.handle, null_image_view_1d.handle, null_image_view_2d.handle,
+ null_image_view_2d_array.handle, null_image_view_cube.handle,
+ };
+ for (const GLuint handle : texture_handles) {
+ static constexpr std::array NULL_SWIZZLE{GL_ZERO, GL_ZERO, GL_ZERO, GL_ZERO};
+ glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, NULL_SWIZZLE.data());
+ }
+ const auto set_view = [this](ImageViewType type, GLuint handle) {
+ if (device.HasDebuggingToolAttached()) {
+ const std::string name = fmt::format("NullImage {}", type);
+ glObjectLabel(GL_TEXTURE, handle, static_cast<GLsizei>(name.size()), name.data());
+ }
+ null_image_views[static_cast<size_t>(type)] = handle;
+ };
+ set_view(ImageViewType::e1D, null_image_view_1d.handle);
+ set_view(ImageViewType::e2D, null_image_view_2d.handle);
+ set_view(ImageViewType::Cube, null_image_view_cube.handle);
+ set_view(ImageViewType::e3D, null_image_3d.handle);
+ set_view(ImageViewType::e1DArray, null_image_1d_array.handle);
+ set_view(ImageViewType::e2DArray, null_image_view_2d_array.handle);
+ set_view(ImageViewType::CubeArray, null_image_cube_array.handle);
+ set_view(ImageViewType::Rect, null_image_rect.handle);
}
-CachedSurface::~CachedSurface() = default;
-
-void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) {
- MICROPROFILE_SCOPE(OpenGL_Texture_Download);
+TextureCacheRuntime::~TextureCacheRuntime() = default;
- if (params.IsBuffer()) {
- glGetNamedBufferSubData(texture_buffer.handle, 0,
- static_cast<GLsizeiptr>(params.GetHostSizeInBytes(false)),
- staging_buffer.data());
- return;
- }
+void TextureCacheRuntime::Finish() {
+ glFinish();
+}
- SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); });
+ImageBufferMap TextureCacheRuntime::UploadStagingBuffer(size_t size) {
+ return upload_buffers.RequestMap(size, true);
+}
- for (u32 level = 0; level < params.emulated_levels; ++level) {
- glPixelStorei(GL_PACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level, is_converted)));
- glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level)));
- const std::size_t mip_offset = params.GetHostMipmapLevelOffset(level, is_converted);
+ImageBufferMap TextureCacheRuntime::DownloadStagingBuffer(size_t size) {
+ return download_buffers.RequestMap(size, false);
+}
- u8* const mip_data = staging_buffer.data() + mip_offset;
- const GLsizei size = static_cast<GLsizei>(params.GetHostMipmapSize(level));
- if (is_compressed) {
- glGetCompressedTextureImage(texture.handle, level, size, mip_data);
- } else {
- glGetTextureImage(texture.handle, level, format, type, size, mip_data);
- }
+void TextureCacheRuntime::CopyImage(Image& dst_image, Image& src_image,
+ std::span<const ImageCopy> copies) {
+ const GLuint dst_name = dst_image.Handle();
+ const GLuint src_name = src_image.Handle();
+ const GLenum dst_target = ImageTarget(dst_image.info);
+ const GLenum src_target = ImageTarget(src_image.info);
+ for (const ImageCopy& copy : copies) {
+ const auto src_origin = MakeCopyOrigin(copy.src_offset, copy.src_subresource, src_target);
+ const auto dst_origin = MakeCopyOrigin(copy.dst_offset, copy.dst_subresource, dst_target);
+ const auto region = MakeCopyRegion(copy.extent, copy.dst_subresource, dst_target);
+ glCopyImageSubData(src_name, src_target, src_origin.level, src_origin.x, src_origin.y,
+ src_origin.z, dst_name, dst_target, dst_origin.level, dst_origin.x,
+ dst_origin.y, dst_origin.z, region.width, region.height, region.depth);
}
}
-void CachedSurface::UploadTexture(const std::vector<u8>& staging_buffer) {
- MICROPROFILE_SCOPE(OpenGL_Texture_Upload);
- SCOPE_EXIT({ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); });
- for (u32 level = 0; level < params.emulated_levels; ++level) {
- UploadTextureMipmap(level, staging_buffer);
+bool TextureCacheRuntime::CanImageBeCopied(const Image& dst, const Image& src) {
+ if (dst.info.type == ImageType::e3D && dst.info.format == PixelFormat::BC4_UNORM) {
+ return false;
}
+ return true;
}
-void CachedSurface::UploadTextureMipmap(u32 level, const std::vector<u8>& staging_buffer) {
- glPixelStorei(GL_UNPACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level, is_converted)));
- glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level)));
-
- const std::size_t mip_offset = params.GetHostMipmapLevelOffset(level, is_converted);
- const u8* buffer{staging_buffer.data() + mip_offset};
- if (is_compressed) {
- const auto image_size{static_cast<GLsizei>(params.GetHostMipmapSize(level))};
- switch (params.target) {
- case SurfaceTarget::Texture2D:
- glCompressedTextureSubImage2D(texture.handle, level, 0, 0,
- static_cast<GLsizei>(params.GetMipWidth(level)),
- static_cast<GLsizei>(params.GetMipHeight(level)),
- internal_format, image_size, buffer);
- break;
- case SurfaceTarget::Texture3D:
- case SurfaceTarget::Texture2DArray:
- case SurfaceTarget::TextureCubeArray:
- glCompressedTextureSubImage3D(texture.handle, level, 0, 0, 0,
- static_cast<GLsizei>(params.GetMipWidth(level)),
- static_cast<GLsizei>(params.GetMipHeight(level)),
- static_cast<GLsizei>(params.GetMipDepth(level)),
- internal_format, image_size, buffer);
- break;
- case SurfaceTarget::TextureCubemap: {
- const std::size_t layer_size{params.GetHostLayerSize(level)};
- for (std::size_t face = 0; face < params.depth; ++face) {
- glCompressedTextureSubImage3D(texture.handle, level, 0, 0, static_cast<GLint>(face),
- static_cast<GLsizei>(params.GetMipWidth(level)),
- static_cast<GLsizei>(params.GetMipHeight(level)), 1,
- internal_format, static_cast<GLsizei>(layer_size),
- buffer);
- buffer += layer_size;
- }
- break;
- }
- default:
- UNREACHABLE();
- }
+void TextureCacheRuntime::EmulateCopyImage(Image& dst, Image& src,
+ std::span<const ImageCopy> copies) {
+ if (dst.info.type == ImageType::e3D && dst.info.format == PixelFormat::BC4_UNORM) {
+ ASSERT(src.info.type == ImageType::e3D);
+ util_shaders.CopyBC4(dst, src, copies);
} else {
- switch (params.target) {
- case SurfaceTarget::Texture1D:
- glTextureSubImage1D(texture.handle, level, 0, params.GetMipWidth(level), format, type,
- buffer);
- break;
- case SurfaceTarget::TextureBuffer:
- ASSERT(level == 0);
- glNamedBufferSubData(texture_buffer.handle, 0,
- params.GetMipWidth(level) * params.GetBytesPerPixel(), buffer);
- break;
- case SurfaceTarget::Texture1DArray:
- case SurfaceTarget::Texture2D:
- glTextureSubImage2D(texture.handle, level, 0, 0, params.GetMipWidth(level),
- params.GetMipHeight(level), format, type, buffer);
- break;
- case SurfaceTarget::Texture3D:
- case SurfaceTarget::Texture2DArray:
- case SurfaceTarget::TextureCubeArray:
- glTextureSubImage3D(
- texture.handle, level, 0, 0, 0, static_cast<GLsizei>(params.GetMipWidth(level)),
- static_cast<GLsizei>(params.GetMipHeight(level)),
- static_cast<GLsizei>(params.GetMipDepth(level)), format, type, buffer);
- break;
- case SurfaceTarget::TextureCubemap:
- for (std::size_t face = 0; face < params.depth; ++face) {
- glTextureSubImage3D(texture.handle, level, 0, 0, static_cast<GLint>(face),
- params.GetMipWidth(level), params.GetMipHeight(level), 1,
- format, type, buffer);
- buffer += params.GetHostLayerSize(level);
- }
- break;
- default:
- UNREACHABLE();
- }
+ UNREACHABLE();
}
}
-void CachedSurface::DecorateSurfaceName() {
- LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName());
-}
+void TextureCacheRuntime::BlitFramebuffer(Framebuffer* dst, Framebuffer* src,
+ const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ Tegra::Engines::Fermi2D::Filter filter,
+ Tegra::Engines::Fermi2D::Operation operation) {
+ state_tracker.NotifyScissor0();
+ state_tracker.NotifyRasterizeEnable();
+ state_tracker.NotifyFramebufferSRGB();
-void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix) {
- LabelGLObject(GL_TEXTURE, main_view.handle, gpu_addr, prefix);
+ ASSERT(dst->BufferBits() == src->BufferBits());
+
+ glEnable(GL_FRAMEBUFFER_SRGB);
+ glDisable(GL_RASTERIZER_DISCARD);
+ glDisablei(GL_SCISSOR_TEST, 0);
+
+ const GLbitfield buffer_bits = dst->BufferBits();
+ const bool has_depth = (buffer_bits & ~GL_COLOR_BUFFER_BIT) != 0;
+ const bool is_linear = !has_depth && filter == Tegra::Engines::Fermi2D::Filter::Bilinear;
+ glBlitNamedFramebuffer(src->Handle(), dst->Handle(), src_region[0].x, src_region[0].y,
+ src_region[1].x, src_region[1].y, dst_region[0].x, dst_region[0].y,
+ dst_region[1].x, dst_region[1].y, buffer_bits,
+ is_linear ? GL_LINEAR : GL_NEAREST);
}
-View CachedSurface::CreateView(const ViewParams& view_key) {
- return CreateViewInner(view_key, false);
+void TextureCacheRuntime::AccelerateImageUpload(Image& image, const ImageBufferMap& map,
+ std::span<const SwizzleParameters> swizzles) {
+ switch (image.info.type) {
+ case ImageType::e2D:
+ return util_shaders.BlockLinearUpload2D(image, map, swizzles);
+ case ImageType::e3D:
+ return util_shaders.BlockLinearUpload3D(image, map, swizzles);
+ case ImageType::Linear:
+ return util_shaders.PitchUpload(image, map, swizzles);
+ default:
+ UNREACHABLE();
+ break;
+ }
}
-View CachedSurface::CreateViewInner(const ViewParams& view_key, const bool is_proxy) {
- auto view = std::make_shared<CachedSurfaceView>(*this, view_key, is_proxy);
- views[view_key] = view;
- if (!is_proxy)
- view->DecorateViewName(gpu_addr, params.TargetName() + "V:" + std::to_string(view_count++));
- return view;
+void TextureCacheRuntime::InsertUploadMemoryBarrier() {
+ glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}
-CachedSurfaceView::CachedSurfaceView(CachedSurface& surface, const ViewParams& params,
- bool is_proxy)
- : VideoCommon::ViewBase(params), surface{surface}, format{surface.internal_format},
- target{GetTextureTarget(params.target)}, is_proxy{is_proxy} {
- if (!is_proxy) {
- main_view = CreateTextureView();
+FormatProperties TextureCacheRuntime::FormatInfo(ImageType type, GLenum internal_format) const {
+ switch (type) {
+ case ImageType::e1D:
+ return format_properties[0].at(internal_format);
+ case ImageType::e2D:
+ case ImageType::Linear:
+ return format_properties[1].at(internal_format);
+ case ImageType::e3D:
+ return format_properties[2].at(internal_format);
+ default:
+ UNREACHABLE();
+ return FormatProperties{};
}
}
-CachedSurfaceView::~CachedSurfaceView() = default;
+TextureCacheRuntime::StagingBuffers::StagingBuffers(GLenum storage_flags_, GLenum map_flags_)
+ : storage_flags{storage_flags_}, map_flags{map_flags_} {}
-void CachedSurfaceView::Attach(GLenum attachment, GLenum fb_target) const {
- ASSERT(params.num_levels == 1);
+TextureCacheRuntime::StagingBuffers::~StagingBuffers() = default;
- if (params.target == SurfaceTarget::Texture3D) {
- if (params.num_layers > 1) {
- ASSERT(params.base_layer == 0);
- glFramebufferTexture(fb_target, attachment, surface.texture.handle, params.base_level);
- } else {
- glFramebufferTexture3D(fb_target, attachment, target, surface.texture.handle,
- params.base_level, params.base_layer);
- }
- return;
+ImageBufferMap TextureCacheRuntime::StagingBuffers::RequestMap(size_t requested_size,
+ bool insert_fence) {
+ const size_t index = RequestBuffer(requested_size);
+ OGLSync* const sync = insert_fence ? &syncs[index] : nullptr;
+ return ImageBufferMap{
+ .mapped_span = std::span(maps[index], requested_size),
+ .sync = sync,
+ .buffer = buffers[index].handle,
+ };
+}
+
+size_t TextureCacheRuntime::StagingBuffers::RequestBuffer(size_t requested_size) {
+ if (const std::optional<size_t> index = FindBuffer(requested_size); index) {
+ return *index;
}
- if (params.num_layers > 1) {
- UNIMPLEMENTED_IF(params.base_layer != 0);
- glFramebufferTexture(fb_target, attachment, GetTexture(), 0);
- return;
+ OGLBuffer& buffer = buffers.emplace_back();
+ buffer.Create();
+ glNamedBufferStorage(buffer.handle, requested_size, nullptr,
+ storage_flags | GL_MAP_PERSISTENT_BIT);
+ maps.push_back(static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, requested_size,
+ map_flags | GL_MAP_PERSISTENT_BIT)));
+
+ syncs.emplace_back();
+ sizes.push_back(requested_size);
+
+ ASSERT(syncs.size() == buffers.size() && buffers.size() == maps.size() &&
+ maps.size() == sizes.size());
+
+ return buffers.size() - 1;
+}
+
+std::optional<size_t> TextureCacheRuntime::StagingBuffers::FindBuffer(size_t requested_size) {
+ size_t smallest_buffer = std::numeric_limits<size_t>::max();
+ std::optional<size_t> found;
+ const size_t num_buffers = sizes.size();
+ for (size_t index = 0; index < num_buffers; ++index) {
+ const size_t buffer_size = sizes[index];
+ if (buffer_size < requested_size || buffer_size >= smallest_buffer) {
+ continue;
+ }
+ if (syncs[index].handle != 0) {
+ GLint status;
+ glGetSynciv(syncs[index].handle, GL_SYNC_STATUS, 1, nullptr, &status);
+ if (status != GL_SIGNALED) {
+ continue;
+ }
+ syncs[index].Release();
+ }
+ smallest_buffer = buffer_size;
+ found = index;
}
+ return found;
+}
- const GLenum view_target = surface.GetTarget();
- const GLuint texture = surface.GetTexture();
- switch (surface.GetSurfaceParams().target) {
- case SurfaceTarget::Texture1D:
- glFramebufferTexture1D(fb_target, attachment, view_target, texture, params.base_level);
+Image::Image(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info_, GPUVAddr gpu_addr_,
+ VAddr cpu_addr_)
+ : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_) {
+ if (CanBeAccelerated(runtime, info)) {
+ flags |= ImageFlagBits::AcceleratedUpload;
+ }
+ if (IsConverted(runtime.device, info.format, info.type)) {
+ flags |= ImageFlagBits::Converted;
+ gl_internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
+ gl_format = GL_RGBA;
+ gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ } else {
+ const auto& tuple = GetFormatTuple(info.format);
+ gl_internal_format = tuple.internal_format;
+ gl_format = tuple.format;
+ gl_type = tuple.type;
+ }
+ const GLenum target = ImageTarget(info);
+ const GLsizei width = info.size.width;
+ const GLsizei height = info.size.height;
+ const GLsizei depth = info.size.depth;
+ const int max_host_mip_levels = std::bit_width(info.size.width);
+ const GLsizei num_levels = std::min(info.resources.levels, max_host_mip_levels);
+ const GLsizei num_layers = info.resources.layers;
+ const GLsizei num_samples = info.num_samples;
+
+ GLuint handle = 0;
+ if (target != GL_TEXTURE_BUFFER) {
+ texture.Create(target);
+ handle = texture.handle;
+ }
+ switch (target) {
+ case GL_TEXTURE_1D_ARRAY:
+ glTextureStorage2D(handle, num_levels, gl_internal_format, width, num_layers);
+ break;
+ case GL_TEXTURE_2D_ARRAY:
+ glTextureStorage3D(handle, num_levels, gl_internal_format, width, height, num_layers);
break;
- case SurfaceTarget::Texture2D:
- glFramebufferTexture2D(fb_target, attachment, view_target, texture, params.base_level);
+ case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: {
+ // TODO: Where should 'fixedsamplelocations' come from?
+ const auto [samples_x, samples_y] = SamplesLog2(info.num_samples);
+ glTextureStorage3DMultisample(handle, num_samples, gl_internal_format, width >> samples_x,
+ height >> samples_y, num_layers, GL_FALSE);
+ break;
+ }
+ case GL_TEXTURE_RECTANGLE:
+ glTextureStorage2D(handle, num_levels, gl_internal_format, width, height);
break;
- case SurfaceTarget::Texture1DArray:
- case SurfaceTarget::Texture2DArray:
- case SurfaceTarget::TextureCubemap:
- case SurfaceTarget::TextureCubeArray:
- glFramebufferTextureLayer(fb_target, attachment, texture, params.base_level,
- params.base_layer);
+ case GL_TEXTURE_3D:
+ glTextureStorage3D(handle, num_levels, gl_internal_format, width, height, depth);
+ break;
+ case GL_TEXTURE_BUFFER:
+ buffer.Create();
+ glNamedBufferStorage(buffer.handle, guest_size_bytes, nullptr, 0);
break;
default:
- UNIMPLEMENTED();
+ UNREACHABLE_MSG("Invalid target=0x{:x}", target);
+ break;
+ }
+ if (runtime.device.HasDebuggingToolAttached()) {
+ const std::string name = VideoCommon::Name(*this);
+ glObjectLabel(target == GL_TEXTURE_BUFFER ? GL_BUFFER : GL_TEXTURE, handle,
+ static_cast<GLsizei>(name.size()), name.data());
}
}
-GLuint CachedSurfaceView::GetTexture(SwizzleSource x_source, SwizzleSource y_source,
- SwizzleSource z_source, SwizzleSource w_source) {
- if (GetSurfaceParams().IsBuffer()) {
- return GetTexture();
- }
- const u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source);
- if (current_swizzle == new_swizzle) {
- return current_view;
- }
- current_swizzle = new_swizzle;
+void Image::UploadMemory(const ImageBufferMap& map,
+ std::span<const VideoCommon::BufferImageCopy> copies) {
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, map.buffer);
+ glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, map.offset, unswizzled_size_bytes);
- const auto [entry, is_cache_miss] = view_cache.try_emplace(new_swizzle);
- OGLTextureView& view = entry->second;
- if (!is_cache_miss) {
- current_view = view.handle;
- return view.handle;
- }
- view = CreateTextureView();
- current_view = view.handle;
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- std::array swizzle{x_source, y_source, z_source, w_source};
+ u32 current_row_length = std::numeric_limits<u32>::max();
+ u32 current_image_height = std::numeric_limits<u32>::max();
- switch (const PixelFormat format = GetSurfaceParams().pixel_format) {
- case PixelFormat::D24_UNORM_S8_UINT:
- case PixelFormat::D32_FLOAT_S8_UINT:
- case PixelFormat::S8_UINT_D24_UNORM:
- UNIMPLEMENTED_IF(x_source != SwizzleSource::R && x_source != SwizzleSource::G);
- glTextureParameteri(view.handle, GL_DEPTH_STENCIL_TEXTURE_MODE,
- GetComponent(format, x_source == SwizzleSource::R));
-
- // Make sure we sample the first component
- std::transform(swizzle.begin(), swizzle.end(), swizzle.begin(), [](SwizzleSource value) {
- return value == SwizzleSource::G ? SwizzleSource::R : value;
- });
- [[fallthrough]];
- default: {
- const std::array gl_swizzle = {GetSwizzleSource(swizzle[0]), GetSwizzleSource(swizzle[1]),
- GetSwizzleSource(swizzle[2]), GetSwizzleSource(swizzle[3])};
- glTextureParameteriv(view.handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
- break;
- }
+ for (const VideoCommon::BufferImageCopy& copy : copies) {
+ if (current_row_length != copy.buffer_row_length) {
+ current_row_length = copy.buffer_row_length;
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, current_row_length);
+ }
+ if (current_image_height != copy.buffer_image_height) {
+ current_image_height = copy.buffer_image_height;
+ glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, current_image_height);
+ }
+ CopyBufferToImage(copy, map.offset);
}
- return view.handle;
}
-OGLTextureView CachedSurfaceView::CreateTextureView() const {
- OGLTextureView texture_view;
- texture_view.Create();
-
- if (target == GL_TEXTURE_3D) {
- glTextureView(texture_view.handle, target, surface.texture.handle, format,
- params.base_level, params.num_levels, 0, 1);
- } else {
- glTextureView(texture_view.handle, target, surface.texture.handle, format,
- params.base_level, params.num_levels, params.base_layer, params.num_layers);
+void Image::UploadMemory(const ImageBufferMap& map,
+ std::span<const VideoCommon::BufferCopy> copies) {
+ for (const VideoCommon::BufferCopy& copy : copies) {
+ glCopyNamedBufferSubData(map.buffer, buffer.handle, copy.src_offset + map.offset,
+ copy.dst_offset, copy.size);
}
- ApplyTextureDefaults(surface.GetSurfaceParams(), texture_view.handle);
-
- return texture_view;
}
-TextureCacheOpenGL::TextureCacheOpenGL(Core::System& system,
- VideoCore::RasterizerInterface& rasterizer,
- const Device& device, StateTracker& state_tracker)
- : TextureCacheBase{system, rasterizer, device.HasASTC()}, state_tracker{state_tracker} {
- src_framebuffer.Create();
- dst_framebuffer.Create();
-}
+void Image::DownloadMemory(ImageBufferMap& map,
+ std::span<const VideoCommon::BufferImageCopy> copies) {
+ glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT); // TODO: Move this to its own API
-TextureCacheOpenGL::~TextureCacheOpenGL() = default;
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, map.buffer);
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
-Surface TextureCacheOpenGL::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) {
- return std::make_shared<CachedSurface>(gpu_addr, params, is_astc_supported);
-}
+ u32 current_row_length = std::numeric_limits<u32>::max();
+ u32 current_image_height = std::numeric_limits<u32>::max();
-void TextureCacheOpenGL::ImageCopy(Surface& src_surface, Surface& dst_surface,
- const VideoCommon::CopyParams& copy_params) {
- const auto& src_params = src_surface->GetSurfaceParams();
- const auto& dst_params = dst_surface->GetSurfaceParams();
- if (src_params.type != dst_params.type) {
- // A fallback is needed
- return;
+ for (const VideoCommon::BufferImageCopy& copy : copies) {
+ if (current_row_length != copy.buffer_row_length) {
+ current_row_length = copy.buffer_row_length;
+ glPixelStorei(GL_PACK_ROW_LENGTH, current_row_length);
+ }
+ if (current_image_height != copy.buffer_image_height) {
+ current_image_height = copy.buffer_image_height;
+ glPixelStorei(GL_PACK_IMAGE_HEIGHT, current_image_height);
+ }
+ CopyImageToBuffer(copy, map.offset);
}
- const auto src_handle = src_surface->GetTexture();
- const auto src_target = src_surface->GetTarget();
- const auto dst_handle = dst_surface->GetTexture();
- const auto dst_target = dst_surface->GetTarget();
- glCopyImageSubData(src_handle, src_target, copy_params.source_level, copy_params.source_x,
- copy_params.source_y, copy_params.source_z, dst_handle, dst_target,
- copy_params.dest_level, copy_params.dest_x, copy_params.dest_y,
- copy_params.dest_z, copy_params.width, copy_params.height,
- copy_params.depth);
}
-void TextureCacheOpenGL::ImageBlit(View& src_view, View& dst_view,
- const Tegra::Engines::Fermi2D::Config& copy_config) {
- const auto& src_params{src_view->GetSurfaceParams()};
- const auto& dst_params{dst_view->GetSurfaceParams()};
- UNIMPLEMENTED_IF(src_params.depth != 1);
- UNIMPLEMENTED_IF(dst_params.depth != 1);
-
- state_tracker.NotifyScissor0();
- state_tracker.NotifyFramebuffer();
- state_tracker.NotifyRasterizeEnable();
- state_tracker.NotifyFramebufferSRGB();
-
- if (dst_params.srgb_conversion) {
- glEnable(GL_FRAMEBUFFER_SRGB);
- } else {
- glDisable(GL_FRAMEBUFFER_SRGB);
+GLuint Image::StorageHandle() noexcept {
+ switch (info.format) {
+ case PixelFormat::A8B8G8R8_SRGB:
+ case PixelFormat::B8G8R8A8_SRGB:
+ case PixelFormat::BC1_RGBA_SRGB:
+ case PixelFormat::BC2_SRGB:
+ case PixelFormat::BC3_SRGB:
+ case PixelFormat::BC7_SRGB:
+ case PixelFormat::ASTC_2D_4X4_SRGB:
+ case PixelFormat::ASTC_2D_8X8_SRGB:
+ case PixelFormat::ASTC_2D_8X5_SRGB:
+ case PixelFormat::ASTC_2D_5X4_SRGB:
+ case PixelFormat::ASTC_2D_5X5_SRGB:
+ case PixelFormat::ASTC_2D_10X8_SRGB:
+ case PixelFormat::ASTC_2D_6X6_SRGB:
+ case PixelFormat::ASTC_2D_10X10_SRGB:
+ case PixelFormat::ASTC_2D_12X12_SRGB:
+ case PixelFormat::ASTC_2D_8X6_SRGB:
+ case PixelFormat::ASTC_2D_6X5_SRGB:
+ if (store_view.handle != 0) {
+ return store_view.handle;
+ }
+ store_view.Create();
+ glTextureView(store_view.handle, ImageTarget(info), texture.handle, GL_RGBA8, 0,
+ info.resources.levels, 0, info.resources.layers);
+ return store_view.handle;
+ default:
+ return texture.handle;
}
- glDisable(GL_RASTERIZER_DISCARD);
- glDisablei(GL_SCISSOR_TEST, 0);
-
- glBindFramebuffer(GL_READ_FRAMEBUFFER, src_framebuffer.handle);
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst_framebuffer.handle);
-
- GLenum buffers = 0;
- if (src_params.type == SurfaceType::ColorTexture) {
- src_view->Attach(GL_COLOR_ATTACHMENT0, GL_READ_FRAMEBUFFER);
- glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
- 0);
+}
- dst_view->Attach(GL_COLOR_ATTACHMENT0, GL_DRAW_FRAMEBUFFER);
- glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
- 0);
+void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) {
+ // Compressed formats don't have a pixel format or type
+ const bool is_compressed = gl_format == GL_NONE;
+ const void* const offset = reinterpret_cast<const void*>(copy.buffer_offset + buffer_offset);
- buffers = GL_COLOR_BUFFER_BIT;
- } else if (src_params.type == SurfaceType::Depth) {
- glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
- src_view->Attach(GL_DEPTH_ATTACHMENT, GL_READ_FRAMEBUFFER);
- glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+ switch (info.type) {
+ case ImageType::e1D:
+ if (is_compressed) {
+ glCompressedTextureSubImage2D(texture.handle, copy.image_subresource.base_level,
+ copy.image_offset.x, copy.image_subresource.base_layer,
+ copy.image_extent.width,
+ copy.image_subresource.num_layers, gl_internal_format,
+ static_cast<GLsizei>(copy.buffer_size), offset);
+ } else {
+ glTextureSubImage2D(texture.handle, copy.image_subresource.base_level,
+ copy.image_offset.x, copy.image_subresource.base_layer,
+ copy.image_extent.width, copy.image_subresource.num_layers,
+ gl_format, gl_type, offset);
+ }
+ break;
+ case ImageType::e2D:
+ case ImageType::Linear:
+ if (is_compressed) {
+ glCompressedTextureSubImage3D(
+ texture.handle, copy.image_subresource.base_level, copy.image_offset.x,
+ copy.image_offset.y, copy.image_subresource.base_layer, copy.image_extent.width,
+ copy.image_extent.height, copy.image_subresource.num_layers, gl_internal_format,
+ static_cast<GLsizei>(copy.buffer_size), offset);
+ } else {
+ glTextureSubImage3D(texture.handle, copy.image_subresource.base_level,
+ copy.image_offset.x, copy.image_offset.y,
+ copy.image_subresource.base_layer, copy.image_extent.width,
+ copy.image_extent.height, copy.image_subresource.num_layers,
+ gl_format, gl_type, offset);
+ }
+ break;
+ case ImageType::e3D:
+ if (is_compressed) {
+ glCompressedTextureSubImage3D(
+ texture.handle, copy.image_subresource.base_level, copy.image_offset.x,
+ copy.image_offset.y, copy.image_offset.z, copy.image_extent.width,
+ copy.image_extent.height, copy.image_extent.depth, gl_internal_format,
+ static_cast<GLsizei>(copy.buffer_size), offset);
+ } else {
+ glTextureSubImage3D(texture.handle, copy.image_subresource.base_level,
+ copy.image_offset.x, copy.image_offset.y, copy.image_offset.z,
+ copy.image_extent.width, copy.image_extent.height,
+ copy.image_extent.depth, gl_format, gl_type, offset);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
- glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
- dst_view->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
- glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+void Image::CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) {
+ const GLint x_offset = copy.image_offset.x;
+ const GLsizei width = copy.image_extent.width;
- buffers = GL_DEPTH_BUFFER_BIT;
- } else if (src_params.type == SurfaceType::DepthStencil) {
- glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
- src_view->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_READ_FRAMEBUFFER);
+ const GLint level = copy.image_subresource.base_level;
+ const GLsizei buffer_size = static_cast<GLsizei>(copy.buffer_size);
+ void* const offset = reinterpret_cast<void*>(copy.buffer_offset + buffer_offset);
- glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
- dst_view->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
+ GLint y_offset = 0;
+ GLint z_offset = 0;
+ GLsizei height = 1;
+ GLsizei depth = 1;
- buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+ switch (info.type) {
+ case ImageType::e1D:
+ y_offset = copy.image_subresource.base_layer;
+ height = copy.image_subresource.num_layers;
+ break;
+ case ImageType::e2D:
+ case ImageType::Linear:
+ y_offset = copy.image_offset.y;
+ z_offset = copy.image_subresource.base_layer;
+ height = copy.image_extent.height;
+ depth = copy.image_subresource.num_layers;
+ break;
+ case ImageType::e3D:
+ y_offset = copy.image_offset.y;
+ z_offset = copy.image_offset.z;
+ height = copy.image_extent.height;
+ depth = copy.image_extent.depth;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ // Compressed formats don't have a pixel format or type
+ const bool is_compressed = gl_format == GL_NONE;
+ if (is_compressed) {
+ glGetCompressedTextureSubImage(texture.handle, level, x_offset, y_offset, z_offset, width,
+ height, depth, buffer_size, offset);
+ } else {
+ glGetTextureSubImage(texture.handle, level, x_offset, y_offset, z_offset, width, height,
+ depth, gl_format, gl_type, buffer_size, offset);
}
-
- const Common::Rectangle<u32>& src_rect = copy_config.src_rect;
- const Common::Rectangle<u32>& dst_rect = copy_config.dst_rect;
- const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear;
-
- glBlitFramebuffer(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.top),
- static_cast<GLint>(src_rect.right), static_cast<GLint>(src_rect.bottom),
- static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.top),
- static_cast<GLint>(dst_rect.right), static_cast<GLint>(dst_rect.bottom),
- buffers,
- is_linear && (buffers == GL_COLOR_BUFFER_BIT) ? GL_LINEAR : GL_NEAREST);
}
-void TextureCacheOpenGL::BufferCopy(Surface& src_surface, Surface& dst_surface) {
- MICROPROFILE_SCOPE(OpenGL_Texture_Buffer_Copy);
- const auto& src_params = src_surface->GetSurfaceParams();
- const auto& dst_params = dst_surface->GetSurfaceParams();
- UNIMPLEMENTED_IF(src_params.num_levels > 1 || dst_params.num_levels > 1);
+ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info,
+ ImageId image_id_, Image& image)
+ : VideoCommon::ImageViewBase{info, image.info, image_id_}, views{runtime.null_image_views} {
+ const Device& device = runtime.device;
+ if (True(image.flags & ImageFlagBits::Converted)) {
+ internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
+ } else {
+ internal_format = GetFormatTuple(format).internal_format;
+ }
+ VideoCommon::SubresourceRange flatten_range = info.range;
+ std::array<GLuint, 2> handles;
+ stored_views.reserve(2);
- const auto source_format = GetFormatTuple(src_params.pixel_format);
- const auto dest_format = GetFormatTuple(dst_params.pixel_format);
+ switch (info.type) {
+ case ImageViewType::e1DArray:
+ flatten_range.extent.layers = 1;
+ [[fallthrough]];
+ case ImageViewType::e1D:
+ glGenTextures(2, handles.data());
+ SetupView(device, image, ImageViewType::e1D, handles[0], info, flatten_range);
+ SetupView(device, image, ImageViewType::e1DArray, handles[1], info, info.range);
+ break;
+ case ImageViewType::e2DArray:
+ flatten_range.extent.layers = 1;
+ [[fallthrough]];
+ case ImageViewType::e2D:
+ if (True(flags & VideoCommon::ImageViewFlagBits::Slice)) {
+ // 2D and 2D array views on a 3D textures are used exclusively for render targets
+ ASSERT(info.range.extent.levels == 1);
+ const VideoCommon::SubresourceRange slice_range{
+ .base = {.level = info.range.base.level, .layer = 0},
+ .extent = {.levels = 1, .layers = 1},
+ };
+ glGenTextures(1, handles.data());
+ SetupView(device, image, ImageViewType::e3D, handles[0], info, slice_range);
+ break;
+ }
+ glGenTextures(2, handles.data());
+ SetupView(device, image, ImageViewType::e2D, handles[0], info, flatten_range);
+ SetupView(device, image, ImageViewType::e2DArray, handles[1], info, info.range);
+ break;
+ case ImageViewType::e3D:
+ glGenTextures(1, handles.data());
+ SetupView(device, image, ImageViewType::e3D, handles[0], info, info.range);
+ break;
+ case ImageViewType::CubeArray:
+ flatten_range.extent.layers = 6;
+ [[fallthrough]];
+ case ImageViewType::Cube:
+ glGenTextures(2, handles.data());
+ SetupView(device, image, ImageViewType::Cube, handles[0], info, flatten_range);
+ SetupView(device, image, ImageViewType::CubeArray, handles[1], info, info.range);
+ break;
+ case ImageViewType::Rect:
+ glGenTextures(1, handles.data());
+ SetupView(device, image, ImageViewType::Rect, handles[0], info, info.range);
+ break;
+ case ImageViewType::Buffer:
+ glCreateTextures(GL_TEXTURE_BUFFER, 1, handles.data());
+ SetupView(device, image, ImageViewType::Buffer, handles[0], info, info.range);
+ break;
+ }
+ default_handle = Handle(info.type);
+}
- const std::size_t source_size = src_surface->GetHostSizeInBytes();
- const std::size_t dest_size = dst_surface->GetHostSizeInBytes();
+ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageParams& params)
+ : VideoCommon::ImageViewBase{params}, views{runtime.null_image_views} {}
- const std::size_t buffer_size = std::max(source_size, dest_size);
+void ImageView::SetupView(const Device& device, Image& image, ImageViewType view_type,
+ GLuint handle, const VideoCommon::ImageViewInfo& info,
+ VideoCommon::SubresourceRange view_range) {
+ if (info.type == ImageViewType::Buffer) {
+ // TODO: Take offset from buffer cache
+ glTextureBufferRange(handle, internal_format, image.buffer.handle, 0,
+ image.guest_size_bytes);
+ } else {
+ const GLuint parent = image.texture.handle;
+ const GLenum target = ImageTarget(view_type, image.info.num_samples);
+ glTextureView(handle, target, parent, internal_format, view_range.base.level,
+ view_range.extent.levels, view_range.base.layer, view_range.extent.layers);
+ if (!info.IsRenderTarget()) {
+ ApplySwizzle(handle, format, info.Swizzle());
+ }
+ }
+ if (device.HasDebuggingToolAttached()) {
+ const std::string name = VideoCommon::Name(*this, view_type);
+ glObjectLabel(GL_TEXTURE, handle, static_cast<GLsizei>(name.size()), name.data());
+ }
+ stored_views.emplace_back().handle = handle;
+ views[static_cast<size_t>(view_type)] = handle;
+}
- GLuint copy_pbo_handle = FetchPBO(buffer_size);
+Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) {
+ const GLenum compare_mode = config.depth_compare_enabled ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE;
+ const GLenum compare_func = MaxwellToGL::DepthCompareFunc(config.depth_compare_func);
+ const GLenum mag = MaxwellToGL::TextureFilterMode(config.mag_filter, TextureMipmapFilter::None);
+ const GLenum min = MaxwellToGL::TextureFilterMode(config.min_filter, config.mipmap_filter);
+ const GLenum reduction_filter = MaxwellToGL::ReductionFilter(config.reduction_filter);
+ const GLint seamless = config.cubemap_interface_filtering ? GL_TRUE : GL_FALSE;
+
+ UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1);
+ UNIMPLEMENTED_IF(config.float_coord_normalization != 0);
+
+ sampler.Create();
+ const GLuint handle = sampler.handle;
+ glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u));
+ glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v));
+ glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p));
+ glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode);
+ glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func);
+ glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag);
+ glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min);
+ glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias());
+ glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod());
+ glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod());
+ glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data());
+
+ if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) {
+ glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, config.MaxAnisotropy());
+ } else {
+ LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required");
+ }
+ if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) {
+ glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter);
+ } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) {
+ LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required");
+ }
+ if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) {
+ glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless);
+ } else if (seamless == GL_FALSE) {
+ // We default to false because it's more common
+ LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required");
+ }
+}
- glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle);
+Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers,
+ ImageView* depth_buffer, const VideoCommon::RenderTargets& key) {
+ // Bind to READ_FRAMEBUFFER to stop Nvidia's driver from creating an EXT_framebuffer instead of
+ // a core framebuffer. EXT framebuffer attachments have to match in size and can be shared
+ // across contexts. yuzu doesn't share framebuffers across contexts and we need attachments with
+ // mismatching size, this is why core framebuffers are preferred.
+ GLuint handle;
+ glGenFramebuffers(1, &handle);
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, handle);
+
+ GLsizei num_buffers = 0;
+ std::array<GLenum, NUM_RT> gl_draw_buffers;
+ gl_draw_buffers.fill(GL_NONE);
+
+ for (size_t index = 0; index < color_buffers.size(); ++index) {
+ const ImageView* const image_view = color_buffers[index];
+ if (!image_view) {
+ continue;
+ }
+ buffer_bits |= GL_COLOR_BUFFER_BIT;
+ gl_draw_buffers[index] = GL_COLOR_ATTACHMENT0 + key.draw_buffers[index];
+ num_buffers = static_cast<GLsizei>(index + 1);
- if (src_surface->IsCompressed()) {
- glGetCompressedTextureImage(src_surface->GetTexture(), 0, static_cast<GLsizei>(source_size),
- nullptr);
- } else {
- glGetTextureImage(src_surface->GetTexture(), 0, source_format.format, source_format.type,
- static_cast<GLsizei>(source_size), nullptr);
+ const GLenum attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + index);
+ AttachTexture(handle, attachment, image_view);
}
- glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
- glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo_handle);
+ if (const ImageView* const image_view = depth_buffer; image_view) {
+ if (GetFormatType(image_view->format) == SurfaceType::DepthStencil) {
+ buffer_bits |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+ } else {
+ buffer_bits |= GL_DEPTH_BUFFER_BIT;
+ }
+ const GLenum attachment = AttachmentType(image_view->format);
+ AttachTexture(handle, attachment, image_view);
+ }
- const GLsizei width = static_cast<GLsizei>(dst_params.width);
- const GLsizei height = static_cast<GLsizei>(dst_params.height);
- const GLsizei depth = static_cast<GLsizei>(dst_params.depth);
- if (dst_surface->IsCompressed()) {
- LOG_CRITICAL(HW_GPU, "Compressed buffer copy is unimplemented!");
- UNREACHABLE();
+ if (num_buffers > 1) {
+ glNamedFramebufferDrawBuffers(handle, num_buffers, gl_draw_buffers.data());
+ } else if (num_buffers > 0) {
+ glNamedFramebufferDrawBuffer(handle, gl_draw_buffers[0]);
} else {
- switch (dst_params.target) {
- case SurfaceTarget::Texture1D:
- glTextureSubImage1D(dst_surface->GetTexture(), 0, 0, width, dest_format.format,
- dest_format.type, nullptr);
- break;
- case SurfaceTarget::Texture2D:
- glTextureSubImage2D(dst_surface->GetTexture(), 0, 0, 0, width, height,
- dest_format.format, dest_format.type, nullptr);
- break;
- case SurfaceTarget::Texture3D:
- case SurfaceTarget::Texture2DArray:
- case SurfaceTarget::TextureCubeArray:
- glTextureSubImage3D(dst_surface->GetTexture(), 0, 0, 0, 0, width, height, depth,
- dest_format.format, dest_format.type, nullptr);
- break;
- case SurfaceTarget::TextureCubemap:
- glTextureSubImage3D(dst_surface->GetTexture(), 0, 0, 0, 0, width, height, depth,
- dest_format.format, dest_format.type, nullptr);
- break;
- default:
- LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
- static_cast<u32>(dst_params.target));
- UNREACHABLE();
- }
+ glNamedFramebufferDrawBuffer(handle, GL_NONE);
}
- glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
- glTextureBarrier();
-}
+ glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_WIDTH, key.size.width);
+ glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_HEIGHT, key.size.height);
+ // TODO
+ // glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_LAYERS, ...);
+ // glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_SAMPLES, ...);
+ // glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS, ...);
-GLuint TextureCacheOpenGL::FetchPBO(std::size_t buffer_size) {
- ASSERT_OR_EXECUTE(buffer_size > 0, { return 0; });
- const u32 l2 = Common::Log2Ceil64(static_cast<u64>(buffer_size));
- OGLBuffer& cp = copy_pbo_cache[l2];
- if (cp.handle == 0) {
- const std::size_t ceil_size = 1ULL << l2;
- cp.Create();
- cp.MakeStreamCopy(ceil_size);
+ if (runtime.device.HasDebuggingToolAttached()) {
+ const std::string name = VideoCommon::Name(key);
+ glObjectLabel(GL_FRAMEBUFFER, handle, static_cast<GLsizei>(name.size()), name.data());
}
- return cp.handle;
+ framebuffer.handle = handle;
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index de8f18489..a6172f009 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -4,154 +4,241 @@
#pragma once
-#include <array>
-#include <functional>
#include <memory>
-#include <unordered_map>
-#include <utility>
-#include <vector>
+#include <span>
#include <glad/glad.h>
-#include "common/common_types.h"
-#include "video_core/engines/shader_bytecode.h"
-#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/util_shaders.h"
#include "video_core/texture_cache/texture_cache.h"
namespace OpenGL {
-using VideoCommon::SurfaceParams;
-using VideoCommon::ViewParams;
-
-class CachedSurfaceView;
-class CachedSurface;
-class TextureCacheOpenGL;
+class Device;
+class ProgramManager;
class StateTracker;
-using Surface = std::shared_ptr<CachedSurface>;
-using View = std::shared_ptr<CachedSurfaceView>;
-using TextureCacheBase = VideoCommon::TextureCache<Surface, View>;
+class Framebuffer;
+class Image;
+class ImageView;
+class Sampler;
+
+using VideoCommon::ImageId;
+using VideoCommon::ImageViewId;
+using VideoCommon::ImageViewType;
+using VideoCommon::NUM_RT;
+using VideoCommon::Offset2D;
+using VideoCommon::RenderTargets;
+
+struct ImageBufferMap {
+ ~ImageBufferMap();
+
+ std::span<u8> mapped_span;
+ size_t offset = 0;
+ OGLSync* sync;
+ GLuint buffer;
+};
+
+struct FormatProperties {
+ GLenum compatibility_class;
+ bool compatibility_by_size;
+ bool is_compressed;
+};
-class CachedSurface final : public VideoCommon::SurfaceBase<View> {
- friend CachedSurfaceView;
+class TextureCacheRuntime {
+ friend Framebuffer;
+ friend Image;
+ friend ImageView;
+ friend Sampler;
public:
- explicit CachedSurface(GPUVAddr gpu_addr, const SurfaceParams& params, bool is_astc_supported);
- ~CachedSurface();
+ explicit TextureCacheRuntime(const Device& device, ProgramManager& program_manager,
+ StateTracker& state_tracker);
+ ~TextureCacheRuntime();
- void UploadTexture(const std::vector<u8>& staging_buffer) override;
- void DownloadTexture(std::vector<u8>& staging_buffer) override;
+ void Finish();
- GLenum GetTarget() const {
- return target;
- }
+ ImageBufferMap UploadStagingBuffer(size_t size);
- GLuint GetTexture() const {
- return texture.handle;
- }
+ ImageBufferMap DownloadStagingBuffer(size_t size);
+
+ void CopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
- bool IsCompressed() const {
- return is_compressed;
+ void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view) {
+ UNIMPLEMENTED();
}
-protected:
- void DecorateSurfaceName() override;
+ bool CanImageBeCopied(const Image& dst, const Image& src);
+
+ void EmulateCopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
+
+ void BlitFramebuffer(Framebuffer* dst, Framebuffer* src,
+ const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ Tegra::Engines::Fermi2D::Filter filter,
+ Tegra::Engines::Fermi2D::Operation operation);
+
+ void AccelerateImageUpload(Image& image, const ImageBufferMap& map,
+ std::span<const VideoCommon::SwizzleParameters> swizzles);
+
+ void InsertUploadMemoryBarrier();
- View CreateView(const ViewParams& view_key) override;
- View CreateViewInner(const ViewParams& view_key, bool is_proxy);
+ FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
+
+ bool HasBrokenTextureViewFormats() const noexcept {
+ return has_broken_texture_view_formats;
+ }
private:
- void UploadTextureMipmap(u32 level, const std::vector<u8>& staging_buffer);
+ struct StagingBuffers {
+ explicit StagingBuffers(GLenum storage_flags_, GLenum map_flags_);
+ ~StagingBuffers();
- GLenum internal_format{};
- GLenum format{};
- GLenum type{};
- bool is_compressed{};
- GLenum target{};
- u32 view_count{};
+ ImageBufferMap RequestMap(size_t requested_size, bool insert_fence);
- OGLTexture texture;
- OGLBuffer texture_buffer;
+ size_t RequestBuffer(size_t requested_size);
+
+ std::optional<size_t> FindBuffer(size_t requested_size);
+
+ std::vector<OGLSync> syncs;
+ std::vector<OGLBuffer> buffers;
+ std::vector<u8*> maps;
+ std::vector<size_t> sizes;
+ GLenum storage_flags;
+ GLenum map_flags;
+ };
+
+ const Device& device;
+ StateTracker& state_tracker;
+ UtilShaders util_shaders;
+
+ std::array<std::unordered_map<GLenum, FormatProperties>, 3> format_properties;
+ bool has_broken_texture_view_formats = false;
+
+ StagingBuffers upload_buffers{GL_MAP_WRITE_BIT, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT};
+ StagingBuffers download_buffers{GL_MAP_READ_BIT, GL_MAP_READ_BIT};
+
+ OGLTexture null_image_1d_array;
+ OGLTexture null_image_cube_array;
+ OGLTexture null_image_3d;
+ OGLTexture null_image_rect;
+ OGLTextureView null_image_view_1d;
+ OGLTextureView null_image_view_2d;
+ OGLTextureView null_image_view_2d_array;
+ OGLTextureView null_image_view_cube;
+
+ std::array<GLuint, VideoCommon::NUM_IMAGE_VIEW_TYPES> null_image_views;
};
-class CachedSurfaceView final : public VideoCommon::ViewBase {
+class Image : public VideoCommon::ImageBase {
+ friend ImageView;
+
public:
- explicit CachedSurfaceView(CachedSurface& surface, const ViewParams& params, bool is_proxy);
- ~CachedSurfaceView();
+ explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,
+ VAddr cpu_addr);
- /// @brief Attaches this texture view to the currently bound fb_target framebuffer
- /// @param attachment Attachment to bind textures to
- /// @param fb_target Framebuffer target to attach to (e.g. DRAW_FRAMEBUFFER)
- void Attach(GLenum attachment, GLenum fb_target) const;
+ void UploadMemory(const ImageBufferMap& map,
+ std::span<const VideoCommon::BufferImageCopy> copies);
- GLuint GetTexture(Tegra::Texture::SwizzleSource x_source,
- Tegra::Texture::SwizzleSource y_source,
- Tegra::Texture::SwizzleSource z_source,
- Tegra::Texture::SwizzleSource w_source);
+ void UploadMemory(const ImageBufferMap& map, std::span<const VideoCommon::BufferCopy> copies);
- void DecorateViewName(GPUVAddr gpu_addr, const std::string& prefix);
+ void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies);
- void MarkAsModified(u64 tick) {
- surface.MarkAsModified(true, tick);
+ GLuint StorageHandle() noexcept;
+
+ GLuint Handle() const noexcept {
+ return texture.handle;
}
- GLuint GetTexture() const {
- if (is_proxy) {
- return surface.GetTexture();
- }
- return main_view.handle;
+private:
+ void CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset);
+
+ void CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset);
+
+ OGLTexture texture;
+ OGLBuffer buffer;
+ OGLTextureView store_view;
+ GLenum gl_internal_format = GL_NONE;
+ GLenum gl_format = GL_NONE;
+ GLenum gl_type = GL_NONE;
+};
+
+class ImageView : public VideoCommon::ImageViewBase {
+ friend Image;
+
+public:
+ explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&);
+ explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams&);
+
+ [[nodiscard]] GLuint Handle(ImageViewType query_type) const noexcept {
+ return views[static_cast<size_t>(query_type)];
}
- GLenum GetFormat() const {
- return format;
+ [[nodiscard]] GLuint DefaultHandle() const noexcept {
+ return default_handle;
}
- const SurfaceParams& GetSurfaceParams() const {
- return surface.GetSurfaceParams();
+ [[nodiscard]] GLenum Format() const noexcept {
+ return internal_format;
}
private:
- OGLTextureView CreateTextureView() const;
+ void SetupView(const Device& device, Image& image, ImageViewType view_type, GLuint handle,
+ const VideoCommon::ImageViewInfo& info,
+ VideoCommon::SubresourceRange view_range);
+
+ std::array<GLuint, VideoCommon::NUM_IMAGE_VIEW_TYPES> views{};
+ std::vector<OGLTextureView> stored_views;
+ GLuint default_handle = 0;
+ GLenum internal_format = GL_NONE;
+};
- CachedSurface& surface;
- const GLenum format;
- const GLenum target;
- const bool is_proxy;
+class ImageAlloc : public VideoCommon::ImageAllocBase {};
- std::unordered_map<u32, OGLTextureView> view_cache;
- OGLTextureView main_view;
+class Sampler {
+public:
+ explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
+
+ GLuint Handle() const noexcept {
+ return sampler.handle;
+ }
- // Use an invalid default so it always fails the comparison test
- u32 current_swizzle = 0xffffffff;
- GLuint current_view = 0;
+private:
+ OGLSampler sampler;
};
-class TextureCacheOpenGL final : public TextureCacheBase {
+class Framebuffer {
public:
- explicit TextureCacheOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const Device& device, StateTracker& state_tracker);
- ~TextureCacheOpenGL();
-
-protected:
- Surface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) override;
-
- void ImageCopy(Surface& src_surface, Surface& dst_surface,
- const VideoCommon::CopyParams& copy_params) override;
+ explicit Framebuffer(TextureCacheRuntime&, std::span<ImageView*, NUM_RT> color_buffers,
+ ImageView* depth_buffer, const VideoCommon::RenderTargets& key);
- void ImageBlit(View& src_view, View& dst_view,
- const Tegra::Engines::Fermi2D::Config& copy_config) override;
+ [[nodiscard]] GLuint Handle() const noexcept {
+ return framebuffer.handle;
+ }
- void BufferCopy(Surface& src_surface, Surface& dst_surface) override;
+ [[nodiscard]] GLbitfield BufferBits() const noexcept {
+ return buffer_bits;
+ }
private:
- GLuint FetchPBO(std::size_t buffer_size);
-
- StateTracker& state_tracker;
+ OGLFramebuffer framebuffer;
+ GLbitfield buffer_bits = GL_NONE;
+};
- OGLFramebuffer src_framebuffer;
- OGLFramebuffer dst_framebuffer;
- std::unordered_map<u32, OGLBuffer> copy_pbo_cache;
+struct TextureCacheParams {
+ static constexpr bool ENABLE_VALIDATION = true;
+ static constexpr bool FRAMEBUFFER_BLITS = true;
+ static constexpr bool HAS_EMULATED_COPIES = true;
+
+ using Runtime = OpenGL::TextureCacheRuntime;
+ using Image = OpenGL::Image;
+ using ImageAlloc = OpenGL::ImageAlloc;
+ using ImageView = OpenGL::ImageView;
+ using Sampler = OpenGL::Sampler;
+ using Framebuffer = OpenGL::Framebuffer;
};
+using TextureCache = VideoCommon::TextureCache<TextureCacheParams>;
+
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index fe9bd4b5a..f7ad8f370 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -4,23 +4,10 @@
#pragma once
-#include <array>
#include <glad/glad.h>
-#include "common/common_types.h"
-#include "common/logging/log.h"
#include "video_core/engines/maxwell_3d.h"
-namespace OpenGL {
-
-using GLvec2 = std::array<GLfloat, 2>;
-using GLvec3 = std::array<GLfloat, 3>;
-using GLvec4 = std::array<GLfloat, 4>;
-
-using GLuvec2 = std::array<GLuint, 2>;
-using GLuvec3 = std::array<GLuint, 3>;
-using GLuvec4 = std::array<GLuint, 4>;
-
-namespace MaxwellToGL {
+namespace OpenGL::MaxwellToGL {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
@@ -47,6 +34,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
return GL_UNSIGNED_INT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return GL_UNSIGNED_INT_2_10_10_10_REV;
+ default:
+ break;
}
break;
case Maxwell::VertexAttribute::Type::SignedNorm:
@@ -70,6 +59,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
return GL_INT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return GL_INT_2_10_10_10_REV;
+ default:
+ break;
}
break;
case Maxwell::VertexAttribute::Type::Float:
@@ -84,6 +75,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
case Maxwell::VertexAttribute::Size::Size_32_32_32:
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return GL_FLOAT;
+ default:
+ break;
}
break;
}
@@ -101,7 +94,7 @@ inline GLenum IndexFormat(Maxwell::IndexFormat index_format) {
case Maxwell::IndexFormat::UnsignedInt:
return GL_UNSIGNED_INT;
}
- UNREACHABLE_MSG("Invalid index_format={}", static_cast<u32>(index_format));
+ UNREACHABLE_MSG("Invalid index_format={}", index_format);
return {};
}
@@ -138,7 +131,7 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
case Maxwell::PrimitiveTopology::Patches:
return GL_PATCHES;
}
- UNREACHABLE_MSG("Invalid topology={}", static_cast<int>(topology));
+ UNREACHABLE_MSG("Invalid topology={}", topology);
return GL_POINTS;
}
@@ -166,8 +159,8 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
}
break;
}
- UNREACHABLE_MSG("Invalid texture filter mode={} and mipmap filter mode={}",
- static_cast<u32>(filter_mode), static_cast<u32>(mipmap_filter_mode));
+ UNREACHABLE_MSG("Invalid texture filter mode={} and mipmap filter mode={}", filter_mode,
+ mipmap_filter_mode);
return GL_NEAREST;
}
@@ -198,7 +191,7 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
return GL_MIRROR_CLAMP_TO_EDGE;
}
}
- UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode));
+ UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", wrap_mode);
return GL_REPEAT;
}
@@ -221,7 +214,7 @@ inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) {
case Tegra::Texture::DepthCompareFunc::Always:
return GL_ALWAYS;
}
- UNIMPLEMENTED_MSG("Unimplemented texture depth compare function={}", static_cast<u32>(func));
+ UNIMPLEMENTED_MSG("Unimplemented texture depth compare function={}", func);
return GL_GREATER;
}
@@ -243,7 +236,7 @@ inline GLenum BlendEquation(Maxwell::Blend::Equation equation) {
case Maxwell::Blend::Equation::MaxGL:
return GL_MAX;
}
- UNIMPLEMENTED_MSG("Unimplemented blend equation={}", static_cast<u32>(equation));
+ UNIMPLEMENTED_MSG("Unimplemented blend equation={}", equation);
return GL_FUNC_ADD;
}
@@ -307,27 +300,7 @@ inline GLenum BlendFunc(Maxwell::Blend::Factor factor) {
case Maxwell::Blend::Factor::OneMinusConstantAlphaGL:
return GL_ONE_MINUS_CONSTANT_ALPHA;
}
- UNIMPLEMENTED_MSG("Unimplemented blend factor={}", static_cast<u32>(factor));
- return GL_ZERO;
-}
-
-inline GLenum SwizzleSource(Tegra::Texture::SwizzleSource source) {
- switch (source) {
- case Tegra::Texture::SwizzleSource::Zero:
- return GL_ZERO;
- case Tegra::Texture::SwizzleSource::R:
- return GL_RED;
- case Tegra::Texture::SwizzleSource::G:
- return GL_GREEN;
- case Tegra::Texture::SwizzleSource::B:
- return GL_BLUE;
- case Tegra::Texture::SwizzleSource::A:
- return GL_ALPHA;
- case Tegra::Texture::SwizzleSource::OneInt:
- case Tegra::Texture::SwizzleSource::OneFloat:
- return GL_ONE;
- }
- UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", static_cast<u32>(source));
+ UNIMPLEMENTED_MSG("Unimplemented blend factor={}", factor);
return GL_ZERO;
}
@@ -358,7 +331,7 @@ inline GLenum ComparisonOp(Maxwell::ComparisonOp comparison) {
case Maxwell::ComparisonOp::AlwaysOld:
return GL_ALWAYS;
}
- UNIMPLEMENTED_MSG("Unimplemented comparison op={}", static_cast<u32>(comparison));
+ UNIMPLEMENTED_MSG("Unimplemented comparison op={}", comparison);
return GL_ALWAYS;
}
@@ -389,7 +362,7 @@ inline GLenum StencilOp(Maxwell::StencilOp stencil) {
case Maxwell::StencilOp::DecrWrapOGL:
return GL_DECR_WRAP;
}
- UNIMPLEMENTED_MSG("Unimplemented stencil op={}", static_cast<u32>(stencil));
+ UNIMPLEMENTED_MSG("Unimplemented stencil op={}", stencil);
return GL_KEEP;
}
@@ -400,7 +373,7 @@ inline GLenum FrontFace(Maxwell::FrontFace front_face) {
case Maxwell::FrontFace::CounterClockWise:
return GL_CCW;
}
- UNIMPLEMENTED_MSG("Unimplemented front face cull={}", static_cast<u32>(front_face));
+ UNIMPLEMENTED_MSG("Unimplemented front face cull={}", front_face);
return GL_CCW;
}
@@ -413,7 +386,7 @@ inline GLenum CullFace(Maxwell::CullFace cull_face) {
case Maxwell::CullFace::FrontAndBack:
return GL_FRONT_AND_BACK;
}
- UNIMPLEMENTED_MSG("Unimplemented cull face={}", static_cast<u32>(cull_face));
+ UNIMPLEMENTED_MSG("Unimplemented cull face={}", cull_face);
return GL_BACK;
}
@@ -452,7 +425,7 @@ inline GLenum LogicOp(Maxwell::LogicOperation operation) {
case Maxwell::LogicOperation::Set:
return GL_SET;
}
- UNIMPLEMENTED_MSG("Unimplemented logic operation={}", static_cast<u32>(operation));
+ UNIMPLEMENTED_MSG("Unimplemented logic operation={}", operation);
return GL_COPY;
}
@@ -465,14 +438,26 @@ inline GLenum PolygonMode(Maxwell::PolygonMode polygon_mode) {
case Maxwell::PolygonMode::Fill:
return GL_FILL;
}
- UNREACHABLE_MSG("Invalid polygon mode={}", static_cast<int>(polygon_mode));
+ UNREACHABLE_MSG("Invalid polygon mode={}", polygon_mode);
return GL_FILL;
}
+inline GLenum ReductionFilter(Tegra::Texture::SamplerReduction filter) {
+ switch (filter) {
+ case Tegra::Texture::SamplerReduction::WeightedAverage:
+ return GL_WEIGHTED_AVERAGE_ARB;
+ case Tegra::Texture::SamplerReduction::Min:
+ return GL_MIN;
+ case Tegra::Texture::SamplerReduction::Max:
+ return GL_MAX;
+ }
+ UNREACHABLE_MSG("Invalid reduction filter={}", static_cast<int>(filter));
+ return GL_WEIGHTED_AVERAGE_ARB;
+}
+
inline GLenum ViewportSwizzle(Maxwell::ViewportSwizzle swizzle) {
// Enumeration order matches register order. We can convert it arithmetically.
return GL_VIEWPORT_SWIZZLE_POSITIVE_X_NV + static_cast<GLenum>(swizzle);
}
-} // namespace MaxwellToGL
-} // namespace OpenGL
+} // namespace OpenGL::MaxwellToGL
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index b759c2dba..9d2acd4d9 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -23,29 +23,13 @@
#include "core/telemetry_session.h"
#include "video_core/host_shaders/opengl_present_frag.h"
#include "video_core/host_shaders/opengl_present_vert.h"
-#include "video_core/morton.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
+#include "video_core/textures/decoders.h"
namespace OpenGL {
-
namespace {
-
-constexpr std::size_t SWAP_CHAIN_SIZE = 3;
-
-struct Frame {
- u32 width{}; /// Width of the frame (to detect resize)
- u32 height{}; /// Height of the frame
- bool color_reloaded{}; /// Texture attachment was recreated (ie: resized)
- OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
- OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
- OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
- GLsync render_fence{}; /// Fence created on the render thread
- GLsync present_fence{}; /// Fence created on the presentation thread
- bool is_srgb{}; /// Framebuffer is sRGB or RGB
-};
-
constexpr GLint PositionLocation = 0;
constexpr GLint TexCoordLocation = 1;
constexpr GLint ModelViewMatrixLocation = 0;
@@ -58,24 +42,6 @@ struct ScreenRectVertex {
std::array<GLfloat, 2> tex_coord;
};
-/// Returns true if any debug tool is attached
-bool HasDebugTool() {
- const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
- if (nsight) {
- return true;
- }
-
- GLint num_extensions;
- glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
- for (GLuint index = 0; index < static_cast<GLuint>(num_extensions); ++index) {
- const auto name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, index));
- if (!std::strcmp(name, "GL_EXT_debug_tool")) {
- return true;
- }
- }
- return false;
-}
-
/**
* Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left
* corner and (width, height) on the lower-bottom.
@@ -156,217 +122,62 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit
break;
}
}
-
} // Anonymous namespace
-/**
- * For smooth Vsync rendering, we want to always present the latest frame that the core generates,
- * but also make sure that rendering happens at the pace that the frontend dictates. This is a
- * helper class that the renderer uses to sync frames between the render thread and the presentation
- * thread
- */
-class FrameMailbox {
-public:
- std::mutex swap_chain_lock;
- std::condition_variable present_cv;
- std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{};
- std::queue<Frame*> free_queue;
- std::deque<Frame*> present_queue;
- Frame* previous_frame{};
-
- FrameMailbox() {
- for (auto& frame : swap_chain) {
- free_queue.push(&frame);
- }
- }
-
- ~FrameMailbox() {
- // lock the mutex and clear out the present and free_queues and notify any people who are
- // blocked to prevent deadlock on shutdown
- std::scoped_lock lock{swap_chain_lock};
- std::queue<Frame*>().swap(free_queue);
- present_queue.clear();
- present_cv.notify_all();
- }
-
- void ReloadPresentFrame(Frame* frame, u32 height, u32 width) {
- frame->present.Release();
- frame->present.Create();
- GLint previous_draw_fbo{};
- glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
- glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
- frame->color.handle);
- if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
- LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
- }
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
- frame->color_reloaded = false;
- }
-
- void ReloadRenderFrame(Frame* frame, u32 width, u32 height) {
- // Recreate the color texture attachment
- frame->color.Release();
- frame->color.Create();
- const GLenum internal_format = frame->is_srgb ? GL_SRGB8 : GL_RGB8;
- glNamedRenderbufferStorage(frame->color.handle, internal_format, width, height);
-
- // Recreate the FBO for the render target
- frame->render.Release();
- frame->render.Create();
- glBindFramebuffer(GL_FRAMEBUFFER, frame->render.handle);
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
- frame->color.handle);
- if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
- LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
- }
-
- frame->width = width;
- frame->height = height;
- frame->color_reloaded = true;
- }
-
- Frame* GetRenderFrame() {
- std::unique_lock lock{swap_chain_lock};
-
- // If theres no free frames, we will reuse the oldest render frame
- if (free_queue.empty()) {
- auto frame = present_queue.back();
- present_queue.pop_back();
- return frame;
- }
-
- Frame* frame = free_queue.front();
- free_queue.pop();
- return frame;
- }
-
- void ReleaseRenderFrame(Frame* frame) {
- std::unique_lock lock{swap_chain_lock};
- present_queue.push_front(frame);
- present_cv.notify_one();
- }
-
- Frame* TryGetPresentFrame(int timeout_ms) {
- std::unique_lock lock{swap_chain_lock};
- // wait for new entries in the present_queue
- present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
- [&] { return !present_queue.empty(); });
- if (present_queue.empty()) {
- // timed out waiting for a frame to draw so return the previous frame
- return previous_frame;
- }
-
- // free the previous frame and add it back to the free queue
- if (previous_frame) {
- free_queue.push(previous_frame);
- }
-
- // the newest entries are pushed to the front of the queue
- Frame* frame = present_queue.front();
- present_queue.pop_front();
- // remove all old entries from the present queue and move them back to the free_queue
- for (auto f : present_queue) {
- free_queue.push(f);
- }
- present_queue.clear();
- previous_frame = frame;
- return frame;
- }
-};
-
-RendererOpenGL::RendererOpenGL(Core::System& system_, Core::Frontend::EmuWindow& emu_window_,
- Tegra::GPU& gpu_,
+RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
+ Core::Frontend::EmuWindow& emu_window_,
+ Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
std::unique_ptr<Core::Frontend::GraphicsContext> context_)
- : RendererBase{emu_window_, std::move(context_)}, system{system_},
- emu_window{emu_window_}, gpu{gpu_}, program_manager{device}, has_debug_tool{HasDebugTool()} {}
+ : RendererBase{emu_window_, std::move(context_)}, telemetry_session{telemetry_session_},
+ emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, state_tracker{gpu},
+ program_manager{device},
+ rasterizer(emu_window, gpu, cpu_memory, device, screen_info, program_manager, state_tracker) {
+ if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
+ glEnable(GL_DEBUG_OUTPUT);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+ glDebugMessageCallback(DebugHandler, nullptr);
+ }
+ AddTelemetryFields();
+ InitOpenGLObjects();
+}
RendererOpenGL::~RendererOpenGL() = default;
-MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
-MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
-
void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
if (!framebuffer) {
return;
}
-
PrepareRendertarget(framebuffer);
RenderScreenshot();
- Frame* frame;
- {
- MICROPROFILE_SCOPE(OpenGL_WaitPresent);
-
- frame = frame_mailbox->GetRenderFrame();
+ state_tracker.BindFramebuffer(0);
+ DrawScreen(emu_window.GetFramebufferLayout());
- // Clean up sync objects before drawing
+ ++m_current_frame;
- // INTEL driver workaround. We can't delete the previous render sync object until we are
- // sure that the presentation is done
- if (frame->present_fence) {
- glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
- }
-
- // delete the draw fence if the frame wasn't presented
- if (frame->render_fence) {
- glDeleteSync(frame->render_fence);
- frame->render_fence = 0;
- }
+ rasterizer.TickFrame();
- // wait for the presentation to be done
- if (frame->present_fence) {
- glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
- glDeleteSync(frame->present_fence);
- frame->present_fence = 0;
- }
- }
-
- {
- MICROPROFILE_SCOPE(OpenGL_RenderFrame);
- const auto& layout = render_window.GetFramebufferLayout();
-
- // Recreate the frame if the size of the window has changed
- if (layout.width != frame->width || layout.height != frame->height ||
- screen_info.display_srgb != frame->is_srgb) {
- LOG_DEBUG(Render_OpenGL, "Reloading render frame");
- frame->is_srgb = screen_info.display_srgb;
- frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
- }
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame->render.handle);
- DrawScreen(layout);
- // Create a fence for the frontend to wait on and swap this frame to OffTex
- frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
- glFlush();
- frame_mailbox->ReleaseRenderFrame(frame);
- m_current_frame++;
- rasterizer->TickFrame();
- }
-
- render_window.PollEvents();
- if (has_debug_tool) {
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
- Present(0);
- context->SwapBuffers();
- }
+ context->SwapBuffers();
+ render_window.OnFrameDisplayed();
}
void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) {
- if (framebuffer) {
- // If framebuffer is provided, reload it from memory to a texture
- if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
- screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
- screen_info.texture.pixel_format != framebuffer->pixel_format ||
- gl_framebuffer_data.empty()) {
- // Reallocate texture if the framebuffer size has changed.
- // This is expected to not happen very often and hence should not be a
- // performance problem.
- ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
- }
-
- // Load the framebuffer from memory, draw it to the screen, and swap buffers
- LoadFBToScreenInfo(*framebuffer);
+ if (!framebuffer) {
+ return;
+ }
+ // If framebuffer is provided, reload it from memory to a texture
+ if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
+ screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
+ screen_info.texture.pixel_format != framebuffer->pixel_format ||
+ gl_framebuffer_data.empty()) {
+ // Reallocate texture if the framebuffer size has changed.
+ // This is expected to not happen very often and hence should not be a
+ // performance problem.
+ ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
}
+
+ // Load the framebuffer from memory, draw it to the screen, and swap buffers
+ LoadFBToScreenInfo(*framebuffer);
}
void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
@@ -375,26 +186,27 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
framebuffer_crop_rect = framebuffer.crop_rect;
const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
- if (rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) {
+ if (rasterizer.AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) {
return;
}
// Reset the screen info's display texture to its own permanent texture
screen_info.display_texture = screen_info.texture.resource.handle;
- const auto pixel_format{
- VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
- const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)};
- const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
- u8* const host_ptr{system.Memory().GetPointer(framebuffer_addr)};
- rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes);
-
// TODO(Rodrigo): Read this from HLE
constexpr u32 block_height_log2 = 4;
- VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format,
- framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1,
- gl_framebuffer_data.data(), host_ptr);
-
+ const auto pixel_format{
+ VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
+ const u32 bytes_per_pixel{VideoCore::Surface::BytesPerBlock(pixel_format)};
+ const u64 size_in_bytes{Tegra::Texture::CalculateSize(
+ true, bytes_per_pixel, framebuffer.stride, framebuffer.height, 1, block_height_log2, 0)};
+ const u8* const host_ptr{cpu_memory.GetPointer(framebuffer_addr)};
+ const std::span<const u8> input_data(host_ptr, size_in_bytes);
+ Tegra::Texture::UnswizzleTexture(gl_framebuffer_data, input_data, bytes_per_pixel,
+ framebuffer.width, framebuffer.height, 1, block_height_log2,
+ 0);
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
// Update existing texture
@@ -416,8 +228,6 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
}
void RendererOpenGL::InitOpenGLObjects() {
- frame_mailbox = std::make_unique<FrameMailbox>();
-
glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
Settings::values.bg_blue.GetValue(), 0.0f);
@@ -435,6 +245,10 @@ void RendererOpenGL::InitOpenGLObjects() {
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vertex_program.handle);
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fragment_program.handle);
+ // Generate presentation sampler
+ present_sampler.Create();
+ glSamplerParameteri(present_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
// Generate VBO handle for drawing
vertex_buffer.Create();
@@ -452,9 +266,15 @@ void RendererOpenGL::InitOpenGLObjects() {
// Clear screen to black
LoadColorToActiveGLTexture(0, 0, 0, 0, screen_info.texture);
+ // Enable seamless cubemaps when per texture parameters are not available
+ if (!GLAD_GL_ARB_seamless_cubemap_per_texture && !GLAD_GL_AMD_seamless_cubemap_per_texture) {
+ glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+ }
+
// Enable unified vertex attributes and query vertex buffer address when the driver supports it
if (device.HasVertexBufferUnifiedMemory()) {
glEnableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
+ glEnableClientState(GL_ELEMENT_ARRAY_UNIFIED_NV);
glMakeNamedBufferResidentNV(vertex_buffer.handle, GL_READ_ONLY);
glGetNamedBufferParameterui64vNV(vertex_buffer.handle, GL_BUFFER_GPU_ADDRESS_NV,
@@ -471,19 +291,10 @@ void RendererOpenGL::AddTelemetryFields() {
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
- auto& telemetry_session = system.TelemetrySession();
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
- telemetry_session.AddField(user_system, "GPU_Vendor", gpu_vendor);
- telemetry_session.AddField(user_system, "GPU_Model", gpu_model);
- telemetry_session.AddField(user_system, "GPU_OpenGL_Version", gl_version);
-}
-
-void RendererOpenGL::CreateRasterizer() {
- if (rasterizer) {
- return;
- }
- rasterizer = std::make_unique<RasterizerOpenGL>(system, emu_window, device, screen_info,
- program_manager, state_tracker);
+ telemetry_session.AddField(user_system, "GPU_Vendor", std::string(gpu_vendor));
+ telemetry_session.AddField(user_system, "GPU_Model", std::string(gpu_model));
+ telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
}
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
@@ -494,7 +305,7 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
const auto pixel_format{
VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
- const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)};
+ const u32 bytes_per_pixel{VideoCore::Surface::BytesPerBlock(pixel_format)};
gl_framebuffer_data.resize(texture.width * texture.height * bytes_per_pixel);
GLint internal_format;
@@ -513,8 +324,8 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
internal_format = GL_RGBA8;
texture.gl_format = GL_RGBA;
texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
- UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
- static_cast<u32>(framebuffer.pixel_format));
+ // UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
+ // static_cast<u32>(framebuffer.pixel_format));
}
texture.resource.Release();
@@ -546,7 +357,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
} else {
// Other transformations are unsupported
LOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags={}",
- static_cast<u32>(framebuffer_transform_flags));
+ framebuffer_transform_flags);
UNIMPLEMENTED();
}
}
@@ -580,7 +391,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
state_tracker.NotifyPolygonModes();
state_tracker.NotifyViewport0();
state_tracker.NotifyScissor0();
- state_tracker.NotifyColorMask0();
+ state_tracker.NotifyColorMask(0);
state_tracker.NotifyBlend0();
state_tracker.NotifyFramebuffer();
state_tracker.NotifyFrontFace();
@@ -596,6 +407,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
program_manager.BindHostPipeline(pipeline.handle);
+ state_tracker.ClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
glEnable(GL_CULL_FACE);
if (screen_info.display_srgb) {
glEnable(GL_FRAMEBUFFER_SRGB);
@@ -614,7 +426,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
glCullFace(GL_BACK);
glFrontFace(GL_CW);
glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
- glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(layout.width),
static_cast<GLfloat>(layout.height));
glDepthRangeIndexed(0, 0.0, 0.0);
@@ -638,7 +449,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
}
glBindTextureUnit(0, screen_info.display_texture);
- glBindSampler(0, 0);
+ glBindSampler(0, present_sampler.handle);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
@@ -646,51 +457,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
program_manager.RestoreGuestPipeline();
}
-bool RendererOpenGL::TryPresent(int timeout_ms) {
- if (has_debug_tool) {
- LOG_DEBUG(Render_OpenGL,
- "Skipping presentation because we are presenting on the main context");
- return false;
- }
- return Present(timeout_ms);
-}
-
-bool RendererOpenGL::Present(int timeout_ms) {
- const auto& layout = render_window.GetFramebufferLayout();
- auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms);
- if (!frame) {
- LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
- return false;
- }
-
- // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
- // readback since we won't be doing any blending
- glClear(GL_COLOR_BUFFER_BIT);
-
- // Recreate the presentation FBO if the color attachment was changed
- if (frame->color_reloaded) {
- LOG_DEBUG(Render_OpenGL, "Reloading present frame");
- frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
- }
- glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
- // INTEL workaround.
- // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
- // it on the emulation thread without too much penalty
- // glDeleteSync(frame.render_sync);
- // frame.render_sync = 0;
-
- glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
- glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height,
- GL_COLOR_BUFFER_BIT, GL_LINEAR);
-
- // Insert fence for the main thread to block on
- frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
- glFlush();
-
- glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
- return true;
-}
-
void RendererOpenGL::RenderScreenshot() {
if (!renderer_settings.screenshot_requested) {
return;
@@ -705,7 +471,7 @@ void RendererOpenGL::RenderScreenshot() {
screenshot_framebuffer.Create();
glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle);
- Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
+ const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
@@ -716,6 +482,8 @@ void RendererOpenGL::RenderScreenshot() {
DrawScreen(layout);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
renderer_settings.screenshot_bits);
@@ -729,25 +497,4 @@ void RendererOpenGL::RenderScreenshot() {
renderer_settings.screenshot_requested = false;
}
-bool RendererOpenGL::Init() {
- if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
- glEnable(GL_DEBUG_OUTPUT);
- glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
- glDebugMessageCallback(DebugHandler, nullptr);
- }
-
- AddTelemetryFields();
-
- if (!GLAD_GL_VERSION_4_3) {
- return false;
- }
-
- InitOpenGLObjects();
- CreateRasterizer();
-
- return true;
-}
-
-void RendererOpenGL::ShutDown() {}
-
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 52ea76b7d..cc19a110f 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -10,22 +10,32 @@
#include "common/math_util.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/gl_device.h"
+#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
namespace Core {
class System;
-}
+class TelemetrySession;
+} // namespace Core
namespace Core::Frontend {
class EmuWindow;
}
+namespace Core::Memory {
+class Memory;
+}
+
namespace Layout {
struct FramebufferLayout;
}
+namespace Tegra {
+class GPU;
+}
+
namespace OpenGL {
/// Structure used for storing information about the textures for the Switch screen
@@ -46,25 +56,19 @@ struct ScreenInfo {
TextureInfo texture;
};
-struct PresentationTexture {
- u32 width = 0;
- u32 height = 0;
- OGLTexture texture;
-};
-
-class FrameMailbox;
-
class RendererOpenGL final : public VideoCore::RendererBase {
public:
- explicit RendererOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
- Tegra::GPU& gpu,
- std::unique_ptr<Core::Frontend::GraphicsContext> context);
+ explicit RendererOpenGL(Core::TelemetrySession& telemetry_session_,
+ Core::Frontend::EmuWindow& emu_window_,
+ Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context_);
~RendererOpenGL() override;
- bool Init() override;
- void ShutDown() override;
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
- bool TryPresent(int timeout_ms) override;
+
+ VideoCore::RasterizerInterface* ReadRasterizer() override {
+ return &rasterizer;
+ }
private:
/// Initializes the OpenGL state and creates persistent objects.
@@ -72,8 +76,6 @@ private:
void AddTelemetryFields();
- void CreateRasterizer();
-
void ConfigureFramebufferTexture(TextureInfo& texture,
const Tegra::FramebufferConfig& framebuffer);
@@ -92,16 +94,18 @@ private:
void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer);
- bool Present(int timeout_ms);
-
- Core::System& system;
+ Core::TelemetrySession& telemetry_session;
Core::Frontend::EmuWindow& emu_window;
+ Core::Memory::Memory& cpu_memory;
Tegra::GPU& gpu;
- const Device device;
- StateTracker state_tracker{system};
+ Device device;
+ StateTracker state_tracker;
+ ProgramManager program_manager;
+ RasterizerOpenGL rasterizer;
// OpenGL object IDs
+ OGLSampler present_sampler;
OGLBuffer vertex_buffer;
OGLProgram vertex_program;
OGLProgram fragment_program;
@@ -114,20 +118,12 @@ private:
/// Display information for Switch screen
ScreenInfo screen_info;
- /// Global dummy shader pipeline
- ProgramManager program_manager;
-
/// OpenGL framebuffer data
std::vector<u8> gl_framebuffer_data;
/// Used for transforming the framebuffer orientation
Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{};
Common::Rectangle<int> framebuffer_crop_rect;
-
- /// Frame presentation mailbox
- std::unique_ptr<FrameMailbox> frame_mailbox;
-
- bool has_debug_tool = false;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp
new file mode 100644
index 000000000..31ec68505
--- /dev/null
+++ b/src/video_core/renderer_opengl/util_shaders.cpp
@@ -0,0 +1,225 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <bit>
+#include <span>
+#include <string_view>
+
+#include <glad/glad.h>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/div_ceil.h"
+#include "video_core/host_shaders/block_linear_unswizzle_2d_comp.h"
+#include "video_core/host_shaders/block_linear_unswizzle_3d_comp.h"
+#include "video_core/host_shaders/opengl_copy_bc4_comp.h"
+#include "video_core/host_shaders/pitch_unswizzle_comp.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/renderer_opengl/gl_shader_manager.h"
+#include "video_core/renderer_opengl/gl_texture_cache.h"
+#include "video_core/renderer_opengl/util_shaders.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/accelerated_swizzle.h"
+#include "video_core/texture_cache/types.h"
+#include "video_core/texture_cache/util.h"
+#include "video_core/textures/decoders.h"
+
+namespace OpenGL {
+
+using namespace HostShaders;
+
+using VideoCommon::Extent3D;
+using VideoCommon::ImageCopy;
+using VideoCommon::ImageType;
+using VideoCommon::SwizzleParameters;
+using VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams;
+using VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams;
+using VideoCore::Surface::BytesPerBlock;
+
+namespace {
+
+OGLProgram MakeProgram(std::string_view source) {
+ OGLShader shader;
+ shader.Create(source, GL_COMPUTE_SHADER);
+
+ OGLProgram program;
+ program.Create(true, false, shader.handle);
+ return program;
+}
+
+} // Anonymous namespace
+
+UtilShaders::UtilShaders(ProgramManager& program_manager_)
+ : program_manager{program_manager_},
+ block_linear_unswizzle_2d_program(MakeProgram(BLOCK_LINEAR_UNSWIZZLE_2D_COMP)),
+ block_linear_unswizzle_3d_program(MakeProgram(BLOCK_LINEAR_UNSWIZZLE_3D_COMP)),
+ pitch_unswizzle_program(MakeProgram(PITCH_UNSWIZZLE_COMP)),
+ copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)) {
+ const auto swizzle_table = Tegra::Texture::MakeSwizzleTable();
+ swizzle_table_buffer.Create();
+ glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0);
+}
+
+UtilShaders::~UtilShaders() = default;
+
+void UtilShaders::BlockLinearUpload2D(Image& image, const ImageBufferMap& map,
+ std::span<const SwizzleParameters> swizzles) {
+ static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1};
+ static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0;
+ static constexpr GLuint BINDING_INPUT_BUFFER = 1;
+ static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
+
+ program_manager.BindHostCompute(block_linear_unswizzle_2d_program.handle);
+ glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle);
+
+ const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format));
+ for (const SwizzleParameters& swizzle : swizzles) {
+ const Extent3D num_tiles = swizzle.num_tiles;
+ const size_t input_offset = swizzle.buffer_offset + map.offset;
+
+ const u32 num_dispatches_x = Common::DivCeil(num_tiles.width, WORKGROUP_SIZE.width);
+ const u32 num_dispatches_y = Common::DivCeil(num_tiles.height, WORKGROUP_SIZE.height);
+
+ const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info);
+ glUniform3uiv(0, 1, params.origin.data());
+ glUniform3iv(1, 1, params.destination.data());
+ glUniform1ui(2, params.bytes_per_block_log2);
+ glUniform1ui(3, params.layer_stride);
+ glUniform1ui(4, params.block_size);
+ glUniform1ui(5, params.x_shift);
+ glUniform1ui(6, params.block_height);
+ glUniform1ui(7, params.block_height_mask);
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset,
+ image.guest_size_bytes - swizzle.buffer_offset);
+ glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0,
+ GL_WRITE_ONLY, store_format);
+ glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers);
+ }
+ program_manager.RestoreGuestCompute();
+}
+
+void UtilShaders::BlockLinearUpload3D(Image& image, const ImageBufferMap& map,
+ std::span<const SwizzleParameters> swizzles) {
+ static constexpr Extent3D WORKGROUP_SIZE{16, 8, 8};
+
+ static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0;
+ static constexpr GLuint BINDING_INPUT_BUFFER = 1;
+ static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
+
+ glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);
+ program_manager.BindHostCompute(block_linear_unswizzle_3d_program.handle);
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle);
+
+ const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format));
+ for (const SwizzleParameters& swizzle : swizzles) {
+ const Extent3D num_tiles = swizzle.num_tiles;
+ const size_t input_offset = swizzle.buffer_offset + map.offset;
+
+ const u32 num_dispatches_x = Common::DivCeil(num_tiles.width, WORKGROUP_SIZE.width);
+ const u32 num_dispatches_y = Common::DivCeil(num_tiles.height, WORKGROUP_SIZE.height);
+ const u32 num_dispatches_z = Common::DivCeil(num_tiles.depth, WORKGROUP_SIZE.depth);
+
+ const auto params = MakeBlockLinearSwizzle3DParams(swizzle, image.info);
+ glUniform3uiv(0, 1, params.origin.data());
+ glUniform3iv(1, 1, params.destination.data());
+ glUniform1ui(2, params.bytes_per_block_log2);
+ glUniform1ui(3, params.slice_size);
+ glUniform1ui(4, params.block_size);
+ glUniform1ui(5, params.x_shift);
+ glUniform1ui(6, params.block_height);
+ glUniform1ui(7, params.block_height_mask);
+ glUniform1ui(8, params.block_depth);
+ glUniform1ui(9, params.block_depth_mask);
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset,
+ image.guest_size_bytes - swizzle.buffer_offset);
+ glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0,
+ GL_WRITE_ONLY, store_format);
+ glDispatchCompute(num_dispatches_x, num_dispatches_y, num_dispatches_z);
+ }
+ program_manager.RestoreGuestCompute();
+}
+
+void UtilShaders::PitchUpload(Image& image, const ImageBufferMap& map,
+ std::span<const SwizzleParameters> swizzles) {
+ static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1};
+ static constexpr GLuint BINDING_INPUT_BUFFER = 0;
+ static constexpr GLuint BINDING_OUTPUT_IMAGE = 0;
+ static constexpr GLuint LOC_ORIGIN = 0;
+ static constexpr GLuint LOC_DESTINATION = 1;
+ static constexpr GLuint LOC_BYTES_PER_BLOCK = 2;
+ static constexpr GLuint LOC_PITCH = 3;
+
+ const u32 bytes_per_block = BytesPerBlock(image.info.format);
+ const GLenum format = StoreFormat(bytes_per_block);
+ const u32 pitch = image.info.pitch;
+
+ UNIMPLEMENTED_IF_MSG(!std::has_single_bit(bytes_per_block),
+ "Non-power of two images are not implemented");
+
+ program_manager.BindHostCompute(pitch_unswizzle_program.handle);
+ glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes);
+ glUniform2ui(LOC_ORIGIN, 0, 0);
+ glUniform2i(LOC_DESTINATION, 0, 0);
+ glUniform1ui(LOC_BYTES_PER_BLOCK, bytes_per_block);
+ glUniform1ui(LOC_PITCH, pitch);
+ glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), 0, GL_FALSE, 0, GL_WRITE_ONLY,
+ format);
+ for (const SwizzleParameters& swizzle : swizzles) {
+ const Extent3D num_tiles = swizzle.num_tiles;
+ const size_t input_offset = swizzle.buffer_offset + map.offset;
+
+ const u32 num_dispatches_x = Common::DivCeil(num_tiles.width, WORKGROUP_SIZE.width);
+ const u32 num_dispatches_y = Common::DivCeil(num_tiles.height, WORKGROUP_SIZE.height);
+
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset,
+ image.guest_size_bytes - swizzle.buffer_offset);
+ glDispatchCompute(num_dispatches_x, num_dispatches_y, 1);
+ }
+ program_manager.RestoreGuestCompute();
+}
+
+void UtilShaders::CopyBC4(Image& dst_image, Image& src_image, std::span<const ImageCopy> copies) {
+ static constexpr GLuint BINDING_INPUT_IMAGE = 0;
+ static constexpr GLuint BINDING_OUTPUT_IMAGE = 1;
+ static constexpr GLuint LOC_SRC_OFFSET = 0;
+ static constexpr GLuint LOC_DST_OFFSET = 1;
+
+ program_manager.BindHostCompute(copy_bc4_program.handle);
+
+ for (const ImageCopy& copy : copies) {
+ ASSERT(copy.src_subresource.base_layer == 0);
+ ASSERT(copy.src_subresource.num_layers == 1);
+ ASSERT(copy.dst_subresource.base_layer == 0);
+ ASSERT(copy.dst_subresource.num_layers == 1);
+
+ glUniform3ui(LOC_SRC_OFFSET, copy.src_offset.x, copy.src_offset.y, copy.src_offset.z);
+ glUniform3ui(LOC_DST_OFFSET, copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z);
+ glBindImageTexture(BINDING_INPUT_IMAGE, src_image.StorageHandle(),
+ copy.src_subresource.base_level, GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI);
+ glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.StorageHandle(),
+ copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI);
+ glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth);
+ }
+ program_manager.RestoreGuestCompute();
+}
+
+GLenum StoreFormat(u32 bytes_per_block) {
+ switch (bytes_per_block) {
+ case 1:
+ return GL_R8UI;
+ case 2:
+ return GL_R16UI;
+ case 4:
+ return GL_R32UI;
+ case 8:
+ return GL_RG32UI;
+ case 16:
+ return GL_RGBA32UI;
+ }
+ UNREACHABLE();
+ return GL_R8UI;
+}
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/util_shaders.h b/src/video_core/renderer_opengl/util_shaders.h
new file mode 100644
index 000000000..7b1d16b09
--- /dev/null
+++ b/src/video_core/renderer_opengl/util_shaders.h
@@ -0,0 +1,52 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <span>
+
+#include <glad/glad.h>
+
+#include "common/common_types.h"
+#include "video_core/renderer_opengl/gl_resource_manager.h"
+#include "video_core/texture_cache/types.h"
+
+namespace OpenGL {
+
+class Image;
+class ProgramManager;
+
+struct ImageBufferMap;
+
+class UtilShaders {
+public:
+ explicit UtilShaders(ProgramManager& program_manager);
+ ~UtilShaders();
+
+ void BlockLinearUpload2D(Image& image, const ImageBufferMap& map,
+ std::span<const VideoCommon::SwizzleParameters> swizzles);
+
+ void BlockLinearUpload3D(Image& image, const ImageBufferMap& map,
+ std::span<const VideoCommon::SwizzleParameters> swizzles);
+
+ void PitchUpload(Image& image, const ImageBufferMap& map,
+ std::span<const VideoCommon::SwizzleParameters> swizzles);
+
+ void CopyBC4(Image& dst_image, Image& src_image,
+ std::span<const VideoCommon::ImageCopy> copies);
+
+private:
+ ProgramManager& program_manager;
+
+ OGLBuffer swizzle_table_buffer;
+
+ OGLProgram block_linear_unswizzle_2d_program;
+ OGLProgram block_linear_unswizzle_3d_program;
+ OGLProgram pitch_unswizzle_program;
+ OGLProgram copy_bc4_program;
+};
+
+GLenum StoreFormat(u32 bytes_per_block);
+
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp
deleted file mode 100644
index 6d7bb16b2..000000000
--- a/src/video_core/renderer_opengl/utils.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <string>
-#include <vector>
-
-#include <fmt/format.h>
-#include <glad/glad.h>
-
-#include "common/common_types.h"
-#include "video_core/renderer_opengl/gl_state_tracker.h"
-#include "video_core/renderer_opengl/utils.h"
-
-namespace OpenGL {
-
-void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info) {
- if (!GLAD_GL_KHR_debug) {
- // We don't need to throw an error as this is just for debugging
- return;
- }
-
- std::string object_label;
- if (extra_info.empty()) {
- switch (identifier) {
- case GL_TEXTURE:
- object_label = fmt::format("Texture@0x{:016X}", addr);
- break;
- case GL_PROGRAM:
- object_label = fmt::format("Shader@0x{:016X}", addr);
- break;
- default:
- object_label = fmt::format("Object(0x{:X})@0x{:016X}", identifier, addr);
- break;
- }
- } else {
- object_label = fmt::format("{}@0x{:016X}", extra_info, addr);
- }
- glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
-}
-
-} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h
deleted file mode 100644
index 9c09ee12c..000000000
--- a/src/video_core/renderer_opengl/utils.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string_view>
-#include <vector>
-#include <glad/glad.h>
-#include "common/common_types.h"
-
-namespace OpenGL {
-
-void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info = {});
-
-} // namespace OpenGL
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
new file mode 100644
index 000000000..1f6a169ae
--- /dev/null
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -0,0 +1,624 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
+#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
+#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
+#include "video_core/host_shaders/vulkan_blit_color_float_frag_spv.h"
+#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
+#include "video_core/renderer_vulkan/blit_image.h"
+#include "video_core/renderer_vulkan/maxwell_to_vk.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/renderer_vulkan/vk_shader_util.h"
+#include "video_core/renderer_vulkan/vk_state_tracker.h"
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+#include "video_core/renderer_vulkan/vk_update_descriptor.h"
+#include "video_core/surface.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+using VideoCommon::ImageViewType;
+
+namespace {
+struct PushConstants {
+ std::array<float, 2> tex_scale;
+ std::array<float, 2> tex_offset;
+};
+
+template <u32 binding>
+inline constexpr VkDescriptorSetLayoutBinding TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING{
+ .binding = binding,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .descriptorCount = 1,
+ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
+ .pImmutableSamplers = nullptr,
+};
+constexpr std::array TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS{
+ TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<0>,
+ TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<1>,
+};
+constexpr VkDescriptorSetLayoutCreateInfo ONE_TEXTURE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .bindingCount = 1,
+ .pBindings = &TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<0>,
+};
+constexpr VkDescriptorSetLayoutCreateInfo TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .bindingCount = static_cast<u32>(TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()),
+ .pBindings = TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(),
+};
+constexpr VkPushConstantRange PUSH_CONSTANT_RANGE{
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .offset = 0,
+ .size = sizeof(PushConstants),
+};
+constexpr VkPipelineVertexInputStateCreateInfo PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .vertexBindingDescriptionCount = 0,
+ .pVertexBindingDescriptions = nullptr,
+ .vertexAttributeDescriptionCount = 0,
+ .pVertexAttributeDescriptions = nullptr,
+};
+constexpr VkPipelineInputAssemblyStateCreateInfo PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+ .primitiveRestartEnable = VK_FALSE,
+};
+constexpr VkPipelineViewportStateCreateInfo PIPELINE_VIEWPORT_STATE_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .viewportCount = 1,
+ .pViewports = nullptr,
+ .scissorCount = 1,
+ .pScissors = nullptr,
+};
+constexpr VkPipelineRasterizationStateCreateInfo PIPELINE_RASTERIZATION_STATE_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .depthClampEnable = VK_FALSE,
+ .rasterizerDiscardEnable = VK_FALSE,
+ .polygonMode = VK_POLYGON_MODE_FILL,
+ .cullMode = VK_CULL_MODE_BACK_BIT,
+ .frontFace = VK_FRONT_FACE_CLOCKWISE,
+ .depthBiasEnable = VK_FALSE,
+ .depthBiasConstantFactor = 0.0f,
+ .depthBiasClamp = 0.0f,
+ .depthBiasSlopeFactor = 0.0f,
+ .lineWidth = 1.0f,
+};
+constexpr VkPipelineMultisampleStateCreateInfo PIPELINE_MULTISAMPLE_STATE_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ .sampleShadingEnable = VK_FALSE,
+ .minSampleShading = 0.0f,
+ .pSampleMask = nullptr,
+ .alphaToCoverageEnable = VK_FALSE,
+ .alphaToOneEnable = VK_FALSE,
+};
+constexpr std::array DYNAMIC_STATES{
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+};
+constexpr VkPipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .dynamicStateCount = static_cast<u32>(DYNAMIC_STATES.size()),
+ .pDynamicStates = DYNAMIC_STATES.data(),
+};
+constexpr VkPipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .logicOpEnable = VK_FALSE,
+ .logicOp = VK_LOGIC_OP_CLEAR,
+ .attachmentCount = 0,
+ .pAttachments = nullptr,
+ .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
+};
+constexpr VkPipelineColorBlendAttachmentState PIPELINE_COLOR_BLEND_ATTACHMENT_STATE{
+ .blendEnable = VK_FALSE,
+ .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+};
+constexpr VkPipelineColorBlendStateCreateInfo PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .logicOpEnable = VK_FALSE,
+ .logicOp = VK_LOGIC_OP_CLEAR,
+ .attachmentCount = 1,
+ .pAttachments = &PIPELINE_COLOR_BLEND_ATTACHMENT_STATE,
+ .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
+};
+constexpr VkPipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .depthTestEnable = VK_TRUE,
+ .depthWriteEnable = VK_TRUE,
+ .depthCompareOp = VK_COMPARE_OP_ALWAYS,
+ .depthBoundsTestEnable = VK_FALSE,
+ .stencilTestEnable = VK_FALSE,
+ .front = VkStencilOpState{},
+ .back = VkStencilOpState{},
+ .minDepthBounds = 0.0f,
+ .maxDepthBounds = 0.0f,
+};
+
+template <VkFilter filter>
+inline constexpr VkSamplerCreateInfo SAMPLER_CREATE_INFO{
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .magFilter = filter,
+ .minFilter = filter,
+ .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
+ .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
+ .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
+ .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
+ .mipLodBias = 0.0f,
+ .anisotropyEnable = VK_FALSE,
+ .maxAnisotropy = 0.0f,
+ .compareEnable = VK_FALSE,
+ .compareOp = VK_COMPARE_OP_NEVER,
+ .minLod = 0.0f,
+ .maxLod = 0.0f,
+ .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE,
+ .unnormalizedCoordinates = VK_TRUE,
+};
+
+constexpr VkPipelineLayoutCreateInfo PipelineLayoutCreateInfo(
+ const VkDescriptorSetLayout* set_layout) {
+ return VkPipelineLayoutCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .setLayoutCount = 1,
+ .pSetLayouts = set_layout,
+ .pushConstantRangeCount = 1,
+ .pPushConstantRanges = &PUSH_CONSTANT_RANGE,
+ };
+}
+
+constexpr VkPipelineShaderStageCreateInfo PipelineShaderStageCreateInfo(VkShaderStageFlagBits stage,
+ VkShaderModule shader) {
+ return VkPipelineShaderStageCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stage = stage,
+ .module = shader,
+ .pName = "main",
+ .pSpecializationInfo = nullptr,
+ };
+}
+
+constexpr std::array<VkPipelineShaderStageCreateInfo, 2> MakeStages(
+ VkShaderModule vertex_shader, VkShaderModule fragment_shader) {
+ return std::array{
+ PipelineShaderStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vertex_shader),
+ PipelineShaderStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, fragment_shader),
+ };
+}
+
+void UpdateOneTextureDescriptorSet(const Device& device, VkDescriptorSet descriptor_set,
+ VkSampler sampler, VkImageView image_view) {
+ const VkDescriptorImageInfo image_info{
+ .sampler = sampler,
+ .imageView = image_view,
+ .imageLayout = VK_IMAGE_LAYOUT_GENERAL,
+ };
+ const VkWriteDescriptorSet write_descriptor_set{
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .pNext = nullptr,
+ .dstSet = descriptor_set,
+ .dstBinding = 0,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &image_info,
+ .pBufferInfo = nullptr,
+ .pTexelBufferView = nullptr,
+ };
+ device.GetLogical().UpdateDescriptorSets(write_descriptor_set, nullptr);
+}
+
+void UpdateTwoTexturesDescriptorSet(const Device& device, VkDescriptorSet descriptor_set,
+ VkSampler sampler, VkImageView image_view_0,
+ VkImageView image_view_1) {
+ const VkDescriptorImageInfo image_info_0{
+ .sampler = sampler,
+ .imageView = image_view_0,
+ .imageLayout = VK_IMAGE_LAYOUT_GENERAL,
+ };
+ const VkDescriptorImageInfo image_info_1{
+ .sampler = sampler,
+ .imageView = image_view_1,
+ .imageLayout = VK_IMAGE_LAYOUT_GENERAL,
+ };
+ const std::array write_descriptor_sets{
+ VkWriteDescriptorSet{
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .pNext = nullptr,
+ .dstSet = descriptor_set,
+ .dstBinding = 0,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &image_info_0,
+ .pBufferInfo = nullptr,
+ .pTexelBufferView = nullptr,
+ },
+ VkWriteDescriptorSet{
+ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+ .pNext = nullptr,
+ .dstSet = descriptor_set,
+ .dstBinding = 1,
+ .dstArrayElement = 0,
+ .descriptorCount = 1,
+ .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ .pImageInfo = &image_info_1,
+ .pBufferInfo = nullptr,
+ .pTexelBufferView = nullptr,
+ },
+ };
+ device.GetLogical().UpdateDescriptorSets(write_descriptor_sets, nullptr);
+}
+
+void BindBlitState(vk::CommandBuffer cmdbuf, VkPipelineLayout layout,
+ const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region) {
+ const VkOffset2D offset{
+ .x = std::min(dst_region[0].x, dst_region[1].x),
+ .y = std::min(dst_region[0].y, dst_region[1].y),
+ };
+ const VkExtent2D extent{
+ .width = static_cast<u32>(std::abs(dst_region[1].x - dst_region[0].x)),
+ .height = static_cast<u32>(std::abs(dst_region[1].y - dst_region[0].y)),
+ };
+ const VkViewport viewport{
+ .x = static_cast<float>(offset.x),
+ .y = static_cast<float>(offset.y),
+ .width = static_cast<float>(extent.width),
+ .height = static_cast<float>(extent.height),
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f,
+ };
+ // TODO: Support scissored blits
+ const VkRect2D scissor{
+ .offset = offset,
+ .extent = extent,
+ };
+ const float scale_x = static_cast<float>(src_region[1].x - src_region[0].x);
+ const float scale_y = static_cast<float>(src_region[1].y - src_region[0].y);
+ const PushConstants push_constants{
+ .tex_scale = {scale_x, scale_y},
+ .tex_offset = {static_cast<float>(src_region[0].x), static_cast<float>(src_region[0].y)},
+ };
+ cmdbuf.SetViewport(0, viewport);
+ cmdbuf.SetScissor(0, scissor);
+ cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT, push_constants);
+}
+
+} // Anonymous namespace
+
+BlitImageHelper::BlitImageHelper(const Device& device_, VKScheduler& scheduler_,
+ StateTracker& state_tracker_, VKDescriptorPool& descriptor_pool)
+ : device{device_}, scheduler{scheduler_}, state_tracker{state_tracker_},
+ one_texture_set_layout(device.GetLogical().CreateDescriptorSetLayout(
+ ONE_TEXTURE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)),
+ two_textures_set_layout(device.GetLogical().CreateDescriptorSetLayout(
+ TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)),
+ one_texture_descriptor_allocator(descriptor_pool, *one_texture_set_layout),
+ two_textures_descriptor_allocator(descriptor_pool, *two_textures_set_layout),
+ one_texture_pipeline_layout(device.GetLogical().CreatePipelineLayout(
+ PipelineLayoutCreateInfo(one_texture_set_layout.address()))),
+ two_textures_pipeline_layout(device.GetLogical().CreatePipelineLayout(
+ PipelineLayoutCreateInfo(two_textures_set_layout.address()))),
+ full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
+ blit_color_to_color_frag(BuildShader(device, VULKAN_BLIT_COLOR_FLOAT_FRAG_SPV)),
+ convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
+ convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
+ linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
+ nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {
+ if (device.IsExtShaderStencilExportSupported()) {
+ blit_depth_stencil_frag = BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV);
+ }
+}
+
+BlitImageHelper::~BlitImageHelper() = default;
+
+void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
+ const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ Tegra::Engines::Fermi2D::Filter filter,
+ Tegra::Engines::Fermi2D::Operation operation) {
+ const bool is_linear = filter == Tegra::Engines::Fermi2D::Filter::Bilinear;
+ const BlitImagePipelineKey key{
+ .renderpass = dst_framebuffer->RenderPass(),
+ .operation = operation,
+ };
+ const VkPipelineLayout layout = *one_texture_pipeline_layout;
+ const VkImageView src_view = src_image_view.Handle(ImageViewType::e2D);
+ const VkSampler sampler = is_linear ? *linear_sampler : *nearest_sampler;
+ const VkPipeline pipeline = FindOrEmplacePipeline(key);
+ const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
+ scheduler.RequestRenderpass(dst_framebuffer);
+ scheduler.Record([dst_region, src_region, pipeline, layout, sampler, src_view, descriptor_set,
+ &device = device](vk::CommandBuffer cmdbuf) {
+ // TODO: Barriers
+ UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
+ nullptr);
+ BindBlitState(cmdbuf, layout, dst_region, src_region);
+ cmdbuf.Draw(3, 1, 0, 0);
+ });
+ scheduler.InvalidateState();
+}
+
+void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
+ VkImageView src_depth_view, VkImageView src_stencil_view,
+ const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ Tegra::Engines::Fermi2D::Filter filter,
+ Tegra::Engines::Fermi2D::Operation operation) {
+ ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point);
+ ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy);
+
+ const VkPipelineLayout layout = *two_textures_pipeline_layout;
+ const VkSampler sampler = *nearest_sampler;
+ const VkPipeline pipeline = BlitDepthStencilPipeline(dst_framebuffer->RenderPass());
+ const VkDescriptorSet descriptor_set = two_textures_descriptor_allocator.Commit();
+ scheduler.RequestRenderpass(dst_framebuffer);
+ scheduler.Record([dst_region, src_region, pipeline, layout, sampler, src_depth_view,
+ src_stencil_view, descriptor_set,
+ &device = device](vk::CommandBuffer cmdbuf) {
+ // TODO: Barriers
+ UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view,
+ src_stencil_view);
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
+ nullptr);
+ BindBlitState(cmdbuf, layout, dst_region, src_region);
+ cmdbuf.Draw(3, 1, 0, 0);
+ });
+ scheduler.InvalidateState();
+}
+
+void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer,
+ const ImageView& src_image_view) {
+ ConvertDepthToColorPipeline(convert_d32_to_r32_pipeline, dst_framebuffer->RenderPass());
+ Convert(*convert_d32_to_r32_pipeline, dst_framebuffer, src_image_view);
+}
+
+void BlitImageHelper::ConvertR32ToD32(const Framebuffer* dst_framebuffer,
+ const ImageView& src_image_view) {
+
+ ConvertColorToDepthPipeline(convert_r32_to_d32_pipeline, dst_framebuffer->RenderPass());
+ Convert(*convert_r32_to_d32_pipeline, dst_framebuffer, src_image_view);
+}
+
+void BlitImageHelper::ConvertD16ToR16(const Framebuffer* dst_framebuffer,
+ const ImageView& src_image_view) {
+ ConvertDepthToColorPipeline(convert_d16_to_r16_pipeline, dst_framebuffer->RenderPass());
+ Convert(*convert_d16_to_r16_pipeline, dst_framebuffer, src_image_view);
+}
+
+void BlitImageHelper::ConvertR16ToD16(const Framebuffer* dst_framebuffer,
+ const ImageView& src_image_view) {
+ ConvertColorToDepthPipeline(convert_r16_to_d16_pipeline, dst_framebuffer->RenderPass());
+ Convert(*convert_r16_to_d16_pipeline, dst_framebuffer, src_image_view);
+}
+
+void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
+ const ImageView& src_image_view) {
+ const VkPipelineLayout layout = *one_texture_pipeline_layout;
+ const VkImageView src_view = src_image_view.Handle(ImageViewType::e2D);
+ const VkSampler sampler = *nearest_sampler;
+ const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
+ const VkExtent2D extent{
+ .width = src_image_view.size.width,
+ .height = src_image_view.size.height,
+ };
+ scheduler.RequestRenderpass(dst_framebuffer);
+ scheduler.Record([pipeline, layout, sampler, src_view, descriptor_set, extent,
+ &device = device](vk::CommandBuffer cmdbuf) {
+ const VkOffset2D offset{
+ .x = 0,
+ .y = 0,
+ };
+ const VkViewport viewport{
+ .x = 0.0f,
+ .y = 0.0f,
+ .width = static_cast<float>(extent.width),
+ .height = static_cast<float>(extent.height),
+ .minDepth = 0.0f,
+ .maxDepth = 0.0f,
+ };
+ const VkRect2D scissor{
+ .offset = offset,
+ .extent = extent,
+ };
+ const PushConstants push_constants{
+ .tex_scale = {viewport.width, viewport.height},
+ .tex_offset = {0.0f, 0.0f},
+ };
+ UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
+
+ // TODO: Barriers
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
+ nullptr);
+ cmdbuf.SetViewport(0, viewport);
+ cmdbuf.SetScissor(0, scissor);
+ cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT, push_constants);
+ cmdbuf.Draw(3, 1, 0, 0);
+ });
+ scheduler.InvalidateState();
+}
+
+VkPipeline BlitImageHelper::FindOrEmplacePipeline(const BlitImagePipelineKey& key) {
+ const auto it = std::ranges::find(blit_color_keys, key);
+ if (it != blit_color_keys.end()) {
+ return *blit_color_pipelines[std::distance(blit_color_keys.begin(), it)];
+ }
+ blit_color_keys.push_back(key);
+
+ const std::array stages = MakeStages(*full_screen_vert, *blit_color_to_color_frag);
+ const VkPipelineColorBlendAttachmentState blend_attachment{
+ .blendEnable = VK_FALSE,
+ .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .colorBlendOp = VK_BLEND_OP_ADD,
+ .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
+ .alphaBlendOp = VK_BLEND_OP_ADD,
+ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
+ };
+ // TODO: programmable blending
+ const VkPipelineColorBlendStateCreateInfo color_blend_create_info{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .logicOpEnable = VK_FALSE,
+ .logicOp = VK_LOGIC_OP_CLEAR,
+ .attachmentCount = 1,
+ .pAttachments = &blend_attachment,
+ .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f},
+ };
+ blit_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stageCount = static_cast<u32>(stages.size()),
+ .pStages = stages.data(),
+ .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pTessellationState = nullptr,
+ .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pDepthStencilState = nullptr,
+ .pColorBlendState = &color_blend_create_info,
+ .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .layout = *one_texture_pipeline_layout,
+ .renderPass = key.renderpass,
+ .subpass = 0,
+ .basePipelineHandle = VK_NULL_HANDLE,
+ .basePipelineIndex = 0,
+ }));
+ return *blit_color_pipelines.back();
+}
+
+VkPipeline BlitImageHelper::BlitDepthStencilPipeline(VkRenderPass renderpass) {
+ if (blit_depth_stencil_pipeline) {
+ return *blit_depth_stencil_pipeline;
+ }
+ const std::array stages = MakeStages(*full_screen_vert, *blit_depth_stencil_frag);
+ blit_depth_stencil_pipeline = device.GetLogical().CreateGraphicsPipeline({
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stageCount = static_cast<u32>(stages.size()),
+ .pStages = stages.data(),
+ .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pTessellationState = nullptr,
+ .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+ .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
+ .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .layout = *two_textures_pipeline_layout,
+ .renderPass = renderpass,
+ .subpass = 0,
+ .basePipelineHandle = VK_NULL_HANDLE,
+ .basePipelineIndex = 0,
+ });
+ return *blit_depth_stencil_pipeline;
+}
+
+void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
+ if (pipeline) {
+ return;
+ }
+ const std::array stages = MakeStages(*full_screen_vert, *convert_depth_to_float_frag);
+ pipeline = device.GetLogical().CreateGraphicsPipeline({
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stageCount = static_cast<u32>(stages.size()),
+ .pStages = stages.data(),
+ .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pTessellationState = nullptr,
+ .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pDepthStencilState = nullptr,
+ .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
+ .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .layout = *one_texture_pipeline_layout,
+ .renderPass = renderpass,
+ .subpass = 0,
+ .basePipelineHandle = VK_NULL_HANDLE,
+ .basePipelineIndex = 0,
+ });
+}
+
+void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
+ if (pipeline) {
+ return;
+ }
+ const std::array stages = MakeStages(*full_screen_vert, *convert_float_to_depth_frag);
+ pipeline = device.GetLogical().CreateGraphicsPipeline({
+ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .stageCount = static_cast<u32>(stages.size()),
+ .pStages = stages.data(),
+ .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+ .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+ .pTessellationState = nullptr,
+ .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+ .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+ .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+ .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
+ .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
+ .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+ .layout = *one_texture_pipeline_layout,
+ .renderPass = renderpass,
+ .subpass = 0,
+ .basePipelineHandle = VK_NULL_HANDLE,
+ .basePipelineIndex = 0,
+ });
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
new file mode 100644
index 000000000..43fd3d737
--- /dev/null
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -0,0 +1,96 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <compare>
+
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
+#include "video_core/texture_cache/types.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+using VideoCommon::Offset2D;
+
+class Device;
+class Framebuffer;
+class ImageView;
+class StateTracker;
+class VKScheduler;
+
+struct BlitImagePipelineKey {
+ constexpr auto operator<=>(const BlitImagePipelineKey&) const noexcept = default;
+
+ VkRenderPass renderpass;
+ Tegra::Engines::Fermi2D::Operation operation;
+};
+
+class BlitImageHelper {
+public:
+ explicit BlitImageHelper(const Device& device, VKScheduler& scheduler,
+ StateTracker& state_tracker, VKDescriptorPool& descriptor_pool);
+ ~BlitImageHelper();
+
+ void BlitColor(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
+ const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ Tegra::Engines::Fermi2D::Filter filter,
+ Tegra::Engines::Fermi2D::Operation operation);
+
+ void BlitDepthStencil(const Framebuffer* dst_framebuffer, VkImageView src_depth_view,
+ VkImageView src_stencil_view, const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ Tegra::Engines::Fermi2D::Filter filter,
+ Tegra::Engines::Fermi2D::Operation operation);
+
+ void ConvertD32ToR32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
+
+ void ConvertR32ToD32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
+
+ void ConvertD16ToR16(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
+
+ void ConvertR16ToD16(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
+
+private:
+ void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
+ const ImageView& src_image_view);
+
+ [[nodiscard]] VkPipeline FindOrEmplacePipeline(const BlitImagePipelineKey& key);
+
+ [[nodiscard]] VkPipeline BlitDepthStencilPipeline(VkRenderPass renderpass);
+
+ void ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass);
+
+ void ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass);
+
+ const Device& device;
+ VKScheduler& scheduler;
+ StateTracker& state_tracker;
+
+ vk::DescriptorSetLayout one_texture_set_layout;
+ vk::DescriptorSetLayout two_textures_set_layout;
+ DescriptorAllocator one_texture_descriptor_allocator;
+ DescriptorAllocator two_textures_descriptor_allocator;
+ vk::PipelineLayout one_texture_pipeline_layout;
+ vk::PipelineLayout two_textures_pipeline_layout;
+ vk::ShaderModule full_screen_vert;
+ vk::ShaderModule blit_color_to_color_frag;
+ vk::ShaderModule blit_depth_stencil_frag;
+ vk::ShaderModule convert_depth_to_float_frag;
+ vk::ShaderModule convert_float_to_depth_frag;
+ vk::Sampler linear_sampler;
+ vk::Sampler nearest_sampler;
+
+ std::vector<BlitImagePipelineKey> blit_color_keys;
+ std::vector<vk::Pipeline> blit_color_pipelines;
+ vk::Pipeline blit_depth_stencil_pipeline;
+ vk::Pipeline convert_d32_to_r32_pipeline;
+ vk::Pipeline convert_r32_to_d32_pipeline;
+ vk::Pipeline convert_d16_to_r16_pipeline;
+ vk::Pipeline convert_r16_to_d16_pipeline;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
index 81a39a3b8..362278f01 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -8,17 +8,19 @@
#include <boost/functional/hash.hpp>
+#include "common/bit_cast.h"
#include "common/cityhash.h"
#include "common/common_types.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
+#include "video_core/renderer_vulkan/vk_state_tracker.h"
namespace Vulkan {
namespace {
-constexpr std::size_t POINT = 0;
-constexpr std::size_t LINE = 1;
-constexpr std::size_t POLYGON = 2;
+constexpr size_t POINT = 0;
+constexpr size_t LINE = 1;
+constexpr size_t POLYGON = 2;
constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
POINT, // Points
LINE, // Lines
@@ -39,13 +41,17 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
} // Anonymous namespace
-void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) {
- const std::array enabled_lut = {regs.polygon_offset_point_enable,
- regs.polygon_offset_line_enable,
- regs.polygon_offset_fill_enable};
+void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d,
+ bool has_extended_dynamic_state) {
+ const Maxwell& regs = maxwell3d.regs;
+ const std::array enabled_lut{
+ regs.polygon_offset_point_enable,
+ regs.polygon_offset_line_enable,
+ regs.polygon_offset_fill_enable,
+ };
const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
- raw = 0;
+ raw1 = 0;
primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0);
depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value());
@@ -58,40 +64,58 @@ void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_sta
logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
logic_op.Assign(PackLogicOp(regs.logic_op.operation));
rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0);
+ topology.Assign(regs.draw.topology);
+ msaa_mode.Assign(regs.multisample_mode);
- std::memcpy(&point_size, &regs.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast
-
- for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
- binding_divisors[index] =
- regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0;
+ raw2 = 0;
+ const auto test_func =
+ regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always;
+ alpha_test_func.Assign(PackComparisonOp(test_func));
+ early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0);
+
+ alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref);
+ point_size = Common::BitCast<u32>(regs.point_size);
+
+ if (maxwell3d.dirty.flags[Dirty::InstanceDivisors]) {
+ maxwell3d.dirty.flags[Dirty::InstanceDivisors] = false;
+ for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ const bool is_enabled = regs.instanced_arrays.IsInstancingEnabled(index);
+ binding_divisors[index] = is_enabled ? regs.vertex_array[index].divisor : 0;
+ }
}
-
- for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
- const auto& input = regs.vertex_attrib_format[index];
- auto& attribute = attributes[index];
- attribute.raw = 0;
- attribute.enabled.Assign(input.IsConstant() ? 0 : 1);
- attribute.buffer.Assign(input.buffer);
- attribute.offset.Assign(input.offset);
- attribute.type.Assign(static_cast<u32>(input.type.Value()));
- attribute.size.Assign(static_cast<u32>(input.size.Value()));
+ if (maxwell3d.dirty.flags[Dirty::VertexAttributes]) {
+ maxwell3d.dirty.flags[Dirty::VertexAttributes] = false;
+ for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
+ const auto& input = regs.vertex_attrib_format[index];
+ auto& attribute = attributes[index];
+ attribute.raw = 0;
+ attribute.enabled.Assign(input.IsConstant() ? 0 : 1);
+ attribute.buffer.Assign(input.buffer);
+ attribute.offset.Assign(input.offset);
+ attribute.type.Assign(static_cast<u32>(input.type.Value()));
+ attribute.size.Assign(static_cast<u32>(input.size.Value()));
+ }
}
-
- for (std::size_t index = 0; index < std::size(attachments); ++index) {
- attachments[index].Fill(regs, index);
+ if (maxwell3d.dirty.flags[Dirty::Blending]) {
+ maxwell3d.dirty.flags[Dirty::Blending] = false;
+ for (size_t index = 0; index < attachments.size(); ++index) {
+ attachments[index].Refresh(regs, index);
+ }
+ }
+ if (maxwell3d.dirty.flags[Dirty::ViewportSwizzles]) {
+ maxwell3d.dirty.flags[Dirty::ViewportSwizzles] = false;
+ const auto& transform = regs.viewport_transform;
+ std::ranges::transform(transform, viewport_swizzles.begin(), [](const auto& viewport) {
+ return static_cast<u16>(viewport.swizzle.raw);
+ });
}
-
- const auto& transform = regs.viewport_transform;
- std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(),
- [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); });
-
if (!has_extended_dynamic_state) {
no_extended_dynamic_state.Assign(1);
- dynamic_state.Fill(regs);
+ dynamic_state.Refresh(regs);
}
}
-void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) {
+void FixedPipelineState::BlendingAttachment::Refresh(const Maxwell& regs, size_t index) {
const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index];
raw = 0;
@@ -130,8 +154,7 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size
enable.Assign(1);
}
-void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) {
- const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
+void FixedPipelineState::DynamicState::Refresh(const Maxwell& regs) {
u32 packed_front_face = PackFrontFace(regs.front_face);
if (regs.screen_y_control.triangle_rast_flip != 0) {
// Flip front face
@@ -161,22 +184,16 @@ void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) {
depth_test_enable.Assign(regs.depth_test_enable);
front_face.Assign(packed_front_face);
depth_test_func.Assign(PackComparisonOp(regs.depth_test_func));
- topology.Assign(topology_index);
cull_face.Assign(PackCullFace(regs.cull_face));
cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0);
-
- for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
- const auto& input = regs.vertex_array[index];
- VertexBinding& binding = vertex_bindings[index];
- binding.raw = 0;
- binding.enabled.Assign(input.IsEnabled() ? 1 : 0);
- binding.stride.Assign(static_cast<u16>(input.stride.Value()));
- }
+ std::ranges::transform(regs.vertex_array, vertex_strides.begin(), [](const auto& array) {
+ return static_cast<u16>(array.stride.Value());
+ });
}
-std::size_t FixedPipelineState::Hash() const noexcept {
+size_t FixedPipelineState::Hash() const noexcept {
const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
- return static_cast<std::size_t>(hash);
+ return static_cast<size_t>(hash);
}
bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
index cdcbb65f5..a0eb83a68 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -58,7 +58,7 @@ struct FixedPipelineState {
BitField<30, 1, u32> enable;
};
- void Fill(const Maxwell& regs, std::size_t index);
+ void Refresh(const Maxwell& regs, size_t index);
constexpr std::array<bool, 4> Mask() const noexcept {
return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0};
@@ -106,7 +106,7 @@ struct FixedPipelineState {
}
};
- template <std::size_t Position>
+ template <size_t Position>
union StencilFace {
BitField<Position + 0, 3, u32> action_stencil_fail;
BitField<Position + 3, 3, u32> action_depth_fail;
@@ -130,12 +130,6 @@ struct FixedPipelineState {
}
};
- union VertexBinding {
- u16 raw;
- BitField<0, 12, u16> stride;
- BitField<12, 1, u16> enabled;
- };
-
struct DynamicState {
union {
u32 raw1;
@@ -150,13 +144,13 @@ struct FixedPipelineState {
};
union {
u32 raw2;
- BitField<0, 4, u32> topology;
- BitField<4, 2, u32> cull_face;
- BitField<6, 1, u32> cull_enable;
+ BitField<0, 2, u32> cull_face;
+ BitField<2, 1, u32> cull_enable;
};
- std::array<VertexBinding, Maxwell::NumVertexArrays> vertex_bindings;
+ // Vertex stride is a 12 bits value, we have 4 bits to spare per element
+ std::array<u16, Maxwell::NumVertexArrays> vertex_strides;
- void Fill(const Maxwell& regs);
+ void Refresh(const Maxwell& regs);
Maxwell::ComparisonOp DepthTestFunc() const noexcept {
return UnpackComparisonOp(depth_test_func);
@@ -169,14 +163,10 @@ struct FixedPipelineState {
Maxwell::FrontFace FrontFace() const noexcept {
return UnpackFrontFace(front_face.Value());
}
-
- constexpr Maxwell::PrimitiveTopology Topology() const noexcept {
- return static_cast<Maxwell::PrimitiveTopology>(topology.Value());
- }
};
union {
- u32 raw;
+ u32 raw1;
BitField<0, 1, u32> no_extended_dynamic_state;
BitField<2, 1, u32> primitive_restart_enable;
BitField<3, 1, u32> depth_bias_enable;
@@ -190,7 +180,16 @@ struct FixedPipelineState {
BitField<18, 1, u32> logic_op_enable;
BitField<19, 4, u32> logic_op;
BitField<23, 1, u32> rasterize_enable;
+ BitField<24, 4, Maxwell::PrimitiveTopology> topology;
+ BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode;
+ };
+ union {
+ u32 raw2;
+ BitField<0, 3, u32> alpha_test_func;
+ BitField<3, 1, u32> early_z;
};
+
+ u32 alpha_test_ref;
u32 point_size;
std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
@@ -198,9 +197,9 @@ struct FixedPipelineState {
std::array<u16, Maxwell::NumViewports> viewport_swizzles;
DynamicState dynamic_state;
- void Fill(const Maxwell& regs, bool has_extended_dynamic_state);
+ void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, bool has_extended_dynamic_state);
- std::size_t Hash() const noexcept;
+ size_t Hash() const noexcept;
bool operator==(const FixedPipelineState& rhs) const noexcept;
@@ -208,8 +207,8 @@ struct FixedPipelineState {
return !operator==(rhs);
}
- std::size_t Size() const noexcept {
- const std::size_t total_size = sizeof *this;
+ size_t Size() const noexcept {
+ const size_t total_size = sizeof *this;
return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState));
}
};
@@ -223,7 +222,7 @@ namespace std {
template <>
struct hash<Vulkan::FixedPipelineState> {
- std::size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept {
+ size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept {
return k.Hash();
}
};
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index f8c77f4fa..19aaf034f 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -9,9 +9,9 @@
#include "common/logging/log.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/surface.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan::MaxwellToVK {
@@ -26,7 +26,7 @@ VkFilter Filter(Tegra::Texture::TextureFilter filter) {
case Tegra::Texture::TextureFilter::Linear:
return VK_FILTER_LINEAR;
}
- UNREACHABLE_MSG("Invalid sampler filter={}", static_cast<u32>(filter));
+ UNREACHABLE_MSG("Invalid sampler filter={}", filter);
return {};
}
@@ -43,11 +43,11 @@ VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter
case Tegra::Texture::TextureMipmapFilter::Linear:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
}
- UNREACHABLE_MSG("Invalid sampler mipmap mode={}", static_cast<u32>(mipmap_filter));
+ UNREACHABLE_MSG("Invalid sampler mipmap mode={}", mipmap_filter);
return {};
}
-VkSamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode wrap_mode,
+VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wrap_mode,
Tegra::Texture::TextureFilter filter) {
switch (wrap_mode) {
case Tegra::Texture::WrapMode::Wrap:
@@ -78,9 +78,10 @@ VkSamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode w
case Tegra::Texture::WrapMode::MirrorOnceBorder:
UNIMPLEMENTED();
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", wrap_mode);
+ return {};
}
- UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
- return {};
}
VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) {
@@ -102,16 +103,15 @@ VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_
case Tegra::Texture::DepthCompareFunc::Always:
return VK_COMPARE_OP_ALWAYS;
}
- UNIMPLEMENTED_MSG("Unimplemented sampler depth compare function={}",
- static_cast<u32>(depth_compare_func));
+ UNIMPLEMENTED_MSG("Unimplemented sampler depth compare function={}", depth_compare_func);
return {};
}
} // namespace Sampler
namespace {
-
-enum : u32 { Attachable = 1, Storage = 2 };
+constexpr u32 Attachable = 1 << 0;
+constexpr u32 Storage = 1 << 1;
struct FormatTuple {
VkFormat format; ///< Vulkan format
@@ -122,7 +122,7 @@ struct FormatTuple {
{VK_FORMAT_A8B8G8R8_SINT_PACK32, Attachable | Storage}, // A8B8G8R8_SINT
{VK_FORMAT_A8B8G8R8_UINT_PACK32, Attachable | Storage}, // A8B8G8R8_UINT
{VK_FORMAT_R5G6B5_UNORM_PACK16, Attachable}, // R5G6B5_UNORM
- {VK_FORMAT_B5G6R5_UNORM_PACK16, Attachable}, // B5G6R5_UNORM
+ {VK_FORMAT_B5G6R5_UNORM_PACK16}, // B5G6R5_UNORM
{VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1R5G5B5_UNORM
{VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM
{VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT
@@ -163,7 +163,7 @@ struct FormatTuple {
{VK_FORMAT_R16G16_UNORM, Attachable | Storage}, // R16G16_UNORM
{VK_FORMAT_R16G16_SFLOAT, Attachable | Storage}, // R16G16_FLOAT
{VK_FORMAT_UNDEFINED}, // R16G16_UINT
- {VK_FORMAT_UNDEFINED}, // R16G16_SINT
+ {VK_FORMAT_R16G16_SINT, Attachable | Storage}, // R16G16_SINT
{VK_FORMAT_R16G16_SNORM, Attachable | Storage}, // R16G16_SNORM
{VK_FORMAT_UNDEFINED}, // R32G32B32_FLOAT
{VK_FORMAT_R8G8B8A8_SRGB, Attachable}, // A8B8G8R8_SRGB
@@ -222,30 +222,36 @@ constexpr bool IsZetaFormat(PixelFormat pixel_format) {
} // Anonymous namespace
-FormatInfo SurfaceFormat(const VKDevice& device, FormatType format_type, PixelFormat pixel_format) {
- ASSERT(static_cast<std::size_t>(pixel_format) < std::size(tex_format_tuples));
-
- auto tuple = tex_format_tuples[static_cast<std::size_t>(pixel_format)];
+FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with_srgb,
+ PixelFormat pixel_format) {
+ ASSERT(static_cast<size_t>(pixel_format) < std::size(tex_format_tuples));
+ FormatTuple tuple = tex_format_tuples[static_cast<size_t>(pixel_format)];
if (tuple.format == VK_FORMAT_UNDEFINED) {
- UNIMPLEMENTED_MSG("Unimplemented texture format with pixel format={}",
- static_cast<u32>(pixel_format));
- return {VK_FORMAT_A8B8G8R8_UNORM_PACK32, true, true};
+ UNIMPLEMENTED_MSG("Unimplemented texture format with pixel format={}", pixel_format);
+ return FormatInfo{VK_FORMAT_A8B8G8R8_UNORM_PACK32, true, true};
}
// Use A8B8G8R8_UNORM on hardware that doesn't support ASTC natively
if (!device.IsOptimalAstcSupported() && VideoCore::Surface::IsPixelFormatASTC(pixel_format)) {
- tuple.format = VideoCore::Surface::IsPixelFormatSRGB(pixel_format)
- ? VK_FORMAT_A8B8G8R8_SRGB_PACK32
- : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+ const bool is_srgb = with_srgb && VideoCore::Surface::IsPixelFormatSRGB(pixel_format);
+ if (is_srgb) {
+ tuple.format = VK_FORMAT_A8B8G8R8_SRGB_PACK32;
+ } else {
+ tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+ tuple.usage |= Storage;
+ }
}
- const bool attachable = tuple.usage & Attachable;
- const bool storage = tuple.usage & Storage;
+ const bool attachable = (tuple.usage & Attachable) != 0;
+ const bool storage = (tuple.usage & Storage) != 0;
- VkFormatFeatureFlags usage;
- if (format_type == FormatType::Buffer) {
+ VkFormatFeatureFlags usage{};
+ switch (format_type) {
+ case FormatType::Buffer:
usage =
VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT | VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT;
- } else {
+ break;
+ case FormatType::Linear:
+ case FormatType::Optimal:
usage = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT |
VK_FORMAT_FEATURE_TRANSFER_SRC_BIT;
if (attachable) {
@@ -255,6 +261,7 @@ FormatInfo SurfaceFormat(const VKDevice& device, FormatType format_type, PixelFo
if (storage) {
usage |= VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
}
+ break;
}
return {device.GetSupportedFormat(tuple.format, usage, format_type), attachable, storage};
}
@@ -274,11 +281,11 @@ VkShaderStageFlagBits ShaderStage(Tegra::Engines::ShaderType stage) {
case Tegra::Engines::ShaderType::Compute:
return VK_SHADER_STAGE_COMPUTE_BIT;
}
- UNIMPLEMENTED_MSG("Unimplemented shader stage={}", static_cast<u32>(stage));
+ UNIMPLEMENTED_MSG("Unimplemented shader stage={}", stage);
return {};
}
-VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const VKDevice& device,
+VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
Maxwell::PrimitiveTopology topology) {
switch (topology) {
case Maxwell::PrimitiveTopology::Points:
@@ -298,9 +305,10 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const VKDevice& device,
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
case Maxwell::PrimitiveTopology::Patches:
return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented topology={}", topology);
+ return {};
}
- UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
- return {};
}
VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) {
@@ -325,6 +333,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R16G16B16A16_UNORM;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+ default:
+ break;
}
break;
case Maxwell::VertexAttribute::Type::SignedNorm:
@@ -347,6 +357,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R16G16B16A16_SNORM;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
+ default:
+ break;
}
break;
case Maxwell::VertexAttribute::Type::UnsignedScaled:
@@ -369,6 +381,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R16G16B16A16_USCALED;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
+ default:
+ break;
}
break;
case Maxwell::VertexAttribute::Type::SignedScaled:
@@ -391,6 +405,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R16G16B16A16_SSCALED;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
+ default:
+ break;
}
break;
case Maxwell::VertexAttribute::Type::UnsignedInt:
@@ -421,6 +437,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R32G32B32A32_UINT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_UINT_PACK32;
+ default:
+ break;
}
break;
case Maxwell::VertexAttribute::Type::SignedInt:
@@ -451,6 +469,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R32G32B32A32_SINT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_SINT_PACK32;
+ default:
+ break;
}
break;
case Maxwell::VertexAttribute::Type::Float:
@@ -471,11 +491,12 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R32G32B32_SFLOAT;
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return VK_FORMAT_R32G32B32A32_SFLOAT;
+ default:
+ break;
}
break;
}
- UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", static_cast<u32>(type),
- static_cast<u32>(size));
+ UNIMPLEMENTED_MSG("Unimplemented vertex format of type={} and size={}", type, size);
return {};
}
@@ -506,24 +527,20 @@ VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison) {
case Maxwell::ComparisonOp::AlwaysOld:
return VK_COMPARE_OP_ALWAYS;
}
- UNIMPLEMENTED_MSG("Unimplemented comparison op={}", static_cast<u32>(comparison));
+ UNIMPLEMENTED_MSG("Unimplemented comparison op={}", comparison);
return {};
}
-VkIndexType IndexFormat(const VKDevice& device, Maxwell::IndexFormat index_format) {
+VkIndexType IndexFormat(Maxwell::IndexFormat index_format) {
switch (index_format) {
case Maxwell::IndexFormat::UnsignedByte:
- if (!device.IsExtIndexTypeUint8Supported()) {
- UNIMPLEMENTED_MSG("Native uint8 indices are not supported on this device");
- return VK_INDEX_TYPE_UINT16;
- }
return VK_INDEX_TYPE_UINT8_EXT;
case Maxwell::IndexFormat::UnsignedShort:
return VK_INDEX_TYPE_UINT16;
case Maxwell::IndexFormat::UnsignedInt:
return VK_INDEX_TYPE_UINT32;
}
- UNIMPLEMENTED_MSG("Unimplemented index_format={}", static_cast<u32>(index_format));
+ UNIMPLEMENTED_MSG("Unimplemented index_format={}", index_format);
return {};
}
@@ -554,7 +571,7 @@ VkStencilOp StencilOp(Maxwell::StencilOp stencil_op) {
case Maxwell::StencilOp::DecrWrapOGL:
return VK_STENCIL_OP_DECREMENT_AND_WRAP;
}
- UNIMPLEMENTED_MSG("Unimplemented stencil op={}", static_cast<u32>(stencil_op));
+ UNIMPLEMENTED_MSG("Unimplemented stencil op={}", stencil_op);
return {};
}
@@ -576,7 +593,7 @@ VkBlendOp BlendEquation(Maxwell::Blend::Equation equation) {
case Maxwell::Blend::Equation::MaxGL:
return VK_BLEND_OP_MAX;
}
- UNIMPLEMENTED_MSG("Unimplemented blend equation={}", static_cast<u32>(equation));
+ UNIMPLEMENTED_MSG("Unimplemented blend equation={}", equation);
return {};
}
@@ -640,7 +657,7 @@ VkBlendFactor BlendFactor(Maxwell::Blend::Factor factor) {
case Maxwell::Blend::Factor::OneMinusConstantAlphaGL:
return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
}
- UNIMPLEMENTED_MSG("Unimplemented blend factor={}", static_cast<u32>(factor));
+ UNIMPLEMENTED_MSG("Unimplemented blend factor={}", factor);
return {};
}
@@ -651,11 +668,11 @@ VkFrontFace FrontFace(Maxwell::FrontFace front_face) {
case Maxwell::FrontFace::CounterClockWise:
return VK_FRONT_FACE_COUNTER_CLOCKWISE;
}
- UNIMPLEMENTED_MSG("Unimplemented front face={}", static_cast<u32>(front_face));
+ UNIMPLEMENTED_MSG("Unimplemented front face={}", front_face);
return {};
}
-VkCullModeFlags CullFace(Maxwell::CullFace cull_face) {
+VkCullModeFlagBits CullFace(Maxwell::CullFace cull_face) {
switch (cull_face) {
case Maxwell::CullFace::Front:
return VK_CULL_MODE_FRONT_BIT;
@@ -664,7 +681,7 @@ VkCullModeFlags CullFace(Maxwell::CullFace cull_face) {
case Maxwell::CullFace::FrontAndBack:
return VK_CULL_MODE_FRONT_AND_BACK;
}
- UNIMPLEMENTED_MSG("Unimplemented cull face={}", static_cast<u32>(cull_face));
+ UNIMPLEMENTED_MSG("Unimplemented cull face={}", cull_face);
return {};
}
@@ -684,7 +701,7 @@ VkComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle) {
case Tegra::Texture::SwizzleSource::OneFloat:
return VK_COMPONENT_SWIZZLE_ONE;
}
- UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", static_cast<u32>(swizzle));
+ UNIMPLEMENTED_MSG("Unimplemented swizzle source={}", swizzle);
return {};
}
@@ -707,8 +724,21 @@ VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle)
case Maxwell::ViewportSwizzle::NegativeW:
return VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV;
}
- UNREACHABLE_MSG("Invalid swizzle={}", static_cast<int>(swizzle));
+ UNREACHABLE_MSG("Invalid swizzle={}", swizzle);
return {};
}
+VkSamplerReductionMode SamplerReduction(Tegra::Texture::SamplerReduction reduction) {
+ switch (reduction) {
+ case Tegra::Texture::SamplerReduction::WeightedAverage:
+ return VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT;
+ case Tegra::Texture::SamplerReduction::Min:
+ return VK_SAMPLER_REDUCTION_MODE_MIN_EXT;
+ case Tegra::Texture::SamplerReduction::Max:
+ return VK_SAMPLER_REDUCTION_MODE_MAX_EXT;
+ }
+ UNREACHABLE_MSG("Invalid sampler mode={}", static_cast<int>(reduction));
+ return VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT;
+}
+
} // namespace Vulkan::MaxwellToVK
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.h b/src/video_core/renderer_vulkan/maxwell_to_vk.h
index 7e213452f..e3e06ba38 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.h
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.h
@@ -6,10 +6,10 @@
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/surface.h"
#include "video_core/textures/texture.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan::MaxwellToVK {
@@ -22,7 +22,7 @@ VkFilter Filter(Tegra::Texture::TextureFilter filter);
VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter);
-VkSamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode wrap_mode,
+VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wrap_mode,
Tegra::Texture::TextureFilter filter);
VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func);
@@ -35,17 +35,25 @@ struct FormatInfo {
bool storage;
};
-FormatInfo SurfaceFormat(const VKDevice& device, FormatType format_type, PixelFormat pixel_format);
+/**
+ * Returns format properties supported in the host
+ * @param device Host device
+ * @param format_type Type of image the buffer will use
+ * @param with_srgb True when the format can be sRGB when converted to another format (ASTC)
+ * @param pixel_format Guest pixel format to describe
+ */
+[[nodiscard]] FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with_srgb,
+ PixelFormat pixel_format);
VkShaderStageFlagBits ShaderStage(Tegra::Engines::ShaderType stage);
-VkPrimitiveTopology PrimitiveTopology(const VKDevice& device, Maxwell::PrimitiveTopology topology);
+VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology);
VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size);
VkCompareOp ComparisonOp(Maxwell::ComparisonOp comparison);
-VkIndexType IndexFormat(const VKDevice& device, Maxwell::IndexFormat index_format);
+VkIndexType IndexFormat(Maxwell::IndexFormat index_format);
VkStencilOp StencilOp(Maxwell::StencilOp stencil_op);
@@ -55,10 +63,12 @@ VkBlendFactor BlendFactor(Maxwell::Blend::Factor factor);
VkFrontFace FrontFace(Maxwell::FrontFace front_face);
-VkCullModeFlags CullFace(Maxwell::CullFace cull_face);
+VkCullModeFlagBits CullFace(Maxwell::CullFace cull_face);
VkComponentSwizzle SwizzleSource(Tegra::Texture::SwizzleSource swizzle);
VkViewportCoordinateSwizzleNV ViewportSwizzle(Maxwell::ViewportSwizzle swizzle);
+VkSamplerReductionMode SamplerReduction(Tegra::Texture::SamplerReduction reduction);
+
} // namespace Vulkan::MaxwellToVK
diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
deleted file mode 100644
index 5b01020ec..000000000
--- a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.cpp
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#ifdef HAS_NSIGHT_AFTERMATH
-
-#include <mutex>
-#include <string>
-#include <string_view>
-#include <utility>
-#include <vector>
-
-#include <fmt/format.h>
-
-#define VK_NO_PROTOTYPES
-#include <vulkan/vulkan.h>
-
-#include <GFSDK_Aftermath.h>
-#include <GFSDK_Aftermath_Defines.h>
-#include <GFSDK_Aftermath_GpuCrashDump.h>
-#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h>
-
-#include "common/common_paths.h"
-#include "common/common_types.h"
-#include "common/file_util.h"
-#include "common/logging/log.h"
-#include "common/scope_exit.h"
-
-#include "video_core/renderer_vulkan/nsight_aftermath_tracker.h"
-
-namespace Vulkan {
-
-static constexpr char AFTERMATH_LIB_NAME[] = "GFSDK_Aftermath_Lib.x64.dll";
-
-NsightAftermathTracker::NsightAftermathTracker() = default;
-
-NsightAftermathTracker::~NsightAftermathTracker() {
- if (initialized) {
- (void)GFSDK_Aftermath_DisableGpuCrashDumps();
- }
-}
-
-bool NsightAftermathTracker::Initialize() {
- if (!dl.Open(AFTERMATH_LIB_NAME)) {
- LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath DLL");
- return false;
- }
-
- if (!dl.GetSymbol("GFSDK_Aftermath_DisableGpuCrashDumps",
- &GFSDK_Aftermath_DisableGpuCrashDumps) ||
- !dl.GetSymbol("GFSDK_Aftermath_EnableGpuCrashDumps",
- &GFSDK_Aftermath_EnableGpuCrashDumps) ||
- !dl.GetSymbol("GFSDK_Aftermath_GetShaderDebugInfoIdentifier",
- &GFSDK_Aftermath_GetShaderDebugInfoIdentifier) ||
- !dl.GetSymbol("GFSDK_Aftermath_GetShaderHashSpirv", &GFSDK_Aftermath_GetShaderHashSpirv) ||
- !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_CreateDecoder",
- &GFSDK_Aftermath_GpuCrashDump_CreateDecoder) ||
- !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_DestroyDecoder",
- &GFSDK_Aftermath_GpuCrashDump_DestroyDecoder) ||
- !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GenerateJSON",
- &GFSDK_Aftermath_GpuCrashDump_GenerateJSON) ||
- !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GetJSON",
- &GFSDK_Aftermath_GpuCrashDump_GetJSON)) {
- LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers");
- return false;
- }
-
- dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash";
-
- (void)Common::FS::DeleteDirRecursively(dump_dir);
- if (!Common::FS::CreateDir(dump_dir)) {
- LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
- return false;
- }
-
- if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_EnableGpuCrashDumps(
- GFSDK_Aftermath_Version_API, GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan,
- GFSDK_Aftermath_GpuCrashDumpFeatureFlags_Default, GpuCrashDumpCallback,
- ShaderDebugInfoCallback, CrashDumpDescriptionCallback, this))) {
- LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed");
- return false;
- }
-
- LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", dump_dir);
-
- initialized = true;
- return true;
-}
-
-void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const {
- if (!initialized) {
- return;
- }
-
- std::vector<u32> spirv_copy = spirv;
- GFSDK_Aftermath_SpirvCode shader;
- shader.pData = spirv_copy.data();
- shader.size = static_cast<u32>(spirv_copy.size() * 4);
-
- std::scoped_lock lock{mutex};
-
- GFSDK_Aftermath_ShaderHash hash;
- if (!GFSDK_Aftermath_SUCCEED(
- GFSDK_Aftermath_GetShaderHashSpirv(GFSDK_Aftermath_Version_API, &shader, &hash))) {
- LOG_ERROR(Render_Vulkan, "Failed to hash SPIR-V module");
- return;
- }
-
- Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb");
- if (!file.IsOpen()) {
- LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash);
- return;
- }
- if (file.WriteArray(spirv.data(), spirv.size()) != spirv.size()) {
- LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash);
- return;
- }
-}
-
-void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
- u32 gpu_crash_dump_size) {
- std::scoped_lock lock{mutex};
-
- LOG_CRITICAL(Render_Vulkan, "called");
-
- GFSDK_Aftermath_GpuCrashDump_Decoder decoder;
- if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_CreateDecoder(
- GFSDK_Aftermath_Version_API, gpu_crash_dump, gpu_crash_dump_size, &decoder))) {
- LOG_ERROR(Render_Vulkan, "Failed to create decoder");
- return;
- }
- SCOPE_EXIT({ GFSDK_Aftermath_GpuCrashDump_DestroyDecoder(decoder); });
-
- u32 json_size = 0;
- if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_GenerateJSON(
- decoder, GFSDK_Aftermath_GpuCrashDumpDecoderFlags_ALL_INFO,
- GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE, nullptr, nullptr, nullptr, nullptr,
- this, &json_size))) {
- LOG_ERROR(Render_Vulkan, "Failed to generate JSON");
- return;
- }
- std::vector<char> json(json_size);
- if (!GFSDK_Aftermath_SUCCEED(
- GFSDK_Aftermath_GpuCrashDump_GetJSON(decoder, json_size, json.data()))) {
- LOG_ERROR(Render_Vulkan, "Failed to query JSON");
- return;
- }
-
- const std::string base_name = [this] {
- const int id = dump_id++;
- if (id == 0) {
- return fmt::format("{}/crash.nv-gpudmp", dump_dir);
- } else {
- return fmt::format("{}/crash_{}.nv-gpudmp", dump_dir, id);
- }
- }();
-
- std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size);
- if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) {
- LOG_ERROR(Render_Vulkan, "Failed to write dump file");
- return;
- }
- const std::string_view json_view(json.data(), json.size());
- if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) {
- LOG_ERROR(Render_Vulkan, "Failed to write JSON");
- return;
- }
-}
-
-void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_info,
- u32 shader_debug_info_size) {
- std::scoped_lock lock{mutex};
-
- GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier;
- if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GetShaderDebugInfoIdentifier(
- GFSDK_Aftermath_Version_API, shader_debug_info, shader_debug_info_size, &identifier))) {
- LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_GetShaderDebugInfoIdentifier failed");
- return;
- }
-
- const std::string path =
- fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]);
- Common::FS::IOFile file(path, "wb");
- if (!file.IsOpen()) {
- LOG_ERROR(Render_Vulkan, "Failed to create file {}", path);
- return;
- }
- if (file.WriteBytes(static_cast<const u8*>(shader_debug_info), shader_debug_info_size) !=
- shader_debug_info_size) {
- LOG_ERROR(Render_Vulkan, "Failed to write file {}", path);
- return;
- }
-}
-
-void NsightAftermathTracker::OnCrashDumpDescriptionCallback(
- PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description) {
- add_description(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationName, "yuzu");
-}
-
-void NsightAftermathTracker::GpuCrashDumpCallback(const void* gpu_crash_dump,
- u32 gpu_crash_dump_size, void* user_data) {
- static_cast<NsightAftermathTracker*>(user_data)->OnGpuCrashDumpCallback(gpu_crash_dump,
- gpu_crash_dump_size);
-}
-
-void NsightAftermathTracker::ShaderDebugInfoCallback(const void* shader_debug_info,
- u32 shader_debug_info_size, void* user_data) {
- static_cast<NsightAftermathTracker*>(user_data)->OnShaderDebugInfoCallback(
- shader_debug_info, shader_debug_info_size);
-}
-
-void NsightAftermathTracker::CrashDumpDescriptionCallback(
- PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description, void* user_data) {
- static_cast<NsightAftermathTracker*>(user_data)->OnCrashDumpDescriptionCallback(
- add_description);
-}
-
-} // namespace Vulkan
-
-#endif // HAS_NSIGHT_AFTERMATH
diff --git a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.h b/src/video_core/renderer_vulkan/nsight_aftermath_tracker.h
deleted file mode 100644
index afe7ae99e..000000000
--- a/src/video_core/renderer_vulkan/nsight_aftermath_tracker.h
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <mutex>
-#include <string>
-#include <vector>
-
-#define VK_NO_PROTOTYPES
-#include <vulkan/vulkan.h>
-
-#ifdef HAS_NSIGHT_AFTERMATH
-#include <GFSDK_Aftermath_Defines.h>
-#include <GFSDK_Aftermath_GpuCrashDump.h>
-#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h>
-#endif
-
-#include "common/common_types.h"
-#include "common/dynamic_library.h"
-
-namespace Vulkan {
-
-class NsightAftermathTracker {
-public:
- NsightAftermathTracker();
- ~NsightAftermathTracker();
-
- NsightAftermathTracker(const NsightAftermathTracker&) = delete;
- NsightAftermathTracker& operator=(const NsightAftermathTracker&) = delete;
-
- // Delete move semantics because Aftermath initialization uses a pointer to this.
- NsightAftermathTracker(NsightAftermathTracker&&) = delete;
- NsightAftermathTracker& operator=(NsightAftermathTracker&&) = delete;
-
- bool Initialize();
-
- void SaveShader(const std::vector<u32>& spirv) const;
-
-private:
-#ifdef HAS_NSIGHT_AFTERMATH
- static void GpuCrashDumpCallback(const void* gpu_crash_dump, u32 gpu_crash_dump_size,
- void* user_data);
-
- static void ShaderDebugInfoCallback(const void* shader_debug_info, u32 shader_debug_info_size,
- void* user_data);
-
- static void CrashDumpDescriptionCallback(
- PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description, void* user_data);
-
- void OnGpuCrashDumpCallback(const void* gpu_crash_dump, u32 gpu_crash_dump_size);
-
- void OnShaderDebugInfoCallback(const void* shader_debug_info, u32 shader_debug_info_size);
-
- void OnCrashDumpDescriptionCallback(
- PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description);
-
- mutable std::mutex mutex;
-
- std::string dump_dir;
- int dump_id = 0;
-
- bool initialized = false;
-
- Common::DynamicLibrary dl;
- PFN_GFSDK_Aftermath_DisableGpuCrashDumps GFSDK_Aftermath_DisableGpuCrashDumps;
- PFN_GFSDK_Aftermath_EnableGpuCrashDumps GFSDK_Aftermath_EnableGpuCrashDumps;
- PFN_GFSDK_Aftermath_GetShaderDebugInfoIdentifier GFSDK_Aftermath_GetShaderDebugInfoIdentifier;
- PFN_GFSDK_Aftermath_GetShaderHashSpirv GFSDK_Aftermath_GetShaderHashSpirv;
- PFN_GFSDK_Aftermath_GpuCrashDump_CreateDecoder GFSDK_Aftermath_GpuCrashDump_CreateDecoder;
- PFN_GFSDK_Aftermath_GpuCrashDump_DestroyDecoder GFSDK_Aftermath_GpuCrashDump_DestroyDecoder;
- PFN_GFSDK_Aftermath_GpuCrashDump_GenerateJSON GFSDK_Aftermath_GpuCrashDump_GenerateJSON;
- PFN_GFSDK_Aftermath_GpuCrashDump_GetJSON GFSDK_Aftermath_GpuCrashDump_GetJSON;
-#endif
-};
-
-#ifndef HAS_NSIGHT_AFTERMATH
-inline NsightAftermathTracker::NsightAftermathTracker() = default;
-inline NsightAftermathTracker::~NsightAftermathTracker() = default;
-inline bool NsightAftermathTracker::Initialize() {
- return false;
-}
-inline void NsightAftermathTracker::SaveShader(const std::vector<u32>&) const {}
-#endif
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index ae46e0444..1cc720ddd 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -12,8 +12,6 @@
#include <fmt/format.h>
-#include "common/dynamic_library.h"
-#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/telemetry.h"
#include "core/core.h"
@@ -24,179 +22,27 @@
#include "video_core/gpu.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-// Include these late to avoid polluting previous headers
-#ifdef _WIN32
-#include <windows.h>
-// ensure include order
-#include <vulkan/vulkan_win32.h>
-#endif
-
-#if !defined(_WIN32) && !defined(__APPLE__)
-#include <X11/Xlib.h>
-#include <vulkan/vulkan_wayland.h>
-#include <vulkan/vulkan_xlib.h>
-#endif
+#include "video_core/vulkan_common/vulkan_debug_callback.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_instance.h"
+#include "video_core/vulkan_common/vulkan_library.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_surface.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-
namespace {
-
-using Core::Frontend::WindowSystemType;
-
-VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
- VkDebugUtilsMessageTypeFlagsEXT type,
- const VkDebugUtilsMessengerCallbackDataEXT* data,
- [[maybe_unused]] void* user_data) {
- const char* message{data->pMessage};
-
- if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
- LOG_CRITICAL(Render_Vulkan, "{}", message);
- } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
- LOG_WARNING(Render_Vulkan, "{}", message);
- } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
- LOG_INFO(Render_Vulkan, "{}", message);
- } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
- LOG_DEBUG(Render_Vulkan, "{}", message);
- }
- return VK_FALSE;
-}
-
-Common::DynamicLibrary OpenVulkanLibrary() {
- Common::DynamicLibrary library;
-#ifdef __APPLE__
- // Check if a path to a specific Vulkan library has been specified.
- char* libvulkan_env = getenv("LIBVULKAN_PATH");
- if (!libvulkan_env || !library.Open(libvulkan_env)) {
- // Use the libvulkan.dylib from the application bundle.
- const std::string filename =
- Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib";
- library.Open(filename.c_str());
- }
-#else
- std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
- if (!library.Open(filename.c_str())) {
- // Android devices may not have libvulkan.so.1, only libvulkan.so.
- filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
- library.Open(filename.c_str());
- }
-#endif
- return library;
-}
-
-vk::Instance CreateInstance(Common::DynamicLibrary& library, vk::InstanceDispatch& dld,
- WindowSystemType window_type = WindowSystemType::Headless,
- bool enable_layers = false) {
- if (!library.IsOpen()) {
- LOG_ERROR(Render_Vulkan, "Vulkan library not available");
- return {};
- }
- if (!library.GetSymbol("vkGetInstanceProcAddr", &dld.vkGetInstanceProcAddr)) {
- LOG_ERROR(Render_Vulkan, "vkGetInstanceProcAddr not present in Vulkan");
- return {};
- }
- if (!vk::Load(dld)) {
- LOG_ERROR(Render_Vulkan, "Failed to load Vulkan function pointers");
- return {};
- }
-
- std::vector<const char*> extensions;
- extensions.reserve(6);
- switch (window_type) {
- case Core::Frontend::WindowSystemType::Headless:
- break;
-#ifdef _WIN32
- case Core::Frontend::WindowSystemType::Windows:
- extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
- break;
-#endif
-#if !defined(_WIN32) && !defined(__APPLE__)
- case Core::Frontend::WindowSystemType::X11:
- extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
- break;
- case Core::Frontend::WindowSystemType::Wayland:
- extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
- break;
-#endif
- default:
- LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
- break;
- }
- if (window_type != Core::Frontend::WindowSystemType::Headless) {
- extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
- }
- if (enable_layers) {
- extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
- }
- extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
-
- const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld);
- if (!properties) {
- LOG_ERROR(Render_Vulkan, "Failed to query extension properties");
- return {};
- }
-
- for (const char* extension : extensions) {
- const auto it =
- std::find_if(properties->begin(), properties->end(), [extension](const auto& prop) {
- return !std::strcmp(extension, prop.extensionName);
- });
- if (it == properties->end()) {
- LOG_ERROR(Render_Vulkan, "Required instance extension {} is not available", extension);
- return {};
- }
- }
-
- std::vector<const char*> layers;
- layers.reserve(1);
- if (enable_layers) {
- layers.push_back("VK_LAYER_KHRONOS_validation");
- }
-
- const std::optional layer_properties = vk::EnumerateInstanceLayerProperties(dld);
- if (!layer_properties) {
- LOG_ERROR(Render_Vulkan, "Failed to query layer properties, disabling layers");
- layers.clear();
- }
-
- for (auto layer_it = layers.begin(); layer_it != layers.end();) {
- const char* const layer = *layer_it;
- const auto it = std::find_if(
- layer_properties->begin(), layer_properties->end(),
- [layer](const VkLayerProperties& prop) { return !std::strcmp(layer, prop.layerName); });
- if (it == layer_properties->end()) {
- LOG_ERROR(Render_Vulkan, "Layer {} not available, removing it", layer);
- layer_it = layers.erase(layer_it);
- } else {
- ++layer_it;
- }
- }
-
- vk::Instance instance = vk::Instance::Create(layers, extensions, dld);
- if (!instance) {
- LOG_ERROR(Render_Vulkan, "Failed to create Vulkan instance");
- return {};
- }
- if (!vk::Load(*instance, dld)) {
- LOG_ERROR(Render_Vulkan, "Failed to load Vulkan instance function pointers");
- }
- return instance;
-}
-
std::string GetReadableVersion(u32 version) {
return fmt::format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version),
VK_VERSION_PATCH(version));
}
-std::string GetDriverVersion(const VKDevice& device) {
+std::string GetDriverVersion(const Device& device) {
// Extracted from
// https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5dddea46ea1120b0df14eef8f15ff8e318e35462/functions.php#L308-L314
const u32 version = device.GetDriverVersion();
@@ -213,7 +59,6 @@ std::string GetDriverVersion(const VKDevice& device) {
const u32 minor = version & 0x3fff;
return fmt::format("{}.{}", major, minor);
}
-
return GetReadableVersion(version);
}
@@ -235,213 +80,98 @@ std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_ext
return separated_extensions;
}
+Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
+ VkSurfaceKHR surface) {
+ const std::vector<VkPhysicalDevice> devices = instance.EnumeratePhysicalDevices();
+ const s32 device_index = Settings::values.vulkan_device.GetValue();
+ if (device_index < 0 || device_index >= static_cast<s32>(devices.size())) {
+ LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ const vk::PhysicalDevice physical_device(devices[device_index], dld);
+ return Device(*instance, physical_device, surface, dld);
+}
} // Anonymous namespace
-RendererVulkan::RendererVulkan(Core::System& system_, Core::Frontend::EmuWindow& emu_window,
- Tegra::GPU& gpu_,
- std::unique_ptr<Core::Frontend::GraphicsContext> context)
- : RendererBase{emu_window, std::move(context)}, system{system_}, gpu{gpu_} {}
+RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
+ Core::Frontend::EmuWindow& emu_window,
+ Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context_) try
+ : RendererBase(emu_window, std::move(context_)),
+ telemetry_session(telemetry_session_),
+ cpu_memory(cpu_memory_),
+ gpu(gpu_),
+ library(OpenLibrary()),
+ instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
+ true, Settings::values.renderer_debug)),
+ debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr),
+ surface(CreateSurface(instance, render_window)),
+ device(CreateDevice(instance, dld, *surface)),
+ memory_allocator(device, false),
+ state_tracker(gpu),
+ scheduler(device, state_tracker),
+ swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,
+ render_window.GetFramebufferLayout().height, false),
+ blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler,
+ screen_info),
+ rasterizer(render_window, gpu, gpu.MemoryManager(), cpu_memory, screen_info, device,
+ memory_allocator, state_tracker, scheduler) {
+ Report();
+} catch (const vk::Exception& exception) {
+ LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
+ throw std::runtime_error{fmt::format("Vulkan initialization error {}", exception.what())};
+}
RendererVulkan::~RendererVulkan() {
- ShutDown();
+ void(device.GetLogical().WaitIdle());
}
void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
- render_window.PollEvents();
-
if (!framebuffer) {
return;
}
-
const auto& layout = render_window.GetFramebufferLayout();
if (layout.width > 0 && layout.height > 0 && render_window.IsShown()) {
const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
const bool use_accelerated =
- rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
+ rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
const bool is_srgb = use_accelerated && screen_info.is_srgb;
- if (swapchain->HasFramebufferChanged(layout) || swapchain->GetSrgbState() != is_srgb) {
- swapchain->Create(layout.width, layout.height, is_srgb);
- blit_screen->Recreate();
- }
-
- scheduler->WaitWorker();
-
- swapchain->AcquireNextImage();
- const auto [fence, render_semaphore] = blit_screen->Draw(*framebuffer, use_accelerated);
-
- scheduler->Flush(false, render_semaphore);
-
- if (swapchain->Present(render_semaphore, fence)) {
- blit_screen->Recreate();
+ if (swapchain.HasFramebufferChanged(layout) || swapchain.GetSrgbState() != is_srgb) {
+ swapchain.Create(layout.width, layout.height, is_srgb);
+ blit_screen.Recreate();
}
- rasterizer->TickFrame();
- }
-
- render_window.PollEvents();
-}
-
-bool RendererVulkan::TryPresent(int /*timeout_ms*/) {
- // TODO (bunnei): ImplementMe
- return true;
-}
-
-bool RendererVulkan::Init() {
- library = OpenVulkanLibrary();
- instance = CreateInstance(library, dld, render_window.GetWindowInfo().type,
- Settings::values.renderer_debug);
- if (!instance || !CreateDebugCallback() || !CreateSurface() || !PickDevices()) {
- return false;
- }
-
- Report();
-
- memory_manager = std::make_unique<VKMemoryManager>(*device);
-
- resource_manager = std::make_unique<VKResourceManager>(*device);
-
- const auto& framebuffer = render_window.GetFramebufferLayout();
- swapchain = std::make_unique<VKSwapchain>(*surface, *device);
- swapchain->Create(framebuffer.width, framebuffer.height, false);
-
- state_tracker = std::make_unique<StateTracker>(system);
-
- scheduler = std::make_unique<VKScheduler>(*device, *resource_manager, *state_tracker);
-
- rasterizer = std::make_unique<RasterizerVulkan>(system, render_window, screen_info, *device,
- *resource_manager, *memory_manager,
- *state_tracker, *scheduler);
-
- blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device,
- *resource_manager, *memory_manager, *swapchain,
- *scheduler, screen_info);
-
- return true;
-}
-
-void RendererVulkan::ShutDown() {
- if (!device) {
- return;
- }
- if (const auto& dev = device->GetLogical()) {
- dev.WaitIdle();
- }
-
- rasterizer.reset();
- blit_screen.reset();
- scheduler.reset();
- swapchain.reset();
- memory_manager.reset();
- resource_manager.reset();
- device.reset();
-}
+ scheduler.WaitWorker();
-bool RendererVulkan::CreateDebugCallback() {
- if (!Settings::values.renderer_debug) {
- return true;
- }
- debug_callback = instance.TryCreateDebugCallback(DebugCallback);
- if (!debug_callback) {
- LOG_ERROR(Render_Vulkan, "Failed to create debug callback");
- return false;
- }
- return true;
-}
+ swapchain.AcquireNextImage();
+ const VkSemaphore render_semaphore = blit_screen.Draw(*framebuffer, use_accelerated);
-bool RendererVulkan::CreateSurface() {
- [[maybe_unused]] const auto& window_info = render_window.GetWindowInfo();
- VkSurfaceKHR unsafe_surface = nullptr;
+ scheduler.Flush(render_semaphore);
-#ifdef _WIN32
- if (window_info.type == Core::Frontend::WindowSystemType::Windows) {
- const HWND hWnd = static_cast<HWND>(window_info.render_surface);
- const VkWin32SurfaceCreateInfoKHR win32_ci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
- nullptr, 0, nullptr, hWnd};
- const auto vkCreateWin32SurfaceKHR = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>(
- dld.vkGetInstanceProcAddr(*instance, "vkCreateWin32SurfaceKHR"));
- if (!vkCreateWin32SurfaceKHR ||
- vkCreateWin32SurfaceKHR(*instance, &win32_ci, nullptr, &unsafe_surface) != VK_SUCCESS) {
- LOG_ERROR(Render_Vulkan, "Failed to initialize Win32 surface");
- return false;
+ if (swapchain.Present(render_semaphore)) {
+ blit_screen.Recreate();
}
- }
-#endif
-#if !defined(_WIN32) && !defined(__APPLE__)
- if (window_info.type == Core::Frontend::WindowSystemType::X11) {
- const VkXlibSurfaceCreateInfoKHR xlib_ci{
- VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0,
- static_cast<Display*>(window_info.display_connection),
- reinterpret_cast<Window>(window_info.render_surface)};
- const auto vkCreateXlibSurfaceKHR = reinterpret_cast<PFN_vkCreateXlibSurfaceKHR>(
- dld.vkGetInstanceProcAddr(*instance, "vkCreateXlibSurfaceKHR"));
- if (!vkCreateXlibSurfaceKHR ||
- vkCreateXlibSurfaceKHR(*instance, &xlib_ci, nullptr, &unsafe_surface) != VK_SUCCESS) {
- LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface");
- return false;
- }
- }
- if (window_info.type == Core::Frontend::WindowSystemType::Wayland) {
- const VkWaylandSurfaceCreateInfoKHR wayland_ci{
- VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
- static_cast<wl_display*>(window_info.display_connection),
- static_cast<wl_surface*>(window_info.render_surface)};
- const auto vkCreateWaylandSurfaceKHR = reinterpret_cast<PFN_vkCreateWaylandSurfaceKHR>(
- dld.vkGetInstanceProcAddr(*instance, "vkCreateWaylandSurfaceKHR"));
- if (!vkCreateWaylandSurfaceKHR ||
- vkCreateWaylandSurfaceKHR(*instance, &wayland_ci, nullptr, &unsafe_surface) !=
- VK_SUCCESS) {
- LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface");
- return false;
- }
- }
-#endif
- if (!unsafe_surface) {
- LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
- return false;
+ rasterizer.TickFrame();
}
- surface = vk::SurfaceKHR(unsafe_surface, *instance, dld);
- return true;
-}
-
-bool RendererVulkan::PickDevices() {
- const auto devices = instance.EnumeratePhysicalDevices();
- if (!devices) {
- LOG_ERROR(Render_Vulkan, "Failed to enumerate physical devices");
- return false;
- }
-
- const s32 device_index = Settings::values.vulkan_device.GetValue();
- if (device_index < 0 || device_index >= static_cast<s32>(devices->size())) {
- LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
- return false;
- }
- const vk::PhysicalDevice physical_device((*devices)[static_cast<std::size_t>(device_index)],
- dld);
- if (!VKDevice::IsSuitable(physical_device, *surface)) {
- return false;
- }
-
- device = std::make_unique<VKDevice>(*instance, physical_device, *surface, dld);
- return device->Create();
+ render_window.OnFrameDisplayed();
}
void RendererVulkan::Report() const {
- const std::string vendor_name{device->GetVendorName()};
- const std::string model_name{device->GetModelName()};
- const std::string driver_version = GetDriverVersion(*device);
+ const std::string vendor_name{device.GetVendorName()};
+ const std::string model_name{device.GetModelName()};
+ const std::string driver_version = GetDriverVersion(device);
const std::string driver_name = fmt::format("{} {}", vendor_name, driver_version);
- const std::string api_version = GetReadableVersion(device->GetApiVersion());
+ const std::string api_version = GetReadableVersion(device.ApiVersion());
- const std::string extensions = BuildCommaSeparatedExtensions(device->GetAvailableExtensions());
+ const std::string extensions = BuildCommaSeparatedExtensions(device.GetAvailableExtensions());
LOG_INFO(Render_Vulkan, "Driver: {}", driver_name);
LOG_INFO(Render_Vulkan, "Device: {}", model_name);
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
- auto& telemetry_session = system.TelemetrySession();
- constexpr auto field = Common::Telemetry::FieldType::UserSystem;
+ static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
telemetry_session.AddField(field, "GPU_Model", model_name);
telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
@@ -449,25 +179,4 @@ void RendererVulkan::Report() const {
telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
}
-std::vector<std::string> RendererVulkan::EnumerateDevices() {
- vk::InstanceDispatch dld;
- Common::DynamicLibrary library = OpenVulkanLibrary();
- vk::Instance instance = CreateInstance(library, dld);
- if (!instance) {
- return {};
- }
-
- const std::optional physical_devices = instance.EnumeratePhysicalDevices();
- if (!physical_devices) {
- return {};
- }
-
- std::vector<std::string> names;
- names.reserve(physical_devices->size());
- for (const auto& device : *physical_devices) {
- names.push_back(vk::PhysicalDevice(device, dld).GetProperties().deviceName);
- }
- return names;
-}
-
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 13debbbc0..72071316c 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -9,75 +9,67 @@
#include <vector>
#include "common/dynamic_library.h"
-
#include "video_core/renderer_base.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/renderer_vulkan/vk_blit_screen.h"
+#include "video_core/renderer_vulkan/vk_rasterizer.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/renderer_vulkan/vk_state_tracker.h"
+#include "video_core/renderer_vulkan/vk_swapchain.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Core {
-class System;
+class TelemetrySession;
}
-namespace Vulkan {
+namespace Core::Memory {
+class Memory;
+}
-class StateTracker;
-class VKBlitScreen;
-class VKDevice;
-class VKFence;
-class VKMemoryManager;
-class VKResourceManager;
-class VKSwapchain;
-class VKScheduler;
-class VKImage;
-
-struct VKScreenInfo {
- VKImage* image{};
- u32 width{};
- u32 height{};
- bool is_srgb{};
-};
+namespace Tegra {
+class GPU;
+}
+
+namespace Vulkan {
class RendererVulkan final : public VideoCore::RendererBase {
public:
- explicit RendererVulkan(Core::System& system, Core::Frontend::EmuWindow& emu_window,
- Tegra::GPU& gpu,
- std::unique_ptr<Core::Frontend::GraphicsContext> context);
+ explicit RendererVulkan(Core::TelemetrySession& telemtry_session,
+ Core::Frontend::EmuWindow& emu_window,
+ Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
+ std::unique_ptr<Core::Frontend::GraphicsContext> context_);
~RendererVulkan() override;
- bool Init() override;
- void ShutDown() override;
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
- bool TryPresent(int timeout_ms) override;
- static std::vector<std::string> EnumerateDevices();
+ VideoCore::RasterizerInterface* ReadRasterizer() override {
+ return &rasterizer;
+ }
private:
- bool CreateDebugCallback();
-
- bool CreateSurface();
-
- bool PickDevices();
-
void Report() const;
- Core::System& system;
+ Core::TelemetrySession& telemetry_session;
+ Core::Memory::Memory& cpu_memory;
Tegra::GPU& gpu;
Common::DynamicLibrary library;
vk::InstanceDispatch dld;
vk::Instance instance;
+ vk::DebugUtilsMessenger debug_callback;
vk::SurfaceKHR surface;
VKScreenInfo screen_info;
- vk::DebugCallback debug_callback;
- std::unique_ptr<VKDevice> device;
- std::unique_ptr<VKSwapchain> swapchain;
- std::unique_ptr<VKMemoryManager> memory_manager;
- std::unique_ptr<VKResourceManager> resource_manager;
- std::unique_ptr<StateTracker> state_tracker;
- std::unique_ptr<VKScheduler> scheduler;
- std::unique_ptr<VKBlitScreen> blit_screen;
+ Device device;
+ MemoryAllocator memory_allocator;
+ StateTracker state_tracker;
+ VKScheduler scheduler;
+ VKSwapchain swapchain;
+ VKBlitScreen blit_screen;
+ RasterizerVulkan rasterizer;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/shaders/blit.frag b/src/video_core/renderer_vulkan/shaders/blit.frag
deleted file mode 100644
index a06ecd24a..000000000
--- a/src/video_core/renderer_vulkan/shaders/blit.frag
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-/*
- * Build instructions:
- * $ glslangValidator -V $THIS_FILE -o output.spv
- * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
- * $ xxd -i optimized.spv
- *
- * Then copy that bytecode to the C++ file
- */
-
-#version 460 core
-
-layout (location = 0) in vec2 frag_tex_coord;
-
-layout (location = 0) out vec4 color;
-
-layout (binding = 1) uniform sampler2D color_texture;
-
-void main() {
- color = texture(color_texture, frag_tex_coord);
-}
diff --git a/src/video_core/renderer_vulkan/shaders/blit.vert b/src/video_core/renderer_vulkan/shaders/blit.vert
deleted file mode 100644
index c64d9235a..000000000
--- a/src/video_core/renderer_vulkan/shaders/blit.vert
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-/*
- * Build instructions:
- * $ glslangValidator -V $THIS_FILE -o output.spv
- * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
- * $ xxd -i optimized.spv
- *
- * Then copy that bytecode to the C++ file
- */
-
-#version 460 core
-
-layout (location = 0) in vec2 vert_position;
-layout (location = 1) in vec2 vert_tex_coord;
-
-layout (location = 0) out vec2 frag_tex_coord;
-
-layout (set = 0, binding = 0) uniform MatrixBlock {
- mat4 modelview_matrix;
-};
-
-void main() {
- gl_Position = modelview_matrix * vec4(vert_position, 0.0, 1.0);
- frag_tex_coord = vert_tex_coord;
-}
diff --git a/src/video_core/renderer_vulkan/shaders/quad_array.comp b/src/video_core/renderer_vulkan/shaders/quad_array.comp
deleted file mode 100644
index 5a5703308..000000000
--- a/src/video_core/renderer_vulkan/shaders/quad_array.comp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-/*
- * Build instructions:
- * $ glslangValidator -V $THIS_FILE -o output.spv
- * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
- * $ xxd -i optimized.spv
- *
- * Then copy that bytecode to the C++ file
- */
-
-#version 460 core
-
-layout (local_size_x = 1024) in;
-
-layout (std430, set = 0, binding = 0) buffer OutputBuffer {
- uint output_indexes[];
-};
-
-layout (push_constant) uniform PushConstants {
- uint first;
-};
-
-void main() {
- uint primitive = gl_GlobalInvocationID.x;
- if (primitive * 6 >= output_indexes.length()) {
- return;
- }
-
- const uint quad_map[6] = uint[](0, 1, 2, 0, 2, 3);
- for (uint vertex = 0; vertex < 6; ++vertex) {
- uint index = first + primitive * 4 + quad_map[vertex];
- output_indexes[primitive * 6 + vertex] = index;
- }
-}
diff --git a/src/video_core/renderer_vulkan/shaders/quad_indexed.comp b/src/video_core/renderer_vulkan/shaders/quad_indexed.comp
deleted file mode 100644
index 5a472ba9b..000000000
--- a/src/video_core/renderer_vulkan/shaders/quad_indexed.comp
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-/*
- * Build instructions:
- * $ glslangValidator -V quad_indexed.comp -o output.spv
- * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
- * $ xxd -i optimized.spv
- *
- * Then copy that bytecode to the C++ file
- */
-
-#version 460 core
-
-layout (local_size_x = 1024) in;
-
-layout (std430, set = 0, binding = 0) readonly buffer InputBuffer {
- uint input_indexes[];
-};
-
-layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
- uint output_indexes[];
-};
-
-layout (push_constant) uniform PushConstants {
- uint base_vertex;
- int index_shift; // 0: uint8, 1: uint16, 2: uint32
-};
-
-void main() {
- int primitive = int(gl_GlobalInvocationID.x);
- if (primitive * 6 >= output_indexes.length()) {
- return;
- }
-
- int index_size = 8 << index_shift;
- int flipped_shift = 2 - index_shift;
- int mask = (1 << flipped_shift) - 1;
-
- const int quad_swizzle[6] = int[](0, 1, 2, 0, 2, 3);
- for (uint vertex = 0; vertex < 6; ++vertex) {
- int offset = primitive * 4 + quad_swizzle[vertex];
- int int_offset = offset >> flipped_shift;
- int bit_offset = (offset & mask) * index_size;
- uint packed_input = input_indexes[int_offset];
- uint index = bitfieldExtract(packed_input, bit_offset, index_size);
- output_indexes[primitive * 6 + vertex] = index + base_vertex;
- }
-}
diff --git a/src/video_core/renderer_vulkan/shaders/uint8.comp b/src/video_core/renderer_vulkan/shaders/uint8.comp
deleted file mode 100644
index a320f3ae0..000000000
--- a/src/video_core/renderer_vulkan/shaders/uint8.comp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-/*
- * Build instructions:
- * $ glslangValidator -V $THIS_FILE -o output.spv
- * $ spirv-opt -O --strip-debug output.spv -o optimized.spv
- * $ xxd -i optimized.spv
- *
- * Then copy that bytecode to the C++ file
- */
-
-#version 460 core
-#extension GL_EXT_shader_16bit_storage : require
-#extension GL_EXT_shader_8bit_storage : require
-
-layout (local_size_x = 1024) in;
-
-layout (std430, set = 0, binding = 0) readonly buffer InputBuffer {
- uint8_t input_indexes[];
-};
-
-layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
- uint16_t output_indexes[];
-};
-
-void main() {
- uint id = gl_GlobalInvocationID.x;
- if (id < input_indexes.length()) {
- output_indexes[id] = uint16_t(input_indexes[id]);
- }
-}
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index a551e3de8..a1a32aabe 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -12,127 +12,28 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/math_util.h"
-
#include "core/core.h"
#include "core/frontend/emu_window.h"
#include "core/memory.h"
-
#include "video_core/gpu.h"
-#include "video_core/morton.h"
-#include "video_core/rasterizer_interface.h"
+#include "video_core/host_shaders/vulkan_present_frag_spv.h"
+#include "video_core/host_shaders/vulkan_present_vert_spv.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_image.h"
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
-#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/surface.h"
+#include "video_core/textures/decoders.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
namespace {
-// Generated from the "shaders/" directory, read the instructions there.
-constexpr u8 blit_vertex_code[] = {
- 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
- 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
- 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x25, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x25, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x20, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x1d, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x91, 0x00, 0x05, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
- 0x41, 0x00, 0x05, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x3d, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0x3e, 0x00, 0x03, 0x00, 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00,
- 0x38, 0x00, 0x01, 0x00};
-
-constexpr u8 blit_fragment_code[] = {
- 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
- 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
- 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
- 0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
-
struct ScreenRectVertex {
ScreenRectVertex() = default;
explicit ScreenRectVertex(f32 x, f32 y, f32 u, f32 v) : position{{x, y}}, tex_coord{{u, v}} {}
@@ -175,9 +76,9 @@ constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
// clang-format on
}
-std::size_t GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
+u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
using namespace VideoCore::Surface;
- return GetBytesPerPixel(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
+ return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
}
std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
@@ -210,17 +111,14 @@ struct VKBlitScreen::BufferData {
// Unaligned image data goes here
};
-VKBlitScreen::VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
- VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKSwapchain& swapchain, VKScheduler& scheduler,
- const VKScreenInfo& screen_info)
- : system{system}, render_window{render_window}, rasterizer{rasterizer}, device{device},
- resource_manager{resource_manager}, memory_manager{memory_manager}, swapchain{swapchain},
- scheduler{scheduler}, image_count{swapchain.GetImageCount()}, screen_info{screen_info} {
- watches.resize(image_count);
- std::generate(watches.begin(), watches.end(),
- []() { return std::make_unique<VKFenceWatch>(); });
+VKBlitScreen::VKBlitScreen(Core::Memory::Memory& cpu_memory_,
+ Core::Frontend::EmuWindow& render_window_, const Device& device_,
+ MemoryAllocator& memory_allocator_, VKSwapchain& swapchain_,
+ VKScheduler& scheduler_, const VKScreenInfo& screen_info_)
+ : cpu_memory{cpu_memory_}, render_window{render_window_}, device{device_},
+ memory_allocator{memory_allocator_}, swapchain{swapchain_}, scheduler{scheduler_},
+ image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
+ resource_ticks.resize(image_count);
CreateStaticResources();
CreateDynamicResources();
@@ -232,44 +130,40 @@ void VKBlitScreen::Recreate() {
CreateDynamicResources();
}
-std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
- bool use_accelerated) {
+VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated) {
RefreshResources(framebuffer);
// Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext();
const std::size_t image_index = swapchain.GetImageIndex();
- watches[image_index]->Watch(scheduler.GetFence());
- VKImage* blit_image = use_accelerated ? screen_info.image : raw_images[image_index].get();
+ scheduler.Wait(resource_ticks[image_index]);
+ resource_ticks[image_index] = scheduler.CurrentTick();
- UpdateDescriptorSet(image_index, blit_image->GetPresentView());
+ UpdateDescriptorSet(image_index,
+ use_accelerated ? screen_info.image_view : *raw_image_views[image_index]);
BufferData data;
SetUniformData(data, framebuffer);
SetVertexData(data, framebuffer);
- auto map = buffer_commit->Map();
- std::memcpy(map.GetAddress(), &data, sizeof(data));
+ const std::span<u8> mapped_span = buffer_commit.Map();
+ std::memcpy(mapped_span.data(), &data, sizeof(data));
if (!use_accelerated) {
const u64 image_offset = GetRawImageOffset(framebuffer, image_index);
- const auto pixel_format =
- VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format);
const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
- const auto host_ptr = system.Memory().GetPointer(framebuffer_addr);
- rasterizer.FlushRegion(ToCacheAddr(host_ptr), GetSizeInBytes(framebuffer));
+ const u8* const host_ptr = cpu_memory.GetPointer(framebuffer_addr);
+ const size_t size_bytes = GetSizeInBytes(framebuffer);
// TODO(Rodrigo): Read this from HLE
constexpr u32 block_height_log2 = 4;
- VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format,
- framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1,
- map.GetAddress() + image_offset, host_ptr);
-
- blit_image->Transition(0, 1, 0, 1, VK_PIPELINE_STAGE_TRANSFER_BIT,
- VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer);
+ Tegra::Texture::UnswizzleTexture(
+ mapped_span.subspan(image_offset, size_bytes), std::span(host_ptr, size_bytes),
+ bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
const VkBufferImageCopy copy{
.bufferOffset = image_offset,
@@ -291,15 +185,42 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
},
};
scheduler.Record(
- [buffer = *buffer, image = *blit_image->GetHandle(), copy](vk::CommandBuffer cmdbuf) {
- cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
+ [buffer = *buffer, image = *raw_images[image_index], copy](vk::CommandBuffer cmdbuf) {
+ const VkImageMemoryBarrier base_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = 0,
+ .dstAccessMask = 0,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image,
+ .subresourceRange =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ };
+ VkImageMemoryBarrier read_barrier = base_barrier;
+ read_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
+ read_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ read_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+ VkImageMemoryBarrier write_barrier = base_barrier;
+ write_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ write_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, read_barrier);
+ cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_GENERAL, copy);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier);
});
}
- map.Release();
-
- blit_image->Transition(0, 1, 0, 1, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
- VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-
scheduler.Record([renderpass = *renderpass, framebuffer = *framebuffers[image_index],
descriptor_set = descriptor_sets[image_index], buffer = *buffer,
size = swapchain.GetSize(), pipeline = *pipeline,
@@ -307,31 +228,31 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
const VkClearValue clear_color{
.color = {.float32 = {0.0f, 0.0f, 0.0f, 0.0f}},
};
-
- VkRenderPassBeginInfo renderpass_bi;
- renderpass_bi.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
- renderpass_bi.pNext = nullptr;
- renderpass_bi.renderPass = renderpass;
- renderpass_bi.framebuffer = framebuffer;
- renderpass_bi.renderArea.offset.x = 0;
- renderpass_bi.renderArea.offset.y = 0;
- renderpass_bi.renderArea.extent = size;
- renderpass_bi.clearValueCount = 1;
- renderpass_bi.pClearValues = &clear_color;
-
- VkViewport viewport;
- viewport.x = 0.0f;
- viewport.y = 0.0f;
- viewport.width = static_cast<float>(size.width);
- viewport.height = static_cast<float>(size.height);
- viewport.minDepth = 0.0f;
- viewport.maxDepth = 1.0f;
-
- VkRect2D scissor;
- scissor.offset.x = 0;
- scissor.offset.y = 0;
- scissor.extent = size;
-
+ const VkRenderPassBeginInfo renderpass_bi{
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .pNext = nullptr,
+ .renderPass = renderpass,
+ .framebuffer = framebuffer,
+ .renderArea =
+ {
+ .offset = {0, 0},
+ .extent = size,
+ },
+ .clearValueCount = 1,
+ .pClearValues = &clear_color,
+ };
+ const VkViewport viewport{
+ .x = 0.0f,
+ .y = 0.0f,
+ .width = static_cast<float>(size.width),
+ .height = static_cast<float>(size.height),
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f,
+ };
+ const VkRect2D scissor{
+ .offset = {0, 0},
+ .extent = size,
+ };
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.SetViewport(0, viewport);
@@ -342,8 +263,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
cmdbuf.Draw(4, 1, 0, 0);
cmdbuf.EndRenderPass();
});
-
- return {scheduler.GetFence(), *semaphores[image_index]};
+ return *semaphores[image_index];
}
void VKBlitScreen::CreateStaticResources() {
@@ -375,8 +295,8 @@ void VKBlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer)
}
void VKBlitScreen::CreateShaders() {
- vertex_shader = BuildShader(device, sizeof(blit_vertex_code), blit_vertex_code);
- fragment_shader = BuildShader(device, sizeof(blit_fragment_code), blit_fragment_code);
+ vertex_shader = BuildShader(device, VULKAN_PRESENT_VERT_SPV);
+ fragment_shader = BuildShader(device, VULKAN_PRESENT_FRAG_SPV);
}
void VKBlitScreen::CreateSemaphores() {
@@ -423,7 +343,7 @@ void VKBlitScreen::CreateRenderPass() {
const VkAttachmentReference color_attachment_ref{
.attachment = 0,
- .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ .layout = VK_IMAGE_LAYOUT_GENERAL,
};
const VkSubpassDescription subpass_description{
@@ -711,12 +631,12 @@ void VKBlitScreen::CreateFramebuffers() {
void VKBlitScreen::ReleaseRawImages() {
for (std::size_t i = 0; i < raw_images.size(); ++i) {
- watches[i]->Wait();
+ scheduler.Wait(resource_ticks.at(i));
}
raw_images.clear();
raw_buffer_commits.clear();
buffer.reset();
- buffer_commit.reset();
+ buffer_commit = MemoryCommit{};
}
void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) {
@@ -733,39 +653,61 @@ void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuff
};
buffer = device.GetLogical().CreateBuffer(ci);
- buffer_commit = memory_manager.Commit(buffer, true);
+ buffer_commit = memory_allocator.Commit(buffer, MemoryUsage::Upload);
}
void VKBlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
raw_images.resize(image_count);
+ raw_image_views.resize(image_count);
raw_buffer_commits.resize(image_count);
- const VkImageCreateInfo ci{
- .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .imageType = VK_IMAGE_TYPE_2D,
- .format = GetFormat(framebuffer),
- .extent =
- {
- .width = framebuffer.width,
- .height = framebuffer.height,
- .depth = 1,
- },
- .mipLevels = 1,
- .arrayLayers = 1,
- .samples = VK_SAMPLE_COUNT_1_BIT,
- .tiling = VK_IMAGE_TILING_LINEAR,
- .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
- .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
- .queueFamilyIndexCount = 0,
- .pQueueFamilyIndices = nullptr,
- .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
- };
-
- for (std::size_t i = 0; i < image_count; ++i) {
- raw_images[i] = std::make_unique<VKImage>(device, scheduler, ci, VK_IMAGE_ASPECT_COLOR_BIT);
- raw_buffer_commits[i] = memory_manager.Commit(raw_images[i]->GetHandle(), false);
+ for (size_t i = 0; i < image_count; ++i) {
+ raw_images[i] = device.GetLogical().CreateImage(VkImageCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = GetFormat(framebuffer),
+ .extent =
+ {
+ .width = framebuffer.width,
+ .height = framebuffer.height,
+ .depth = 1,
+ },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_LINEAR,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ });
+ raw_buffer_commits[i] = memory_allocator.Commit(raw_images[i], MemoryUsage::DeviceLocal);
+ raw_image_views[i] = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = *raw_images[i],
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = GetFormat(framebuffer),
+ .components =
+ {
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ });
}
}
@@ -792,7 +734,7 @@ void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView imag
const VkDescriptorImageInfo image_info{
.sampler = *sampler,
.imageView = image_view,
- .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+ .imageLayout = VK_IMAGE_LAYOUT_GENERAL,
};
const VkWriteDescriptorSet sampler_write{
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 243640fab..5e3177685 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -5,16 +5,18 @@
#pragma once
#include <memory>
-#include <tuple>
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Core {
class System;
}
+namespace Core::Memory {
+class Memory;
+}
+
namespace Core::Frontend {
class EmuWindow;
}
@@ -30,26 +32,31 @@ class RasterizerInterface;
namespace Vulkan {
struct ScreenInfo;
+
+class Device;
class RasterizerVulkan;
-class VKDevice;
-class VKFence;
-class VKImage;
class VKScheduler;
class VKSwapchain;
-class VKBlitScreen final {
+struct VKScreenInfo {
+ VkImageView image_view{};
+ u32 width{};
+ u32 height{};
+ bool is_srgb{};
+};
+
+class VKBlitScreen {
public:
- explicit VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
- VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKSwapchain& swapchain, VKScheduler& scheduler,
- const VKScreenInfo& screen_info);
+ explicit VKBlitScreen(Core::Memory::Memory& cpu_memory,
+ Core::Frontend::EmuWindow& render_window, const Device& device,
+ MemoryAllocator& memory_manager, VKSwapchain& swapchain,
+ VKScheduler& scheduler, const VKScreenInfo& screen_info);
~VKBlitScreen();
void Recreate();
- std::tuple<VKFence&, VkSemaphore> Draw(const Tegra::FramebufferConfig& framebuffer,
- bool use_accelerated);
+ [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer,
+ bool use_accelerated);
private:
struct BufferData;
@@ -81,12 +88,10 @@ private:
u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
std::size_t image_index) const;
- Core::System& system;
+ Core::Memory::Memory& cpu_memory;
Core::Frontend::EmuWindow& render_window;
- VideoCore::RasterizerInterface& rasterizer;
- const VKDevice& device;
- VKResourceManager& resource_manager;
- VKMemoryManager& memory_manager;
+ const Device& device;
+ MemoryAllocator& memory_allocator;
VKSwapchain& swapchain;
VKScheduler& scheduler;
const std::size_t image_count;
@@ -104,13 +109,14 @@ private:
vk::Sampler sampler;
vk::Buffer buffer;
- VKMemoryCommit buffer_commit;
+ MemoryCommit buffer_commit;
- std::vector<std::unique_ptr<VKFenceWatch>> watches;
+ std::vector<u64> resource_ticks;
std::vector<vk::Semaphore> semaphores;
- std::vector<std::unique_ptr<VKImage>> raw_images;
- std::vector<VKMemoryCommit> raw_buffer_commits;
+ std::vector<vk::Image> raw_images;
+ std::vector<vk::ImageView> raw_image_views;
+ std::vector<MemoryCommit> raw_buffer_commits;
u32 raw_width = 0;
u32 raw_height = 0;
};
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 1d2f8b557..848eedd66 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -3,172 +3,308 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <cstring>
-#include <memory>
+#include <span>
+#include <vector>
-#include "core/core.h"
#include "video_core/buffer_cache/buffer_cache.h"
+#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
-#include "video_core/renderer_vulkan/vk_stream_buffer.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
+#include "video_core/renderer_vulkan/vk_update_descriptor.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-
namespace {
+VkBufferCopy MakeBufferCopy(const VideoCommon::BufferCopy& copy) {
+ return VkBufferCopy{
+ .srcOffset = copy.src_offset,
+ .dstOffset = copy.dst_offset,
+ .size = copy.size,
+ };
+}
-constexpr VkBufferUsageFlags BUFFER_USAGE =
- VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
- VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
-
-constexpr VkPipelineStageFlags UPLOAD_PIPELINE_STAGE =
- VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
- VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
- VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
-
-constexpr VkAccessFlags UPLOAD_ACCESS_BARRIERS =
- VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT |
- VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT;
+VkIndexType IndexTypeFromNumElements(const Device& device, u32 num_elements) {
+ if (num_elements <= 0xff && device.IsExtIndexTypeUint8Supported()) {
+ return VK_INDEX_TYPE_UINT8_EXT;
+ }
+ if (num_elements <= 0xffff) {
+ return VK_INDEX_TYPE_UINT16;
+ }
+ return VK_INDEX_TYPE_UINT32;
+}
-std::unique_ptr<VKStreamBuffer> CreateStreamBuffer(const VKDevice& device, VKScheduler& scheduler) {
- return std::make_unique<VKStreamBuffer>(device, scheduler, BUFFER_USAGE);
+size_t BytesPerIndex(VkIndexType index_type) {
+ switch (index_type) {
+ case VK_INDEX_TYPE_UINT8_EXT:
+ return 1;
+ case VK_INDEX_TYPE_UINT16:
+ return 2;
+ case VK_INDEX_TYPE_UINT32:
+ return 4;
+ default:
+ UNREACHABLE_MSG("Invalid index type={}", index_type);
+ return 1;
+ }
}
+template <typename T>
+std::array<T, 6> MakeQuadIndices(u32 quad, u32 first) {
+ std::array<T, 6> indices{0, 1, 2, 0, 2, 3};
+ std::ranges::transform(indices, indices.begin(),
+ [quad, first](u32 index) { return first + index + quad * 4; });
+ return indices;
+}
} // Anonymous namespace
-Buffer::Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler_,
- VKStagingBufferPool& staging_pool_, VAddr cpu_addr, std::size_t size)
- : BufferBlock{cpu_addr, size}, scheduler{scheduler_}, staging_pool{staging_pool_} {
- const VkBufferCreateInfo ci{
+Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
+ : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(null_params) {}
+
+Buffer::Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
+ VAddr cpu_addr_, u64 size_bytes_)
+ : VideoCommon::BufferBase<VideoCore::RasterizerInterface>(rasterizer_, cpu_addr_, size_bytes_) {
+ buffer = runtime.device.GetLogical().CreateBuffer(VkBufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .size = static_cast<VkDeviceSize>(size),
- .usage = BUFFER_USAGE | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ .size = SizeBytes(),
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+ VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
+ VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
+ VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
- };
+ });
+ if (runtime.device.HasDebuggingToolAttached()) {
+ buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str());
+ }
+ commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
+}
- buffer.handle = device.GetLogical().CreateBuffer(ci);
- buffer.commit = memory_manager.Commit(buffer.handle, false);
+BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_allocator_,
+ VKScheduler& scheduler_, StagingBufferPool& staging_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_,
+ VKDescriptorPool& descriptor_pool)
+ : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
+ staging_pool{staging_pool_}, update_descriptor_queue{update_descriptor_queue_},
+ uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
+ quad_index_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue) {}
+
+StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
+ return staging_pool.Request(size, MemoryUsage::Upload);
}
-Buffer::~Buffer() = default;
+StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size) {
+ return staging_pool.Request(size, MemoryUsage::Download);
+}
-void Buffer::Upload(std::size_t offset, std::size_t size, const u8* data) {
- const auto& staging = staging_pool.GetUnusedBuffer(size, true);
- std::memcpy(staging.commit->Map(size), data, size);
+void BufferCacheRuntime::Finish() {
+ scheduler.Finish();
+}
+void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
+ std::span<const VideoCommon::BufferCopy> copies) {
+ static constexpr VkMemoryBarrier READ_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
+ };
+ static constexpr VkMemoryBarrier WRITE_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+ };
+ // Measuring a popular game, this number never exceeds the specified size once data is warmed up
+ boost::container::small_vector<VkBufferCopy, 3> vk_copies(copies.size());
+ std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy);
scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([src_buffer, dst_buffer, vk_copies](vk::CommandBuffer cmdbuf) {
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, READ_BARRIER);
+ cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ 0, WRITE_BARRIER);
+ });
+}
- const VkBuffer handle = Handle();
- scheduler.Record([staging = *staging.handle, handle, offset, size](vk::CommandBuffer cmdbuf) {
- cmdbuf.CopyBuffer(staging, handle, VkBufferCopy{0, offset, size});
-
- const VkBufferMemoryBarrier barrier{
- .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
- .pNext = nullptr,
- .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
- .dstAccessMask = UPLOAD_ACCESS_BARRIERS,
- .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .buffer = handle,
- .offset = offset,
- .size = size,
- };
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, UPLOAD_PIPELINE_STAGE, 0, {},
- barrier, {});
+void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
+ u32 base_vertex, u32 num_indices, VkBuffer buffer,
+ u32 offset, [[maybe_unused]] u32 size) {
+ VkIndexType vk_index_type = MaxwellToVK::IndexFormat(index_format);
+ VkDeviceSize vk_offset = offset;
+ VkBuffer vk_buffer = buffer;
+ if (topology == PrimitiveTopology::Quads) {
+ vk_index_type = VK_INDEX_TYPE_UINT32;
+ std::tie(vk_buffer, vk_offset) =
+ quad_index_pass.Assemble(index_format, num_indices, base_vertex, buffer, offset);
+ } else if (vk_index_type == VK_INDEX_TYPE_UINT8_EXT && !device.IsExtIndexTypeUint8Supported()) {
+ vk_index_type = VK_INDEX_TYPE_UINT16;
+ std::tie(vk_buffer, vk_offset) = uint8_pass.Assemble(num_indices, buffer, offset);
+ }
+ if (vk_buffer == VK_NULL_HANDLE) {
+ // Vulkan doesn't support null index buffers. Replace it with our own null buffer.
+ ReserveNullIndexBuffer();
+ vk_buffer = *null_index_buffer;
+ }
+ scheduler.Record([vk_buffer, vk_offset, vk_index_type](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindIndexBuffer(vk_buffer, vk_offset, vk_index_type);
});
}
-void Buffer::Download(std::size_t offset, std::size_t size, u8* data) {
- const auto& staging = staging_pool.GetUnusedBuffer(size, true);
- scheduler.RequestOutsideRenderPassOperationContext();
+void BufferCacheRuntime::BindQuadArrayIndexBuffer(u32 first, u32 count) {
+ ReserveQuadArrayLUT(first + count, true);
- const VkBuffer handle = Handle();
- scheduler.Record([staging = *staging.handle, handle, offset, size](vk::CommandBuffer cmdbuf) {
- const VkBufferMemoryBarrier barrier{
- .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
- .pNext = nullptr,
- .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
- .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
- .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
- .buffer = handle,
- .offset = offset,
- .size = size,
- };
-
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
- VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
- VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
- VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, barrier, {});
- cmdbuf.CopyBuffer(handle, staging, VkBufferCopy{offset, 0, size});
+ // The LUT has the indices 0, 1, 2, and 3 copied as an array
+ // To apply these 'first' offsets we can apply an offset based on the modulus.
+ const VkIndexType index_type = quad_array_lut_index_type;
+ const size_t sub_first_offset = static_cast<size_t>(first % 4) * (current_num_indices / 4);
+ const size_t offset = (sub_first_offset + first / 4) * 6ULL * BytesPerIndex(index_type);
+ scheduler.Record([buffer = *quad_array_lut, index_type, offset](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindIndexBuffer(buffer, offset, index_type);
});
- scheduler.Finish();
-
- std::memcpy(data, staging.commit->Map(size), size);
}
-void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
- std::size_t size) {
- scheduler.RequestOutsideRenderPassOperationContext();
+void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size,
+ u32 stride) {
+ if (device.IsExtExtendedDynamicStateSupported()) {
+ scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
+ const VkDeviceSize vk_offset = offset;
+ const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE;
+ const VkDeviceSize vk_stride = stride;
+ cmdbuf.BindVertexBuffers2EXT(index, 1, &buffer, &vk_offset, &vk_size, &vk_stride);
+ });
+ } else {
+ scheduler.Record([index, buffer, offset](vk::CommandBuffer cmdbuf) {
+ cmdbuf.BindVertexBuffer(index, buffer, offset);
+ });
+ }
+}
- const VkBuffer dst_buffer = Handle();
- scheduler.Record([src_buffer = src.Handle(), dst_buffer, src_offset, dst_offset,
- size](vk::CommandBuffer cmdbuf) {
- cmdbuf.CopyBuffer(src_buffer, dst_buffer, VkBufferCopy{src_offset, dst_offset, size});
-
- std::array<VkBufferMemoryBarrier, 2> barriers;
- barriers[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
- barriers[0].pNext = nullptr;
- barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
- barriers[0].dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
- barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barriers[0].buffer = src_buffer;
- barriers[0].offset = src_offset;
- barriers[0].size = size;
- barriers[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
- barriers[1].pNext = nullptr;
- barriers[1].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
- barriers[1].dstAccessMask = UPLOAD_ACCESS_BARRIERS;
- barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barriers[1].buffer = dst_buffer;
- barriers[1].offset = dst_offset;
- barriers[1].size = size;
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, UPLOAD_PIPELINE_STAGE, 0, {},
- barriers, {});
+void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset,
+ u32 size) {
+ if (!device.IsExtTransformFeedbackSupported()) {
+ // Already logged in the rasterizer
+ return;
+ }
+ scheduler.Record([index, buffer, offset, size](vk::CommandBuffer cmdbuf) {
+ const VkDeviceSize vk_offset = offset;
+ const VkDeviceSize vk_size = size;
+ cmdbuf.BindTransformFeedbackBuffersEXT(index, 1, &buffer, &vk_offset, &vk_size);
});
}
-VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
- const VKDevice& device, VKMemoryManager& memory_manager,
- VKScheduler& scheduler, VKStagingBufferPool& staging_pool)
- : VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, system,
- CreateStreamBuffer(device,
- scheduler)},
- device{device}, memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{
- staging_pool} {}
+void BufferCacheRuntime::BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
+ update_descriptor_queue.AddBuffer(buffer, offset, size);
+}
+
+void BufferCacheRuntime::ReserveQuadArrayLUT(u32 num_indices, bool wait_for_idle) {
+ if (num_indices <= current_num_indices) {
+ return;
+ }
+ if (wait_for_idle) {
+ scheduler.Finish();
+ }
+ current_num_indices = num_indices;
+ quad_array_lut_index_type = IndexTypeFromNumElements(device, num_indices);
-VKBufferCache::~VKBufferCache() = default;
+ const u32 num_quads = num_indices / 4;
+ const u32 num_triangle_indices = num_quads * 6;
+ const u32 num_first_offset_copies = 4;
+ const size_t bytes_per_index = BytesPerIndex(quad_array_lut_index_type);
+ const size_t size_bytes = num_triangle_indices * bytes_per_index * num_first_offset_copies;
+ quad_array_lut = device.GetLogical().CreateBuffer(VkBufferCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = size_bytes,
+ .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ });
+ if (device.HasDebuggingToolAttached()) {
+ quad_array_lut.SetObjectNameEXT("Quad LUT");
+ }
+ quad_array_lut_commit = memory_allocator.Commit(quad_array_lut, MemoryUsage::DeviceLocal);
-std::shared_ptr<Buffer> VKBufferCache::CreateBlock(VAddr cpu_addr, std::size_t size) {
- return std::make_shared<Buffer>(device, memory_manager, scheduler, staging_pool, cpu_addr,
- size);
+ const StagingBufferRef staging = staging_pool.Request(size_bytes, MemoryUsage::Upload);
+ u8* staging_data = staging.mapped_span.data();
+ const size_t quad_size = bytes_per_index * 6;
+ for (u32 first = 0; first < num_first_offset_copies; ++first) {
+ for (u32 quad = 0; quad < num_quads; ++quad) {
+ switch (quad_array_lut_index_type) {
+ case VK_INDEX_TYPE_UINT8_EXT:
+ std::memcpy(staging_data, MakeQuadIndices<u8>(quad, first).data(), quad_size);
+ break;
+ case VK_INDEX_TYPE_UINT16:
+ std::memcpy(staging_data, MakeQuadIndices<u16>(quad, first).data(), quad_size);
+ break;
+ case VK_INDEX_TYPE_UINT32:
+ std::memcpy(staging_data, MakeQuadIndices<u32>(quad, first).data(), quad_size);
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ staging_data += quad_size;
+ }
+ }
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([src_buffer = staging.buffer, src_offset = staging.offset,
+ dst_buffer = *quad_array_lut, size_bytes](vk::CommandBuffer cmdbuf) {
+ const VkBufferCopy copy{
+ .srcOffset = src_offset,
+ .dstOffset = 0,
+ .size = size_bytes,
+ };
+ const VkBufferMemoryBarrier write_barrier{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_INDEX_READ_BIT,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .buffer = dst_buffer,
+ .offset = 0,
+ .size = size_bytes,
+ };
+ cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+ 0, write_barrier);
+ });
}
-VKBufferCache::BufferInfo VKBufferCache::GetEmptyBuffer(std::size_t size) {
- size = std::max(size, std::size_t(4));
- const auto& empty = staging_pool.GetUnusedBuffer(size, false);
+void BufferCacheRuntime::ReserveNullIndexBuffer() {
+ if (null_index_buffer) {
+ return;
+ }
+ null_index_buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = 4,
+ .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ });
+ if (device.HasDebuggingToolAttached()) {
+ null_index_buffer.SetObjectNameEXT("Null index buffer");
+ }
+ null_index_buffer_commit = memory_allocator.Commit(null_index_buffer, MemoryUsage::DeviceLocal);
+
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([size, buffer = *empty.handle](vk::CommandBuffer cmdbuf) {
- cmdbuf.FillBuffer(buffer, 0, size, 0);
+ scheduler.Record([buffer = *null_index_buffer](vk::CommandBuffer cmdbuf) {
+ cmdbuf.FillBuffer(buffer, 0, VK_WHOLE_SIZE, 0);
});
- return {*empty.handle, 0, 0};
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 991ee451c..041e6515c 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -4,70 +4,124 @@
#pragma once
-#include <memory>
-
-#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_cache.h"
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
-#include "video_core/renderer_vulkan/vk_stream_buffer.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Core {
-class System;
-}
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-class VKDevice;
-class VKMemoryManager;
+class Device;
+class VKDescriptorPool;
class VKScheduler;
+class VKUpdateDescriptorQueue;
-class Buffer final : public VideoCommon::BufferBlock {
+class BufferCacheRuntime;
+
+class Buffer : public VideoCommon::BufferBase<VideoCore::RasterizerInterface> {
public:
- explicit Buffer(const VKDevice& device, VKMemoryManager& memory_manager, VKScheduler& scheduler,
- VKStagingBufferPool& staging_pool, VAddr cpu_addr, std::size_t size);
- ~Buffer();
+ explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params);
+ explicit Buffer(BufferCacheRuntime& runtime, VideoCore::RasterizerInterface& rasterizer_,
+ VAddr cpu_addr_, u64 size_bytes_);
+
+ [[nodiscard]] VkBuffer Handle() const noexcept {
+ return *buffer;
+ }
+
+ operator VkBuffer() const noexcept {
+ return *buffer;
+ }
+
+private:
+ vk::Buffer buffer;
+ MemoryCommit commit;
+};
+
+class BufferCacheRuntime {
+ friend Buffer;
+
+ using PrimitiveTopology = Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology;
+ using IndexFormat = Tegra::Engines::Maxwell3D::Regs::IndexFormat;
+
+public:
+ explicit BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_manager_,
+ VKScheduler& scheduler_, StagingBufferPool& staging_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_,
+ VKDescriptorPool& descriptor_pool);
+
+ void Finish();
- void Upload(std::size_t offset, std::size_t size, const u8* data);
+ [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
- void Download(std::size_t offset, std::size_t size, u8* data);
+ [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size);
- void CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
- std::size_t size);
+ void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
+ std::span<const VideoCommon::BufferCopy> copies);
- VkBuffer Handle() const {
- return *buffer.handle;
+ void BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, u32 num_indices,
+ u32 base_vertex, VkBuffer buffer, u32 offset, u32 size);
+
+ void BindQuadArrayIndexBuffer(u32 first, u32 count);
+
+ void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride);
+
+ void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
+
+ std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
+ [[maybe_unused]] u32 binding_index, u32 size) {
+ const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload);
+ BindBuffer(ref.buffer, static_cast<u32>(ref.offset), size);
+ return ref.mapped_span;
}
- u64 Address() const {
- return 0;
+ void BindUniformBuffer(VkBuffer buffer, u32 offset, u32 size) {
+ BindBuffer(buffer, offset, size);
+ }
+
+ void BindStorageBuffer(VkBuffer buffer, u32 offset, u32 size,
+ [[maybe_unused]] bool is_written) {
+ BindBuffer(buffer, offset, size);
}
private:
+ void BindBuffer(VkBuffer buffer, u32 offset, u32 size);
+
+ void ReserveQuadArrayLUT(u32 num_indices, bool wait_for_idle);
+
+ void ReserveNullIndexBuffer();
+
+ const Device& device;
+ MemoryAllocator& memory_allocator;
VKScheduler& scheduler;
- VKStagingBufferPool& staging_pool;
+ StagingBufferPool& staging_pool;
+ VKUpdateDescriptorQueue& update_descriptor_queue;
- VKBuffer buffer;
-};
+ vk::Buffer quad_array_lut;
+ MemoryCommit quad_array_lut_commit;
+ VkIndexType quad_array_lut_index_type{};
+ u32 current_num_indices = 0;
-class VKBufferCache final : public VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer> {
-public:
- explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
- const VKDevice& device, VKMemoryManager& memory_manager,
- VKScheduler& scheduler, VKStagingBufferPool& staging_pool);
- ~VKBufferCache();
+ vk::Buffer null_index_buffer;
+ MemoryCommit null_index_buffer_commit;
- BufferInfo GetEmptyBuffer(std::size_t size) override;
+ Uint8Pass uint8_pass;
+ QuadIndexedPass quad_index_pass;
+};
-protected:
- std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) override;
+struct BufferCacheParams {
+ using Runtime = Vulkan::BufferCacheRuntime;
+ using Buffer = Vulkan::Buffer;
-private:
- const VKDevice& device;
- VKMemoryManager& memory_manager;
- VKScheduler& scheduler;
- VKStagingBufferPool& staging_pool;
+ static constexpr bool IS_OPENGL = false;
+ static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = false;
+ static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = false;
+ static constexpr bool NEEDS_BIND_UNIFORM_INDEX = false;
+ static constexpr bool NEEDS_BIND_STORAGE_INDEX = false;
+ static constexpr bool USE_MEMORY_MAPS = true;
};
+using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_command_pool.cpp b/src/video_core/renderer_vulkan/vk_command_pool.cpp
new file mode 100644
index 000000000..a99df9323
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_command_pool.cpp
@@ -0,0 +1,46 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstddef>
+
+#include "video_core/renderer_vulkan/vk_command_pool.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+constexpr size_t COMMAND_BUFFER_POOL_SIZE = 0x1000;
+
+struct CommandPool::Pool {
+ vk::CommandPool handle;
+ vk::CommandBuffers cmdbufs;
+};
+
+CommandPool::CommandPool(MasterSemaphore& master_semaphore_, const Device& device_)
+ : ResourcePool(master_semaphore_, COMMAND_BUFFER_POOL_SIZE), device{device_} {}
+
+CommandPool::~CommandPool() = default;
+
+void CommandPool::Allocate(size_t begin, size_t end) {
+ // Command buffers are going to be commited, recorded, executed every single usage cycle.
+ // They are also going to be reseted when commited.
+ Pool& pool = pools.emplace_back();
+ pool.handle = device.GetLogical().CreateCommandPool({
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .pNext = nullptr,
+ .flags =
+ VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ .queueFamilyIndex = device.GetGraphicsFamily(),
+ });
+ pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE);
+}
+
+VkCommandBuffer CommandPool::Commit() {
+ const size_t index = CommitResource();
+ const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
+ const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
+ return pools[pool_index].cmdbufs[sub_index];
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_command_pool.h b/src/video_core/renderer_vulkan/vk_command_pool.h
new file mode 100644
index 000000000..61c26a22a
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_command_pool.h
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+class Device;
+class MasterSemaphore;
+
+class CommandPool final : public ResourcePool {
+public:
+ explicit CommandPool(MasterSemaphore& master_semaphore_, const Device& device_);
+ ~CommandPool() override;
+
+ void Allocate(size_t begin, size_t end) override;
+
+ VkCommandBuffer Commit();
+
+private:
+ struct Pool;
+
+ const Device& device;
+ std::vector<Pool> pools;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 182461ed9..2f9a7b028 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -10,131 +10,19 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/div_ceil.h"
+#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
+#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-
namespace {
-
-// Quad array SPIR-V module. Generated from the "shaders/" directory, read the instructions there.
-constexpr u8 quad_array[] = {
- 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x54, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
- 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
- 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x05, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x04, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x1c, 0x00, 0x04, 0x00, 0x34, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x09, 0x00, 0x34, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
- 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00,
- 0x37, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x34, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x44, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00,
- 0x00, 0x04, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
- 0x49, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x3a, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00,
- 0xf8, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x4b, 0x00, 0x00, 0x00,
- 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x4d, 0x00, 0x00, 0x00,
- 0xf8, 0x00, 0x02, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
- 0x44, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0xae, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00,
- 0x4b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00,
- 0x27, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00,
- 0x27, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x2f, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x32, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
- 0x3e, 0x00, 0x03, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
- 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
- 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
- 0x3d, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x44, 0x00, 0x00, 0x00,
- 0x45, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
- 0x3e, 0x00, 0x03, 0x00, 0x45, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00,
- 0xf9, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0xf9, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4e, 0x00, 0x00, 0x00,
- 0xf9, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00,
- 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
-
-VkDescriptorSetLayoutBinding BuildQuadArrayPassDescriptorSetLayoutBinding() {
- return {
- .binding = 0,
- .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
- .descriptorCount = 1,
- .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
- .pImmutableSamplers = nullptr,
- };
-}
-
-VkDescriptorUpdateTemplateEntryKHR BuildQuadArrayPassDescriptorUpdateTemplateEntry() {
- return {
- .dstBinding = 0,
- .dstArrayElement = 0,
- .descriptorCount = 1,
- .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
- .offset = 0,
- .stride = sizeof(DescriptorUpdateEntry),
- };
-}
-
VkPushConstantRange BuildComputePushConstantRange(std::size_t size) {
return {
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
@@ -143,206 +31,6 @@ VkPushConstantRange BuildComputePushConstantRange(std::size_t size) {
};
}
-// Uint8 SPIR-V module. Generated from the "shaders/" directory.
-constexpr u8 uint8_pass[] = {
- 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x2f, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
- 0x51, 0x11, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x61, 0x11, 0x00, 0x00, 0x0a, 0x00, 0x07, 0x00,
- 0x53, 0x50, 0x56, 0x5f, 0x4b, 0x48, 0x52, 0x5f, 0x31, 0x36, 0x62, 0x69, 0x74, 0x5f, 0x73, 0x74,
- 0x6f, 0x72, 0x61, 0x67, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x53, 0x50, 0x56, 0x5f,
- 0x4b, 0x48, 0x52, 0x5f, 0x38, 0x62, 0x69, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
- 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c,
- 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x0b, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
- 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x12, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00,
- 0x1f, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x00,
- 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
- 0x00, 0x04, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x2c, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x0e, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00,
- 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00,
- 0x1a, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0xf7, 0x00, 0x03, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
- 0x1c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0x08, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0x15, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x71, 0x00, 0x04, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
- 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
- 0xf9, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
-
-// Quad indexed SPIR-V module. Generated from the "shaders/" directory.
-constexpr u8 QUAD_INDEXED_SPV[] = {
- 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x7c, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
- 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x06, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
- 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x05, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x05, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x56, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x04, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x18, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x59, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x59, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x72, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00,
- 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00,
- 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x13, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
- 0x20, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x02, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x21, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00,
- 0x24, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x26, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x3b, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x3f, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x41, 0x00, 0x00, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x43, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x09, 0x00, 0x41, 0x00, 0x00, 0x00,
- 0x44, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
- 0x42, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
- 0x46, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x03, 0x00,
- 0x56, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00,
- 0x56, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x57, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x58, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x5b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
- 0x00, 0x04, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00,
- 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
- 0x70, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
- 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
- 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x46, 0x00, 0x00, 0x00,
- 0x47, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00,
- 0xf8, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00, 0xf6, 0x00, 0x04, 0x00, 0x73, 0x00, 0x00, 0x00,
- 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x75, 0x00, 0x00, 0x00,
- 0xf8, 0x00, 0x02, 0x00, 0x75, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x0e, 0x00, 0x00, 0x00,
- 0x0f, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
- 0x44, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
- 0x19, 0x00, 0x00, 0x00, 0xaf, 0x00, 0x05, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
- 0x14, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00,
- 0x73, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
- 0x26, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
- 0xc4, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
- 0x28, 0x00, 0x00, 0x00, 0x82, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
- 0x2b, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x31, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x82, 0x00, 0x05, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0xf9, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00,
- 0xf5, 0x00, 0x07, 0x00, 0x09, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
- 0x1e, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x05, 0x00,
- 0x1b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x00, 0x00,
- 0xf6, 0x00, 0x04, 0x00, 0x37, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0xfa, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
- 0xf8, 0x00, 0x02, 0x00, 0x36, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x40, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
- 0x47, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00,
- 0x48, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
- 0x06, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00,
- 0xc3, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
- 0x2e, 0x00, 0x00, 0x00, 0xc7, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00,
- 0x4a, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
- 0x54, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
- 0x5b, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
- 0x4e, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00,
- 0x5c, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x06, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00,
- 0x5d, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00,
- 0x41, 0x00, 0x05, 0x00, 0x69, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
- 0x42, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00,
- 0x6a, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00,
- 0x62, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x5b, 0x00, 0x00, 0x00,
- 0x6d, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00,
- 0x3e, 0x00, 0x03, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
- 0x09, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
- 0xf9, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x37, 0x00, 0x00, 0x00,
- 0xf9, 0x00, 0x02, 0x00, 0x73, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, 0x00,
- 0xf9, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x73, 0x00, 0x00, 0x00,
- 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
-
std::array<VkDescriptorSetLayoutBinding, 2> BuildInputOutputDescriptorSetBindings() {
return {{
{
@@ -375,11 +63,11 @@ VkDescriptorUpdateTemplateEntryKHR BuildInputOutputDescriptorUpdateTemplate() {
} // Anonymous namespace
-VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descriptor_pool,
+VKComputePass::VKComputePass(const Device& device, VKDescriptorPool& descriptor_pool,
vk::Span<VkDescriptorSetLayoutBinding> bindings,
vk::Span<VkDescriptorUpdateTemplateEntryKHR> templates,
- vk::Span<VkPushConstantRange> push_constants, std::size_t code_size,
- const u8* code) {
+ vk::Span<VkPushConstantRange> push_constants,
+ std::span<const u32> code) {
descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = nullptr,
@@ -387,7 +75,6 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto
.bindingCount = bindings.size(),
.pBindings = bindings.data(),
});
-
layout = device.GetLogical().CreatePipelineLayout({
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pNext = nullptr,
@@ -397,7 +84,6 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto
.pushConstantRangeCount = push_constants.size(),
.pPushConstantRanges = push_constants.data(),
});
-
if (!templates.empty()) {
descriptor_template = device.GetLogical().CreateDescriptorUpdateTemplateKHR({
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR,
@@ -414,18 +100,13 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto
descriptor_allocator.emplace(descriptor_pool, *descriptor_set_layout);
}
-
- auto code_copy = std::make_unique<u32[]>(code_size / sizeof(u32) + 1);
- std::memcpy(code_copy.get(), code, code_size);
-
module = device.GetLogical().CreateShaderModule({
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .codeSize = code_size,
- .pCode = code_copy.get(),
+ .codeSize = static_cast<u32>(code.size_bytes()),
+ .pCode = code.data(),
});
-
pipeline = device.GetLogical().CreateComputePipeline({
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.pNext = nullptr,
@@ -448,126 +129,70 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto
VKComputePass::~VKComputePass() = default;
-VkDescriptorSet VKComputePass::CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue,
- VKFence& fence) {
+VkDescriptorSet VKComputePass::CommitDescriptorSet(
+ VKUpdateDescriptorQueue& update_descriptor_queue) {
if (!descriptor_template) {
return nullptr;
}
- const auto set = descriptor_allocator->Commit(fence);
+ const VkDescriptorSet set = descriptor_allocator->Commit();
update_descriptor_queue.Send(*descriptor_template, set);
return set;
}
-QuadArrayPass::QuadArrayPass(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKStagingBufferPool& staging_buffer_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue)
- : VKComputePass(device, descriptor_pool, BuildQuadArrayPassDescriptorSetLayoutBinding(),
- BuildQuadArrayPassDescriptorUpdateTemplateEntry(),
- BuildComputePushConstantRange(sizeof(u32)), std::size(quad_array), quad_array),
- scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool},
- update_descriptor_queue{update_descriptor_queue} {}
-
-QuadArrayPass::~QuadArrayPass() = default;
-
-std::pair<VkBuffer, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32 first) {
- const u32 num_triangle_vertices = (num_vertices / 4) * 6;
- const std::size_t staging_size = num_triangle_vertices * sizeof(u32);
- auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
-
- update_descriptor_queue.Acquire();
- update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
- const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence());
-
- scheduler.RequestOutsideRenderPassOperationContext();
-
- ASSERT(num_vertices % 4 == 0);
- const u32 num_quads = num_vertices / 4;
- scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, num_quads,
- first, set](vk::CommandBuffer cmdbuf) {
- constexpr u32 dispatch_size = 1024;
- cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
- cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {});
- cmdbuf.PushConstants(layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(first), &first);
- cmdbuf.Dispatch(Common::AlignUp(num_quads, dispatch_size) / dispatch_size, 1, 1);
-
- VkBufferMemoryBarrier barrier;
- barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
- barrier.pNext = nullptr;
- barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.buffer = buffer;
- barrier.offset = 0;
- barrier.size = static_cast<VkDeviceSize>(num_quads) * 6 * sizeof(u32);
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
- VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, {barrier}, {});
- });
- return {*buffer.handle, 0};
-}
-
-Uint8Pass::Uint8Pass(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool, VKStagingBufferPool& staging_buffer_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue)
+Uint8Pass::Uint8Pass(const Device& device, VKScheduler& scheduler_,
+ VKDescriptorPool& descriptor_pool, StagingBufferPool& staging_buffer_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_)
: VKComputePass(device, descriptor_pool, BuildInputOutputDescriptorSetBindings(),
- BuildInputOutputDescriptorUpdateTemplate(), {}, std::size(uint8_pass),
- uint8_pass),
- scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool},
- update_descriptor_queue{update_descriptor_queue} {}
+ BuildInputOutputDescriptorUpdateTemplate(), {}, VULKAN_UINT8_COMP_SPV),
+ scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
+ update_descriptor_queue{update_descriptor_queue_} {}
Uint8Pass::~Uint8Pass() = default;
-std::pair<VkBuffer, u64> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer,
- u64 src_offset) {
- const auto staging_size = static_cast<u32>(num_vertices * sizeof(u16));
- auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
+std::pair<VkBuffer, VkDeviceSize> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer,
+ u32 src_offset) {
+ const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16));
+ const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
update_descriptor_queue.Acquire();
update_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices);
- update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
- const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence());
+ update_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size);
+ const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set,
+ scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = staging.buffer, set,
num_vertices](vk::CommandBuffer cmdbuf) {
- constexpr u32 dispatch_size = 1024;
+ static constexpr u32 DISPATCH_SIZE = 1024;
+ static constexpr VkMemoryBarrier WRITE_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+ };
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {});
- cmdbuf.Dispatch(Common::AlignUp(num_vertices, dispatch_size) / dispatch_size, 1, 1);
-
- VkBufferMemoryBarrier barrier;
- barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
- barrier.pNext = nullptr;
- barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.buffer = buffer;
- barrier.offset = 0;
- barrier.size = static_cast<VkDeviceSize>(num_vertices * sizeof(u16));
+ cmdbuf.Dispatch(Common::DivCeil(num_vertices, DISPATCH_SIZE), 1, 1);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
- VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, barrier, {});
+ VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, WRITE_BARRIER);
});
- return {*buffer.handle, 0};
+ return {staging.buffer, staging.offset};
}
-QuadIndexedPass::QuadIndexedPass(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKStagingBufferPool& staging_buffer_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue)
- : VKComputePass(device, descriptor_pool, BuildInputOutputDescriptorSetBindings(),
+QuadIndexedPass::QuadIndexedPass(const Device& device_, VKScheduler& scheduler_,
+ VKDescriptorPool& descriptor_pool_,
+ StagingBufferPool& staging_buffer_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_)
+ : VKComputePass(device_, descriptor_pool_, BuildInputOutputDescriptorSetBindings(),
BuildInputOutputDescriptorUpdateTemplate(),
- BuildComputePushConstantRange(sizeof(u32) * 2), std::size(QUAD_INDEXED_SPV),
- QUAD_INDEXED_SPV),
- scheduler{scheduler}, staging_buffer_pool{staging_buffer_pool},
- update_descriptor_queue{update_descriptor_queue} {}
+ BuildComputePushConstantRange(sizeof(u32) * 2), VULKAN_QUAD_INDEXED_COMP_SPV),
+ scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
+ update_descriptor_queue{update_descriptor_queue_} {}
QuadIndexedPass::~QuadIndexedPass() = default;
-std::pair<VkBuffer, u64> QuadIndexedPass::Assemble(
+std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format, u32 num_vertices, u32 base_vertex,
- VkBuffer src_buffer, u64 src_offset) {
+ VkBuffer src_buffer, u32 src_offset) {
const u32 index_shift = [index_format] {
switch (index_format) {
case Tegra::Engines::Maxwell3D::Regs::IndexFormat::UnsignedByte:
@@ -584,38 +209,33 @@ std::pair<VkBuffer, u64> QuadIndexedPass::Assemble(
const u32 num_tri_vertices = (num_vertices / 4) * 6;
const std::size_t staging_size = num_tri_vertices * sizeof(u32);
- auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
+ const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
update_descriptor_queue.Acquire();
update_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
- update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
- const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence());
+ update_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size);
+ const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set,
+ scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = staging.buffer, set,
num_tri_vertices, base_vertex, index_shift](vk::CommandBuffer cmdbuf) {
- static constexpr u32 dispatch_size = 1024;
+ static constexpr u32 DISPATCH_SIZE = 1024;
+ static constexpr VkMemoryBarrier WRITE_BARRIER{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+ };
const std::array push_constants = {base_vertex, index_shift};
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {});
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push_constants),
&push_constants);
- cmdbuf.Dispatch(Common::AlignUp(num_tri_vertices, dispatch_size) / dispatch_size, 1, 1);
-
- VkBufferMemoryBarrier barrier;
- barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
- barrier.pNext = nullptr;
- barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.buffer = buffer;
- barrier.offset = 0;
- barrier.size = static_cast<VkDeviceSize>(num_tri_vertices * sizeof(u32));
+ cmdbuf.Dispatch(Common::DivCeil(num_tri_vertices, DISPATCH_SIZE), 1, 1);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
- VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, barrier, {});
+ VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, WRITE_BARRIER);
});
- return {*buffer.handle, 0};
+ return {staging.buffer, staging.offset};
}
} // 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 230b526bc..17d781d99 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -5,33 +5,31 @@
#pragma once
#include <optional>
+#include <span>
#include <utility>
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-class VKDevice;
-class VKFence;
+class Device;
+class StagingBufferPool;
class VKScheduler;
-class VKStagingBufferPool;
class VKUpdateDescriptorQueue;
class VKComputePass {
public:
- explicit VKComputePass(const VKDevice& device, VKDescriptorPool& descriptor_pool,
+ explicit VKComputePass(const Device& device, VKDescriptorPool& descriptor_pool,
vk::Span<VkDescriptorSetLayoutBinding> bindings,
vk::Span<VkDescriptorUpdateTemplateEntryKHR> templates,
- vk::Span<VkPushConstantRange> push_constants, std::size_t code_size,
- const u8* code);
+ vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code);
~VKComputePass();
protected:
- VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue,
- VKFence& fence);
+ VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue);
vk::DescriptorUpdateTemplateKHR descriptor_template;
vk::PipelineLayout layout;
@@ -43,52 +41,39 @@ private:
vk::ShaderModule module;
};
-class QuadArrayPass final : public VKComputePass {
-public:
- explicit QuadArrayPass(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKStagingBufferPool& staging_buffer_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue);
- ~QuadArrayPass();
-
- std::pair<VkBuffer, VkDeviceSize> Assemble(u32 num_vertices, u32 first);
-
-private:
- VKScheduler& scheduler;
- VKStagingBufferPool& staging_buffer_pool;
- VKUpdateDescriptorQueue& update_descriptor_queue;
-};
-
class Uint8Pass final : public VKComputePass {
public:
- explicit Uint8Pass(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool, VKStagingBufferPool& staging_buffer_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue);
+ explicit Uint8Pass(const Device& device_, VKScheduler& scheduler_,
+ VKDescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_);
~Uint8Pass();
- std::pair<VkBuffer, u64> Assemble(u32 num_vertices, VkBuffer src_buffer, u64 src_offset);
+ /// Assemble uint8 indices into an uint16 index buffer
+ /// Returns a pair with the staging buffer, and the offset where the assembled data is
+ std::pair<VkBuffer, VkDeviceSize> Assemble(u32 num_vertices, VkBuffer src_buffer,
+ u32 src_offset);
private:
VKScheduler& scheduler;
- VKStagingBufferPool& staging_buffer_pool;
+ StagingBufferPool& staging_buffer_pool;
VKUpdateDescriptorQueue& update_descriptor_queue;
};
class QuadIndexedPass final : public VKComputePass {
public:
- explicit QuadIndexedPass(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKStagingBufferPool& staging_buffer_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue);
+ explicit QuadIndexedPass(const Device& device_, VKScheduler& scheduler_,
+ VKDescriptorPool& descriptor_pool_,
+ StagingBufferPool& staging_buffer_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_);
~QuadIndexedPass();
- std::pair<VkBuffer, u64> Assemble(Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format,
- u32 num_vertices, u32 base_vertex, VkBuffer src_buffer,
- u64 src_offset);
+ std::pair<VkBuffer, VkDeviceSize> Assemble(
+ Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format, u32 num_vertices,
+ u32 base_vertex, VkBuffer src_buffer, u32 src_offset);
private:
VKScheduler& scheduler;
- VKStagingBufferPool& staging_buffer_pool;
+ StagingBufferPool& staging_buffer_pool;
VKUpdateDescriptorQueue& update_descriptor_queue;
};
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index ed9d2991c..3a48219b7 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -6,25 +6,25 @@
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-VKComputePipeline::VKComputePipeline(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
- const SPIRVShader& shader)
- : device{device}, scheduler{scheduler}, entries{shader.entries},
+VKComputePipeline::VKComputePipeline(const Device& device_, VKScheduler& scheduler_,
+ VKDescriptorPool& descriptor_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_,
+ const SPIRVShader& shader_)
+ : device{device_}, scheduler{scheduler_}, entries{shader_.entries},
descriptor_set_layout{CreateDescriptorSetLayout()},
- descriptor_allocator{descriptor_pool, *descriptor_set_layout},
- update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()},
+ descriptor_allocator{descriptor_pool_, *descriptor_set_layout},
+ update_descriptor_queue{update_descriptor_queue_}, layout{CreatePipelineLayout()},
descriptor_template{CreateDescriptorUpdateTemplate()},
- shader_module{CreateShaderModule(shader.code)}, pipeline{CreatePipeline()} {}
+ shader_module{CreateShaderModule(shader_.code)}, pipeline{CreatePipeline()} {}
VKComputePipeline::~VKComputePipeline() = default;
@@ -32,7 +32,7 @@ VkDescriptorSet VKComputePipeline::CommitDescriptorSet() {
if (!descriptor_template) {
return {};
}
- const auto set = descriptor_allocator.Commit(scheduler.GetFence());
+ const VkDescriptorSet set = descriptor_allocator.Commit();
update_descriptor_queue.Send(*descriptor_template, set);
return set;
}
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
index 6e2f22a4a..7e16575ac 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
@@ -7,20 +7,20 @@
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-class VKDevice;
+class Device;
class VKScheduler;
class VKUpdateDescriptorQueue;
class VKComputePipeline final {
public:
- explicit VKComputePipeline(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
- const SPIRVShader& shader);
+ explicit VKComputePipeline(const Device& device_, VKScheduler& scheduler_,
+ VKDescriptorPool& descriptor_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_,
+ const SPIRVShader& shader_);
~VKComputePipeline();
VkDescriptorSet CommitDescriptorSet();
@@ -48,7 +48,7 @@ private:
vk::Pipeline CreatePipeline() const;
- const VKDevice& device;
+ const Device& device;
VKScheduler& scheduler;
ShaderEntries entries;
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
index ac4a0884e..ef9fb5910 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
@@ -6,23 +6,25 @@
#include "common/common_types.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
// Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines.
constexpr std::size_t SETS_GROW_RATE = 0x20;
-DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool,
- VkDescriptorSetLayout layout)
- : VKFencedPool{SETS_GROW_RATE}, descriptor_pool{descriptor_pool}, layout{layout} {}
+DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool_,
+ VkDescriptorSetLayout layout_)
+ : ResourcePool(descriptor_pool_.master_semaphore, SETS_GROW_RATE),
+ descriptor_pool{descriptor_pool_}, layout{layout_} {}
DescriptorAllocator::~DescriptorAllocator() = default;
-VkDescriptorSet DescriptorAllocator::Commit(VKFence& fence) {
- const std::size_t index = CommitResource(fence);
+VkDescriptorSet DescriptorAllocator::Commit() {
+ const std::size_t index = CommitResource();
return descriptors_allocations[index / SETS_GROW_RATE][index % SETS_GROW_RATE];
}
@@ -30,8 +32,9 @@ void DescriptorAllocator::Allocate(std::size_t begin, std::size_t end) {
descriptors_allocations.push_back(descriptor_pool.AllocateDescriptors(layout, end - begin));
}
-VKDescriptorPool::VKDescriptorPool(const VKDevice& device)
- : device{device}, active_pool{AllocateNewPool()} {}
+VKDescriptorPool::VKDescriptorPool(const Device& device_, VKScheduler& scheduler)
+ : device{device_}, master_semaphore{scheduler.GetMasterSemaphore()}, active_pool{
+ AllocateNewPool()} {}
VKDescriptorPool::~VKDescriptorPool() = default;
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
index 9efa66bef..f892be7be 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.h
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
@@ -6,21 +6,24 @@
#include <vector>
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
+class Device;
class VKDescriptorPool;
+class VKScheduler;
-class DescriptorAllocator final : public VKFencedPool {
+class DescriptorAllocator final : public ResourcePool {
public:
explicit DescriptorAllocator(VKDescriptorPool& descriptor_pool, VkDescriptorSetLayout layout);
~DescriptorAllocator() override;
+ DescriptorAllocator& operator=(const DescriptorAllocator&) = delete;
DescriptorAllocator(const DescriptorAllocator&) = delete;
- VkDescriptorSet Commit(VKFence& fence);
+ VkDescriptorSet Commit();
protected:
void Allocate(std::size_t begin, std::size_t end) override;
@@ -36,15 +39,19 @@ class VKDescriptorPool final {
friend DescriptorAllocator;
public:
- explicit VKDescriptorPool(const VKDevice& device);
+ explicit VKDescriptorPool(const Device& device, VKScheduler& scheduler);
~VKDescriptorPool();
+ VKDescriptorPool(const VKDescriptorPool&) = delete;
+ VKDescriptorPool& operator=(const VKDescriptorPool&) = delete;
+
private:
vk::DescriptorPool* AllocateNewPool();
vk::DescriptorSets AllocateDescriptors(VkDescriptorSetLayout layout, std::size_t count);
- const VKDevice& device;
+ const Device& device;
+ MasterSemaphore& master_semaphore;
std::vector<vk::DescriptorPool> pools;
vk::DescriptorPool* active_pool;
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
deleted file mode 100644
index 030b4dbd3..000000000
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ /dev/null
@@ -1,790 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <bitset>
-#include <chrono>
-#include <optional>
-#include <string_view>
-#include <thread>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "common/assert.h"
-#include "core/settings.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-namespace {
-
-namespace Alternatives {
-
-constexpr std::array Depth24UnormS8_UINT{
- VK_FORMAT_D32_SFLOAT_S8_UINT,
- VK_FORMAT_D16_UNORM_S8_UINT,
- VkFormat{},
-};
-
-constexpr std::array Depth16UnormS8_UINT{
- VK_FORMAT_D24_UNORM_S8_UINT,
- VK_FORMAT_D32_SFLOAT_S8_UINT,
- VkFormat{},
-};
-
-} // namespace Alternatives
-
-constexpr std::array REQUIRED_EXTENSIONS{
- VK_KHR_SWAPCHAIN_EXTENSION_NAME,
- VK_KHR_16BIT_STORAGE_EXTENSION_NAME,
- VK_KHR_8BIT_STORAGE_EXTENSION_NAME,
- VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME,
- VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME,
- VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME,
- VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME,
- VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME,
- VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME,
-};
-
-template <typename T>
-void SetNext(void**& next, T& data) {
- *next = &data;
- next = &data.pNext;
-}
-
-constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
- switch (format) {
- case VK_FORMAT_D24_UNORM_S8_UINT:
- return Alternatives::Depth24UnormS8_UINT.data();
- case VK_FORMAT_D16_UNORM_S8_UINT:
- return Alternatives::Depth16UnormS8_UINT.data();
- default:
- return nullptr;
- }
-}
-
-VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType format_type) {
- switch (format_type) {
- case FormatType::Linear:
- return properties.linearTilingFeatures;
- case FormatType::Optimal:
- return properties.optimalTilingFeatures;
- case FormatType::Buffer:
- return properties.bufferFeatures;
- default:
- return {};
- }
-}
-
-std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(
- vk::PhysicalDevice physical, const vk::InstanceDispatch& dld) {
- static constexpr std::array formats{
- VK_FORMAT_A8B8G8R8_UNORM_PACK32,
- VK_FORMAT_A8B8G8R8_UINT_PACK32,
- VK_FORMAT_A8B8G8R8_SNORM_PACK32,
- VK_FORMAT_A8B8G8R8_SINT_PACK32,
- VK_FORMAT_A8B8G8R8_SRGB_PACK32,
- VK_FORMAT_B5G6R5_UNORM_PACK16,
- VK_FORMAT_A2B10G10R10_UNORM_PACK32,
- VK_FORMAT_A2B10G10R10_UINT_PACK32,
- VK_FORMAT_A1R5G5B5_UNORM_PACK16,
- VK_FORMAT_R32G32B32A32_SFLOAT,
- VK_FORMAT_R32G32B32A32_SINT,
- VK_FORMAT_R32G32B32A32_UINT,
- VK_FORMAT_R32G32_SFLOAT,
- VK_FORMAT_R32G32_SINT,
- VK_FORMAT_R32G32_UINT,
- VK_FORMAT_R16G16B16A16_SINT,
- VK_FORMAT_R16G16B16A16_UINT,
- VK_FORMAT_R16G16B16A16_SNORM,
- VK_FORMAT_R16G16B16A16_UNORM,
- VK_FORMAT_R16G16_UNORM,
- VK_FORMAT_R16G16_SNORM,
- VK_FORMAT_R16G16_SFLOAT,
- VK_FORMAT_R16_UNORM,
- VK_FORMAT_R16_UINT,
- VK_FORMAT_R8G8B8A8_SRGB,
- VK_FORMAT_R8G8_UNORM,
- VK_FORMAT_R8G8_SNORM,
- VK_FORMAT_R8G8_SINT,
- VK_FORMAT_R8G8_UINT,
- VK_FORMAT_R8_UNORM,
- VK_FORMAT_R8_SNORM,
- VK_FORMAT_R8_SINT,
- VK_FORMAT_R8_UINT,
- VK_FORMAT_B10G11R11_UFLOAT_PACK32,
- VK_FORMAT_R32_SFLOAT,
- VK_FORMAT_R32_UINT,
- VK_FORMAT_R32_SINT,
- VK_FORMAT_R16_SFLOAT,
- VK_FORMAT_R16G16B16A16_SFLOAT,
- VK_FORMAT_B8G8R8A8_UNORM,
- VK_FORMAT_B8G8R8A8_SRGB,
- VK_FORMAT_R4G4B4A4_UNORM_PACK16,
- VK_FORMAT_D32_SFLOAT,
- VK_FORMAT_D16_UNORM,
- VK_FORMAT_D16_UNORM_S8_UINT,
- VK_FORMAT_D24_UNORM_S8_UINT,
- VK_FORMAT_D32_SFLOAT_S8_UINT,
- VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
- VK_FORMAT_BC2_UNORM_BLOCK,
- VK_FORMAT_BC3_UNORM_BLOCK,
- VK_FORMAT_BC4_UNORM_BLOCK,
- VK_FORMAT_BC4_SNORM_BLOCK,
- VK_FORMAT_BC5_UNORM_BLOCK,
- VK_FORMAT_BC5_SNORM_BLOCK,
- VK_FORMAT_BC7_UNORM_BLOCK,
- VK_FORMAT_BC6H_UFLOAT_BLOCK,
- VK_FORMAT_BC6H_SFLOAT_BLOCK,
- VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
- VK_FORMAT_BC2_SRGB_BLOCK,
- VK_FORMAT_BC3_SRGB_BLOCK,
- VK_FORMAT_BC7_SRGB_BLOCK,
- VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
- VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
- VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
- VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
- VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
- VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
- VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
- VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
- VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
- VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
- };
- std::unordered_map<VkFormat, VkFormatProperties> format_properties;
- for (const auto format : formats) {
- format_properties.emplace(format, physical.GetFormatProperties(format));
- }
- return format_properties;
-}
-
-} // Anonymous namespace
-
-VKDevice::VKDevice(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface,
- const vk::InstanceDispatch& dld)
- : dld{dld}, physical{physical}, properties{physical.GetProperties()},
- format_properties{GetFormatProperties(physical, dld)} {
- SetupFamilies(surface);
- SetupFeatures();
-}
-
-VKDevice::~VKDevice() = default;
-
-bool VKDevice::Create() {
- const auto queue_cis = GetDeviceQueueCreateInfos();
- const std::vector extensions = LoadExtensions();
-
- VkPhysicalDeviceFeatures2 features2{
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
- .pNext = nullptr,
- };
- const void* first_next = &features2;
- void** next = &features2.pNext;
-
- features2.features = {
- .robustBufferAccess = false,
- .fullDrawIndexUint32 = false,
- .imageCubeArray = false,
- .independentBlend = true,
- .geometryShader = true,
- .tessellationShader = true,
- .sampleRateShading = false,
- .dualSrcBlend = false,
- .logicOp = false,
- .multiDrawIndirect = false,
- .drawIndirectFirstInstance = false,
- .depthClamp = true,
- .depthBiasClamp = true,
- .fillModeNonSolid = false,
- .depthBounds = false,
- .wideLines = false,
- .largePoints = true,
- .alphaToOne = false,
- .multiViewport = true,
- .samplerAnisotropy = true,
- .textureCompressionETC2 = false,
- .textureCompressionASTC_LDR = is_optimal_astc_supported,
- .textureCompressionBC = false,
- .occlusionQueryPrecise = true,
- .pipelineStatisticsQuery = false,
- .vertexPipelineStoresAndAtomics = true,
- .fragmentStoresAndAtomics = true,
- .shaderTessellationAndGeometryPointSize = false,
- .shaderImageGatherExtended = true,
- .shaderStorageImageExtendedFormats = false,
- .shaderStorageImageMultisample = false,
- .shaderStorageImageReadWithoutFormat = is_formatless_image_load_supported,
- .shaderStorageImageWriteWithoutFormat = true,
- .shaderUniformBufferArrayDynamicIndexing = false,
- .shaderSampledImageArrayDynamicIndexing = false,
- .shaderStorageBufferArrayDynamicIndexing = false,
- .shaderStorageImageArrayDynamicIndexing = false,
- .shaderClipDistance = false,
- .shaderCullDistance = false,
- .shaderFloat64 = false,
- .shaderInt64 = false,
- .shaderInt16 = false,
- .shaderResourceResidency = false,
- .shaderResourceMinLod = false,
- .sparseBinding = false,
- .sparseResidencyBuffer = false,
- .sparseResidencyImage2D = false,
- .sparseResidencyImage3D = false,
- .sparseResidency2Samples = false,
- .sparseResidency4Samples = false,
- .sparseResidency8Samples = false,
- .sparseResidency16Samples = false,
- .sparseResidencyAliased = false,
- .variableMultisampleRate = false,
- .inheritedQueries = false,
- };
-
- VkPhysicalDevice16BitStorageFeaturesKHR bit16_storage{
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR,
- .pNext = nullptr,
- .storageBuffer16BitAccess = false,
- .uniformAndStorageBuffer16BitAccess = true,
- .storagePushConstant16 = false,
- .storageInputOutput16 = false,
- };
- SetNext(next, bit16_storage);
-
- VkPhysicalDevice8BitStorageFeaturesKHR bit8_storage{
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES_KHR,
- .pNext = nullptr,
- .storageBuffer8BitAccess = false,
- .uniformAndStorageBuffer8BitAccess = true,
- .storagePushConstant8 = false,
- };
- SetNext(next, bit8_storage);
-
- VkPhysicalDeviceHostQueryResetFeaturesEXT host_query_reset{
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT,
- .hostQueryReset = true,
- };
- SetNext(next, host_query_reset);
-
- VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8;
- if (is_float16_supported) {
- float16_int8 = {
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR,
- .pNext = nullptr,
- .shaderFloat16 = true,
- .shaderInt8 = false,
- };
- SetNext(next, float16_int8);
- } else {
- LOG_INFO(Render_Vulkan, "Device doesn't support float16 natively");
- }
-
- if (!nv_viewport_swizzle) {
- LOG_INFO(Render_Vulkan, "Device doesn't support viewport swizzles");
- }
-
- VkPhysicalDeviceUniformBufferStandardLayoutFeaturesKHR std430_layout;
- if (khr_uniform_buffer_standard_layout) {
- std430_layout = {
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES_KHR,
- .pNext = nullptr,
- .uniformBufferStandardLayout = true,
- };
- SetNext(next, std430_layout);
- } else {
- LOG_INFO(Render_Vulkan, "Device doesn't support packed UBOs");
- }
-
- VkPhysicalDeviceIndexTypeUint8FeaturesEXT index_type_uint8;
- if (ext_index_type_uint8) {
- index_type_uint8 = {
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT,
- .pNext = nullptr,
- .indexTypeUint8 = true,
- };
- SetNext(next, index_type_uint8);
- } else {
- LOG_INFO(Render_Vulkan, "Device doesn't support uint8 indexes");
- }
-
- VkPhysicalDeviceTransformFeedbackFeaturesEXT transform_feedback;
- if (ext_transform_feedback) {
- transform_feedback = {
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT,
- .pNext = nullptr,
- .transformFeedback = true,
- .geometryStreams = true,
- };
- SetNext(next, transform_feedback);
- } else {
- LOG_INFO(Render_Vulkan, "Device doesn't support transform feedbacks");
- }
-
- VkPhysicalDeviceCustomBorderColorFeaturesEXT custom_border;
- if (ext_custom_border_color) {
- custom_border = {
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT,
- .pNext = nullptr,
- .customBorderColors = VK_TRUE,
- .customBorderColorWithoutFormat = VK_TRUE,
- };
- SetNext(next, custom_border);
- } else {
- LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors");
- }
-
- VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
- if (ext_extended_dynamic_state) {
- dynamic_state = {
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT,
- .pNext = nullptr,
- .extendedDynamicState = VK_TRUE,
- };
- SetNext(next, dynamic_state);
- } else {
- LOG_INFO(Render_Vulkan, "Device doesn't support extended dynamic state");
- }
-
- if (!ext_depth_range_unrestricted) {
- LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
- }
-
- VkDeviceDiagnosticsConfigCreateInfoNV diagnostics_nv;
- if (nv_device_diagnostics_config) {
- nsight_aftermath_tracker.Initialize();
-
- diagnostics_nv = {
- .sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV,
- .pNext = &features2,
- .flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV |
- VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV |
- VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV,
- };
- first_next = &diagnostics_nv;
- }
-
- logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld);
- if (!logical) {
- LOG_ERROR(Render_Vulkan, "Failed to create logical device");
- return false;
- }
-
- CollectTelemetryParameters();
-
- graphics_queue = logical.GetQueue(graphics_family);
- present_queue = logical.GetQueue(present_family);
-
- use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue();
- return true;
-}
-
-VkFormat VKDevice::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
- FormatType format_type) const {
- if (IsFormatSupported(wanted_format, wanted_usage, format_type)) {
- return wanted_format;
- }
- // The wanted format is not supported by hardware, search for alternatives
- const VkFormat* alternatives = GetFormatAlternatives(wanted_format);
- if (alternatives == nullptr) {
- UNREACHABLE_MSG("Format={} with usage={} and type={} has no defined alternatives and host "
- "hardware does not support it",
- wanted_format, wanted_usage, format_type);
- return wanted_format;
- }
-
- std::size_t i = 0;
- for (VkFormat alternative = *alternatives; alternative; alternative = alternatives[++i]) {
- if (!IsFormatSupported(alternative, wanted_usage, format_type)) {
- continue;
- }
- LOG_WARNING(Render_Vulkan,
- "Emulating format={} with alternative format={} with usage={} and type={}",
- wanted_format, alternative, wanted_usage, format_type);
- return alternative;
- }
-
- // No alternatives found, panic
- UNREACHABLE_MSG("Format={} with usage={} and type={} is not supported by the host hardware and "
- "doesn't support any of the alternatives",
- wanted_format, wanted_usage, format_type);
- return wanted_format;
-}
-
-void VKDevice::ReportLoss() const {
- LOG_CRITICAL(Render_Vulkan, "Device loss occured!");
-
- // Wait for the log to flush and for Nsight Aftermath to dump the results
- std::this_thread::sleep_for(std::chrono::seconds{3});
-}
-
-void VKDevice::SaveShader(const std::vector<u32>& spirv) const {
- nsight_aftermath_tracker.SaveShader(spirv);
-}
-
-bool VKDevice::IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const {
- // Disable for now to avoid converting ASTC twice.
- static constexpr std::array astc_formats = {
- VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
- VK_FORMAT_ASTC_5x4_UNORM_BLOCK, VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
- VK_FORMAT_ASTC_5x5_UNORM_BLOCK, VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x5_UNORM_BLOCK, VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_6x6_UNORM_BLOCK, VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x5_UNORM_BLOCK, VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x6_UNORM_BLOCK, VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_8x8_UNORM_BLOCK, VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x5_UNORM_BLOCK, VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x6_UNORM_BLOCK, VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x8_UNORM_BLOCK, VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
- VK_FORMAT_ASTC_10x10_UNORM_BLOCK, VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
- VK_FORMAT_ASTC_12x10_UNORM_BLOCK, VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
- VK_FORMAT_ASTC_12x12_UNORM_BLOCK, VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
- };
- if (!features.textureCompressionASTC_LDR) {
- return false;
- }
- const auto format_feature_usage{
- VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT |
- VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
- VK_FORMAT_FEATURE_TRANSFER_DST_BIT};
- for (const auto format : astc_formats) {
- const auto format_properties{physical.GetFormatProperties(format)};
- if (!(format_properties.optimalTilingFeatures & format_feature_usage)) {
- return false;
- }
- }
- return true;
-}
-
-bool VKDevice::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
- FormatType format_type) const {
- const auto it = format_properties.find(wanted_format);
- if (it == format_properties.end()) {
- UNIMPLEMENTED_MSG("Unimplemented format query={}", wanted_format);
- return true;
- }
- const auto supported_usage = GetFormatFeatures(it->second, format_type);
- return (supported_usage & wanted_usage) == wanted_usage;
-}
-
-bool VKDevice::IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface) {
- bool is_suitable = true;
- std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions;
-
- for (const auto& prop : physical.EnumerateDeviceExtensionProperties()) {
- for (std::size_t i = 0; i < REQUIRED_EXTENSIONS.size(); ++i) {
- if (available_extensions[i]) {
- continue;
- }
- const std::string_view name{prop.extensionName};
- available_extensions[i] = name == REQUIRED_EXTENSIONS[i];
- }
- }
- if (!available_extensions.all()) {
- for (std::size_t i = 0; i < REQUIRED_EXTENSIONS.size(); ++i) {
- if (available_extensions[i]) {
- continue;
- }
- LOG_ERROR(Render_Vulkan, "Missing required extension: {}", REQUIRED_EXTENSIONS[i]);
- is_suitable = false;
- }
- }
-
- bool has_graphics{}, has_present{};
- const std::vector queue_family_properties = physical.GetQueueFamilyProperties();
- for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) {
- const auto& family = queue_family_properties[i];
- if (family.queueCount == 0) {
- continue;
- }
- has_graphics |= family.queueFlags & VK_QUEUE_GRAPHICS_BIT;
- has_present |= physical.GetSurfaceSupportKHR(i, surface);
- }
- if (!has_graphics || !has_present) {
- LOG_ERROR(Render_Vulkan, "Device lacks a graphics and present queue");
- is_suitable = false;
- }
-
- // TODO(Rodrigo): Check if the device matches all requeriments.
- const auto properties{physical.GetProperties()};
- const auto& limits{properties.limits};
-
- constexpr u32 required_ubo_size = 65536;
- if (limits.maxUniformBufferRange < required_ubo_size) {
- LOG_ERROR(Render_Vulkan, "Device UBO size {} is too small, {} is required",
- limits.maxUniformBufferRange, required_ubo_size);
- is_suitable = false;
- }
-
- constexpr u32 required_num_viewports = 16;
- if (limits.maxViewports < required_num_viewports) {
- LOG_INFO(Render_Vulkan, "Device number of viewports {} is too small, {} is required",
- limits.maxViewports, required_num_viewports);
- is_suitable = false;
- }
-
- const auto features{physical.GetFeatures()};
- const std::array feature_report = {
- std::make_pair(features.vertexPipelineStoresAndAtomics, "vertexPipelineStoresAndAtomics"),
- std::make_pair(features.independentBlend, "independentBlend"),
- std::make_pair(features.depthClamp, "depthClamp"),
- std::make_pair(features.samplerAnisotropy, "samplerAnisotropy"),
- std::make_pair(features.largePoints, "largePoints"),
- std::make_pair(features.multiViewport, "multiViewport"),
- std::make_pair(features.depthBiasClamp, "depthBiasClamp"),
- std::make_pair(features.geometryShader, "geometryShader"),
- std::make_pair(features.tessellationShader, "tessellationShader"),
- std::make_pair(features.occlusionQueryPrecise, "occlusionQueryPrecise"),
- std::make_pair(features.fragmentStoresAndAtomics, "fragmentStoresAndAtomics"),
- std::make_pair(features.shaderImageGatherExtended, "shaderImageGatherExtended"),
- std::make_pair(features.shaderStorageImageWriteWithoutFormat,
- "shaderStorageImageWriteWithoutFormat"),
- };
- for (const auto& [supported, name] : feature_report) {
- if (supported) {
- continue;
- }
- LOG_ERROR(Render_Vulkan, "Missing required feature: {}", name);
- is_suitable = false;
- }
-
- if (!is_suitable) {
- LOG_ERROR(Render_Vulkan, "{} is not suitable", properties.deviceName);
- }
-
- return is_suitable;
-}
-
-std::vector<const char*> VKDevice::LoadExtensions() {
- std::vector<const char*> extensions;
- const auto Test = [&](const VkExtensionProperties& extension,
- std::optional<std::reference_wrapper<bool>> status, const char* name,
- bool push) {
- if (extension.extensionName != std::string_view(name)) {
- return;
- }
- if (push) {
- extensions.push_back(name);
- }
- if (status) {
- status->get() = true;
- }
- };
-
- extensions.reserve(7 + REQUIRED_EXTENSIONS.size());
- extensions.insert(extensions.begin(), REQUIRED_EXTENSIONS.begin(), REQUIRED_EXTENSIONS.end());
-
- bool has_khr_shader_float16_int8{};
- bool has_ext_subgroup_size_control{};
- bool has_ext_transform_feedback{};
- bool has_ext_custom_border_color{};
- bool has_ext_extended_dynamic_state{};
- for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) {
- Test(extension, nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true);
- Test(extension, khr_uniform_buffer_standard_layout,
- VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, true);
- Test(extension, has_khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME,
- false);
- Test(extension, ext_depth_range_unrestricted,
- VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, true);
- Test(extension, ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true);
- Test(extension, ext_shader_viewport_index_layer,
- VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, true);
- Test(extension, has_ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME,
- false);
- Test(extension, has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME,
- false);
- Test(extension, has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME,
- false);
- Test(extension, has_ext_extended_dynamic_state,
- VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false);
- if (Settings::values.renderer_debug) {
- Test(extension, nv_device_diagnostics_config,
- VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true);
- }
- }
-
- VkPhysicalDeviceFeatures2KHR features;
- features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
-
- VkPhysicalDeviceProperties2KHR properties;
- properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
-
- if (has_khr_shader_float16_int8) {
- VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8_features;
- float16_int8_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR;
- float16_int8_features.pNext = nullptr;
- features.pNext = &float16_int8_features;
-
- physical.GetFeatures2KHR(features);
- is_float16_supported = float16_int8_features.shaderFloat16;
- extensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME);
- }
-
- if (has_ext_subgroup_size_control) {
- VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroup_features;
- subgroup_features.sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT;
- subgroup_features.pNext = nullptr;
- features.pNext = &subgroup_features;
- physical.GetFeatures2KHR(features);
-
- VkPhysicalDeviceSubgroupSizeControlPropertiesEXT subgroup_properties;
- subgroup_properties.sType =
- VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES_EXT;
- subgroup_properties.pNext = nullptr;
- properties.pNext = &subgroup_properties;
- physical.GetProperties2KHR(properties);
-
- is_warp_potentially_bigger = subgroup_properties.maxSubgroupSize > GuestWarpSize;
-
- if (subgroup_features.subgroupSizeControl &&
- subgroup_properties.minSubgroupSize <= GuestWarpSize &&
- subgroup_properties.maxSubgroupSize >= GuestWarpSize) {
- extensions.push_back(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
- guest_warp_stages = subgroup_properties.requiredSubgroupSizeStages;
- }
- } else {
- is_warp_potentially_bigger = true;
- }
-
- if (has_ext_transform_feedback) {
- VkPhysicalDeviceTransformFeedbackFeaturesEXT tfb_features;
- tfb_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT;
- tfb_features.pNext = nullptr;
- features.pNext = &tfb_features;
- physical.GetFeatures2KHR(features);
-
- VkPhysicalDeviceTransformFeedbackPropertiesEXT tfb_properties;
- tfb_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
- tfb_properties.pNext = nullptr;
- properties.pNext = &tfb_properties;
- physical.GetProperties2KHR(properties);
-
- if (tfb_features.transformFeedback && tfb_features.geometryStreams &&
- tfb_properties.maxTransformFeedbackStreams >= 4 &&
- tfb_properties.maxTransformFeedbackBuffers && tfb_properties.transformFeedbackQueries &&
- tfb_properties.transformFeedbackDraw) {
- extensions.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
- ext_transform_feedback = true;
- }
- }
-
- if (has_ext_custom_border_color) {
- VkPhysicalDeviceCustomBorderColorFeaturesEXT border_features;
- border_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT;
- border_features.pNext = nullptr;
- features.pNext = &border_features;
- physical.GetFeatures2KHR(features);
-
- if (border_features.customBorderColors && border_features.customBorderColorWithoutFormat) {
- extensions.push_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
- ext_custom_border_color = true;
- }
- }
-
- if (has_ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_AMD_PROPRIETARY) {
- // AMD's proprietary driver supports VK_EXT_extended_dynamic_state but the <stride> field
- // seems to be bugged. Blacklisting it for now.
- LOG_WARNING(Render_Vulkan,
- "Blacklisting AMD proprietary from VK_EXT_extended_dynamic_state");
- } else if (has_ext_extended_dynamic_state) {
- VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
- dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
- dynamic_state.pNext = nullptr;
- features.pNext = &dynamic_state;
- physical.GetFeatures2KHR(features);
-
- if (dynamic_state.extendedDynamicState) {
- extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
- ext_extended_dynamic_state = true;
- }
- }
-
- return extensions;
-}
-
-void VKDevice::SetupFamilies(VkSurfaceKHR surface) {
- std::optional<u32> graphics_family_, present_family_;
-
- const std::vector queue_family_properties = physical.GetQueueFamilyProperties();
- for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) {
- if (graphics_family_ && present_family_)
- break;
-
- const auto& queue_family = queue_family_properties[i];
- if (queue_family.queueCount == 0)
- continue;
-
- if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
- graphics_family_ = i;
- }
- if (physical.GetSurfaceSupportKHR(i, surface)) {
- present_family_ = i;
- }
- }
- ASSERT(graphics_family_ && present_family_);
-
- graphics_family = *graphics_family_;
- present_family = *present_family_;
-}
-
-void VKDevice::SetupFeatures() {
- const auto supported_features{physical.GetFeatures()};
- is_formatless_image_load_supported = supported_features.shaderStorageImageReadWithoutFormat;
- is_optimal_astc_supported = IsOptimalAstcSupported(supported_features);
-}
-
-void VKDevice::CollectTelemetryParameters() {
- VkPhysicalDeviceDriverPropertiesKHR driver{
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR,
- .pNext = nullptr,
- };
-
- VkPhysicalDeviceProperties2KHR properties{
- .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
- .pNext = &driver,
- };
- physical.GetProperties2KHR(properties);
-
- driver_id = driver.driverID;
- vendor_name = driver.driverName;
-
- const std::vector extensions = physical.EnumerateDeviceExtensionProperties();
- reported_extensions.reserve(std::size(extensions));
- for (const auto& extension : extensions) {
- reported_extensions.emplace_back(extension.extensionName);
- }
-}
-
-std::vector<VkDeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() const {
- static constexpr float QUEUE_PRIORITY = 1.0f;
-
- std::unordered_set<u32> unique_queue_families{graphics_family, present_family};
- std::vector<VkDeviceQueueCreateInfo> queue_cis;
- queue_cis.reserve(unique_queue_families.size());
-
- for (const u32 queue_family : unique_queue_families) {
- auto& ci = queue_cis.emplace_back(VkDeviceQueueCreateInfo{
- .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .queueFamilyIndex = queue_family,
- .queueCount = 1,
- .pQueuePriorities = nullptr,
- });
- ci.pQueuePriorities = &QUEUE_PRIORITY;
- }
-
- return queue_cis;
-}
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
deleted file mode 100644
index 26a233db1..000000000
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ /dev/null
@@ -1,274 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-#include <string_view>
-#include <unordered_map>
-#include <vector>
-
-#include "common/common_types.h"
-#include "video_core/renderer_vulkan/nsight_aftermath_tracker.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-/// Format usage descriptor.
-enum class FormatType { Linear, Optimal, Buffer };
-
-/// Subgroup size of the guest emulated hardware (Nvidia has 32 threads per subgroup).
-const u32 GuestWarpSize = 32;
-
-/// Handles data specific to a physical device.
-class VKDevice final {
-public:
- explicit VKDevice(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface,
- const vk::InstanceDispatch& dld);
- ~VKDevice();
-
- /// Initializes the device. Returns true on success.
- bool Create();
-
- /**
- * Returns a format supported by the device for the passed requeriments.
- * @param wanted_format The ideal format to be returned. It may not be the returned format.
- * @param wanted_usage The usage that must be fulfilled even if the format is not supported.
- * @param format_type Format type usage.
- * @returns A format supported by the device.
- */
- VkFormat GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
- FormatType format_type) const;
-
- /// Reports a device loss.
- void ReportLoss() const;
-
- /// Reports a shader to Nsight Aftermath.
- void SaveShader(const std::vector<u32>& spirv) const;
-
- /// Returns the dispatch loader with direct function pointers of the device.
- const vk::DeviceDispatch& GetDispatchLoader() const {
- return dld;
- }
-
- /// Returns the logical device.
- const vk::Device& GetLogical() const {
- return logical;
- }
-
- /// Returns the physical device.
- vk::PhysicalDevice GetPhysical() const {
- return physical;
- }
-
- /// Returns the main graphics queue.
- vk::Queue GetGraphicsQueue() const {
- return graphics_queue;
- }
-
- /// Returns the main present queue.
- vk::Queue GetPresentQueue() const {
- return present_queue;
- }
-
- /// Returns main graphics queue family index.
- u32 GetGraphicsFamily() const {
- return graphics_family;
- }
-
- /// Returns main present queue family index.
- u32 GetPresentFamily() const {
- return present_family;
- }
-
- /// Returns the current Vulkan API version provided in Vulkan-formatted version numbers.
- u32 GetApiVersion() const {
- return properties.apiVersion;
- }
-
- /// Returns the current driver version provided in Vulkan-formatted version numbers.
- u32 GetDriverVersion() const {
- return properties.driverVersion;
- }
-
- /// Returns the device name.
- std::string_view GetModelName() const {
- return properties.deviceName;
- }
-
- /// Returns the driver ID.
- VkDriverIdKHR GetDriverID() const {
- return driver_id;
- }
-
- /// Returns uniform buffer alignment requeriment.
- VkDeviceSize GetUniformBufferAlignment() const {
- return properties.limits.minUniformBufferOffsetAlignment;
- }
-
- /// Returns storage alignment requeriment.
- VkDeviceSize GetStorageBufferAlignment() const {
- return properties.limits.minStorageBufferOffsetAlignment;
- }
-
- /// Returns the maximum range for storage buffers.
- VkDeviceSize GetMaxStorageBufferRange() const {
- return properties.limits.maxStorageBufferRange;
- }
-
- /// Returns the maximum size for push constants.
- VkDeviceSize GetMaxPushConstantsSize() const {
- return properties.limits.maxPushConstantsSize;
- }
-
- /// Returns the maximum size for shared memory.
- u32 GetMaxComputeSharedMemorySize() const {
- return properties.limits.maxComputeSharedMemorySize;
- }
-
- /// Returns true if ASTC is natively supported.
- bool IsOptimalAstcSupported() const {
- return is_optimal_astc_supported;
- }
-
- /// Returns true if the device supports float16 natively
- bool IsFloat16Supported() const {
- return is_float16_supported;
- }
-
- /// Returns true if the device warp size can potentially be bigger than guest's warp size.
- bool IsWarpSizePotentiallyBiggerThanGuest() const {
- return is_warp_potentially_bigger;
- }
-
- /// Returns true if the device can be forced to use the guest warp size.
- bool IsGuestWarpSizeSupported(VkShaderStageFlagBits stage) const {
- return guest_warp_stages & stage;
- }
-
- /// Returns true if formatless image load is supported.
- bool IsFormatlessImageLoadSupported() const {
- return is_formatless_image_load_supported;
- }
-
- /// Returns true if the device supports VK_NV_viewport_swizzle.
- bool IsNvViewportSwizzleSupported() const {
- return nv_viewport_swizzle;
- }
-
- /// Returns true if the device supports VK_EXT_scalar_block_layout.
- bool IsKhrUniformBufferStandardLayoutSupported() const {
- return khr_uniform_buffer_standard_layout;
- }
-
- /// Returns true if the device supports VK_EXT_index_type_uint8.
- bool IsExtIndexTypeUint8Supported() const {
- return ext_index_type_uint8;
- }
-
- /// Returns true if the device supports VK_EXT_depth_range_unrestricted.
- bool IsExtDepthRangeUnrestrictedSupported() const {
- return ext_depth_range_unrestricted;
- }
-
- /// Returns true if the device supports VK_EXT_shader_viewport_index_layer.
- bool IsExtShaderViewportIndexLayerSupported() const {
- return ext_shader_viewport_index_layer;
- }
-
- /// Returns true if the device supports VK_EXT_transform_feedback.
- bool IsExtTransformFeedbackSupported() const {
- return ext_transform_feedback;
- }
-
- /// Returns true if the device supports VK_EXT_custom_border_color.
- bool IsExtCustomBorderColorSupported() const {
- return ext_custom_border_color;
- }
-
- /// Returns true if the device supports VK_EXT_extended_dynamic_state.
- bool IsExtExtendedDynamicStateSupported() const {
- return ext_extended_dynamic_state;
- }
-
- /// Returns the vendor name reported from Vulkan.
- std::string_view GetVendorName() const {
- return vendor_name;
- }
-
- /// Returns the list of available extensions.
- const std::vector<std::string>& GetAvailableExtensions() const {
- return reported_extensions;
- }
-
- /// Returns true if the setting for async shader compilation is enabled.
- bool UseAsynchronousShaders() const {
- return use_asynchronous_shaders;
- }
-
- /// Checks if the physical device is suitable.
- static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface);
-
-private:
- /// Loads extensions into a vector and stores available ones in this object.
- std::vector<const char*> LoadExtensions();
-
- /// Sets up queue families.
- void SetupFamilies(VkSurfaceKHR surface);
-
- /// Sets up device features.
- void SetupFeatures();
-
- /// Collects telemetry information from the device.
- void CollectTelemetryParameters();
-
- /// Returns a list of queue initialization descriptors.
- std::vector<VkDeviceQueueCreateInfo> GetDeviceQueueCreateInfos() const;
-
- /// Returns true if ASTC textures are natively supported.
- bool IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const;
-
- /// Returns true if a format is supported.
- bool IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
- FormatType format_type) const;
-
- vk::DeviceDispatch dld; ///< Device function pointers.
- vk::PhysicalDevice physical; ///< Physical device.
- VkPhysicalDeviceProperties properties; ///< Device properties.
- vk::Device logical; ///< Logical device.
- vk::Queue graphics_queue; ///< Main graphics queue.
- vk::Queue present_queue; ///< Main present queue.
- u32 graphics_family{}; ///< Main graphics queue family index.
- u32 present_family{}; ///< Main present queue family index.
- VkDriverIdKHR driver_id{}; ///< Driver ID.
- VkShaderStageFlags guest_warp_stages{}; ///< Stages where the guest warp size can be forced.ed
- bool is_optimal_astc_supported{}; ///< Support for native ASTC.
- bool is_float16_supported{}; ///< Support for float16 arithmetics.
- bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest.
- bool is_formatless_image_load_supported{}; ///< Support for shader image read without format.
- bool nv_viewport_swizzle{}; ///< Support for VK_NV_viewport_swizzle.
- bool khr_uniform_buffer_standard_layout{}; ///< Support for std430 on UBOs.
- bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8.
- bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.
- bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
- bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
- bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color.
- bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
- bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
-
- // Asynchronous Graphics Pipeline setting
- bool use_asynchronous_shaders{}; ///< Setting to use asynchronous shaders/graphics pipeline
-
- // Telemetry parameters
- std::string vendor_name; ///< Device's driver name.
- std::vector<std::string> reported_extensions; ///< Reported Vulkan extensions.
-
- /// Format properties dictionary.
- std::unordered_map<VkFormat, VkFormatProperties> format_properties;
-
- /// Nsight Aftermath GPU crash tracker
- NsightAftermathTracker nsight_aftermath_tracker;
-};
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
index d7f65d435..3bec48d14 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
@@ -3,23 +3,21 @@
// Refer to the license.txt file included.
#include <memory>
-#include <thread>
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_fence_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-InnerFence::InnerFence(const VKDevice& device, VKScheduler& scheduler, u32 payload, bool is_stubbed)
- : VideoCommon::FenceBase(payload, is_stubbed), device{device}, scheduler{scheduler} {}
+InnerFence::InnerFence(VKScheduler& scheduler_, u32 payload_, bool is_stubbed_)
+ : FenceBase{payload_, is_stubbed_}, scheduler{scheduler_} {}
-InnerFence::InnerFence(const VKDevice& device, VKScheduler& scheduler, GPUVAddr address,
- u32 payload, bool is_stubbed)
- : VideoCommon::FenceBase(address, payload, is_stubbed), device{device}, scheduler{scheduler} {}
+InnerFence::InnerFence(VKScheduler& scheduler_, GPUVAddr address_, u32 payload_, bool is_stubbed_)
+ : FenceBase{address_, payload_, is_stubbed_}, scheduler{scheduler_} {}
InnerFence::~InnerFence() = default;
@@ -27,63 +25,38 @@ void InnerFence::Queue() {
if (is_stubbed) {
return;
}
- ASSERT(!event);
-
- event = device.GetLogical().CreateNewEvent();
- ticks = scheduler.Ticks();
-
- scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([event = *event](vk::CommandBuffer cmdbuf) {
- cmdbuf.SetEvent(event, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
- });
+ // Get the current tick so we can wait for it
+ wait_tick = scheduler.CurrentTick();
+ scheduler.Flush();
}
bool InnerFence::IsSignaled() const {
if (is_stubbed) {
return true;
}
- ASSERT(event);
- return IsEventSignalled();
+ return scheduler.IsFree(wait_tick);
}
void InnerFence::Wait() {
if (is_stubbed) {
return;
}
- ASSERT(event);
-
- if (ticks >= scheduler.Ticks()) {
- scheduler.Flush();
- }
- while (!IsEventSignalled()) {
- std::this_thread::yield();
- }
-}
-
-bool InnerFence::IsEventSignalled() const {
- switch (const VkResult result = event.GetStatus()) {
- case VK_EVENT_SET:
- return true;
- case VK_EVENT_RESET:
- return false;
- default:
- throw vk::Exception(result);
- }
+ scheduler.Wait(wait_tick);
}
-VKFenceManager::VKFenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKTextureCache& texture_cache, VKBufferCache& buffer_cache,
- VKQueryCache& query_cache)
- : GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, query_cache),
- device{device}, scheduler{scheduler} {}
+VKFenceManager::VKFenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
+ TextureCache& texture_cache_, BufferCache& buffer_cache_,
+ VKQueryCache& query_cache_, const Device& device_,
+ VKScheduler& scheduler_)
+ : GenericFenceManager{rasterizer_, gpu_, texture_cache_, buffer_cache_, query_cache_},
+ scheduler{scheduler_} {}
Fence VKFenceManager::CreateFence(u32 value, bool is_stubbed) {
- return std::make_shared<InnerFence>(device, scheduler, value, is_stubbed);
+ return std::make_shared<InnerFence>(scheduler, value, is_stubbed);
}
Fence VKFenceManager::CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) {
- return std::make_shared<InnerFence>(device, scheduler, addr, value, is_stubbed);
+ return std::make_shared<InnerFence>(scheduler, addr, value, is_stubbed);
}
void VKFenceManager::QueueFence(Fence& fence) {
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h
index 043fe7947..2f8322d29 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.h
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.h
@@ -8,7 +8,8 @@
#include "video_core/fence_manager.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Core {
class System;
@@ -20,18 +21,14 @@ class RasterizerInterface;
namespace Vulkan {
-class VKBufferCache;
-class VKDevice;
+class Device;
class VKQueryCache;
class VKScheduler;
-class VKTextureCache;
class InnerFence : public VideoCommon::FenceBase {
public:
- explicit InnerFence(const VKDevice& device, VKScheduler& scheduler, u32 payload,
- bool is_stubbed);
- explicit InnerFence(const VKDevice& device, VKScheduler& scheduler, GPUVAddr address,
- u32 payload, bool is_stubbed);
+ explicit InnerFence(VKScheduler& scheduler_, u32 payload_, bool is_stubbed_);
+ explicit InnerFence(VKScheduler& scheduler_, GPUVAddr address_, u32 payload_, bool is_stubbed_);
~InnerFence();
void Queue();
@@ -41,24 +38,20 @@ public:
void Wait();
private:
- bool IsEventSignalled() const;
-
- const VKDevice& device;
VKScheduler& scheduler;
- vk::Event event;
- u64 ticks = 0;
+ u64 wait_tick = 0;
};
using Fence = std::shared_ptr<InnerFence>;
using GenericFenceManager =
- VideoCommon::FenceManager<Fence, VKTextureCache, VKBufferCache, VKQueryCache>;
+ VideoCommon::FenceManager<Fence, TextureCache, BufferCache, VKQueryCache>;
class VKFenceManager final : public GenericFenceManager {
public:
- explicit VKFenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKTextureCache& texture_cache, VKBufferCache& buffer_cache,
- VKQueryCache& query_cache);
+ explicit VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
+ TextureCache& texture_cache, BufferCache& buffer_cache,
+ VKQueryCache& query_cache, const Device& device,
+ VKScheduler& scheduler);
protected:
Fence CreateFence(u32 value, bool is_stubbed) override;
@@ -68,7 +61,6 @@ protected:
void WaitFence(Fence& fence) override;
private:
- const VKDevice& device;
VKScheduler& scheduler;
};
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 2e46c6278..fc6dd83eb 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -12,13 +12,12 @@
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
-#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
@@ -69,23 +68,45 @@ VkViewportSwizzleNV UnpackViewportSwizzle(u16 swizzle) {
};
}
+VkSampleCountFlagBits ConvertMsaaMode(Tegra::Texture::MsaaMode msaa_mode) {
+ switch (msaa_mode) {
+ case Tegra::Texture::MsaaMode::Msaa1x1:
+ return VK_SAMPLE_COUNT_1_BIT;
+ case Tegra::Texture::MsaaMode::Msaa2x1:
+ case Tegra::Texture::MsaaMode::Msaa2x1_D3D:
+ return VK_SAMPLE_COUNT_2_BIT;
+ case Tegra::Texture::MsaaMode::Msaa2x2:
+ case Tegra::Texture::MsaaMode::Msaa2x2_VC4:
+ case Tegra::Texture::MsaaMode::Msaa2x2_VC12:
+ return VK_SAMPLE_COUNT_4_BIT;
+ case Tegra::Texture::MsaaMode::Msaa4x2:
+ case Tegra::Texture::MsaaMode::Msaa4x2_D3D:
+ case Tegra::Texture::MsaaMode::Msaa4x2_VC8:
+ case Tegra::Texture::MsaaMode::Msaa4x2_VC24:
+ return VK_SAMPLE_COUNT_8_BIT;
+ case Tegra::Texture::MsaaMode::Msaa4x4:
+ return VK_SAMPLE_COUNT_16_BIT;
+ default:
+ UNREACHABLE_MSG("Invalid msaa_mode={}", static_cast<int>(msaa_mode));
+ return VK_SAMPLE_COUNT_1_BIT;
+ }
+}
+
} // Anonymous namespace
-VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
- VKRenderPassCache& renderpass_cache,
+VKGraphicsPipeline::VKGraphicsPipeline(const Device& device_, VKScheduler& scheduler_,
+ VKDescriptorPool& descriptor_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_,
const GraphicsPipelineCacheKey& key,
vk::Span<VkDescriptorSetLayoutBinding> bindings,
- const SPIRVProgram& program)
- : device{device}, scheduler{scheduler}, cache_key{key}, hash{cache_key.Hash()},
+ const SPIRVProgram& program, u32 num_color_buffers)
+ : device{device_}, scheduler{scheduler_}, cache_key{key}, hash{cache_key.Hash()},
descriptor_set_layout{CreateDescriptorSetLayout(bindings)},
- descriptor_allocator{descriptor_pool, *descriptor_set_layout},
- update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()},
- descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules(
- program)},
- renderpass{renderpass_cache.GetRenderPass(cache_key.renderpass_params)},
- pipeline{CreatePipeline(cache_key.renderpass_params, program)} {}
+ descriptor_allocator{descriptor_pool_, *descriptor_set_layout},
+ update_descriptor_queue{update_descriptor_queue_}, layout{CreatePipelineLayout()},
+ descriptor_template{CreateDescriptorUpdateTemplate(program)},
+ modules(CreateShaderModules(program)),
+ pipeline(CreatePipeline(program, cache_key.renderpass, num_color_buffers)) {}
VKGraphicsPipeline::~VKGraphicsPipeline() = default;
@@ -93,7 +114,7 @@ VkDescriptorSet VKGraphicsPipeline::CommitDescriptorSet() {
if (!descriptor_template) {
return {};
}
- const auto set = descriptor_allocator.Commit(scheduler.GetFence());
+ const VkDescriptorSet set = descriptor_allocator.Commit();
update_descriptor_queue.Send(*descriptor_template, set);
return set;
}
@@ -159,10 +180,12 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
+ .codeSize = 0,
+ .pCode = nullptr,
};
- std::vector<vk::ShaderModule> modules;
- modules.reserve(Maxwell::MaxShaderStage);
+ std::vector<vk::ShaderModule> shader_modules;
+ shader_modules.reserve(Maxwell::MaxShaderStage);
for (std::size_t i = 0; i < Maxwell::MaxShaderStage; ++i) {
const auto& stage = program[i];
if (!stage) {
@@ -173,13 +196,14 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
ci.codeSize = stage->code.size() * sizeof(u32);
ci.pCode = stage->code.data();
- modules.push_back(device.GetLogical().CreateShaderModule(ci));
+ shader_modules.push_back(device.GetLogical().CreateShaderModule(ci));
}
- return modules;
+ return shader_modules;
}
-vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params,
- const SPIRVProgram& program) const {
+vk::Pipeline VKGraphicsPipeline::CreatePipeline(const SPIRVProgram& program,
+ VkRenderPass renderpass,
+ u32 num_color_buffers) const {
const auto& state = cache_key.fixed_state;
const auto& viewport_swizzles = state.viewport_swizzles;
@@ -189,11 +213,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
// state is ignored
dynamic.raw1 = 0;
dynamic.raw2 = 0;
- for (FixedPipelineState::VertexBinding& binding : dynamic.vertex_bindings) {
- // Enable all vertex bindings
- binding.raw = 0;
- binding.enabled.Assign(1);
- }
+ dynamic.vertex_strides.fill(0);
} else {
dynamic = state.dynamic_state;
}
@@ -201,19 +221,13 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
std::vector<VkVertexInputBindingDescription> vertex_bindings;
std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors;
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
- const auto& binding = dynamic.vertex_bindings[index];
- if (!binding.enabled) {
- continue;
- }
const bool instanced = state.binding_divisors[index] != 0;
const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
-
vertex_bindings.push_back({
.binding = static_cast<u32>(index),
- .stride = binding.stride,
+ .stride = dynamic.vertex_strides[index],
.inputRate = rate,
});
-
if (instanced) {
vertex_binding_divisors.push_back({
.binding = static_cast<u32>(index),
@@ -229,7 +243,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
if (!attribute.enabled) {
continue;
}
- if (input_attributes.find(static_cast<u32>(index)) == input_attributes.end()) {
+ if (!input_attributes.contains(static_cast<u32>(index))) {
// Skip attributes not used by the vertex shaders.
continue;
}
@@ -261,12 +275,12 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
vertex_input_ci.pNext = &input_divisor_ci;
}
- const auto input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, dynamic.Topology());
+ const auto input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, state.topology);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .topology = MaxwellToVK::PrimitiveTopology(device, dynamic.Topology()),
+ .topology = MaxwellToVK::PrimitiveTopology(device, state.topology),
.primitiveRestartEnable = state.primitive_restart_enable != 0 &&
SupportsPrimitiveRestart(input_assembly_topology),
};
@@ -289,8 +303,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
};
std::array<VkViewportSwizzleNV, Maxwell::NumViewports> swizzles;
- std::transform(viewport_swizzles.begin(), viewport_swizzles.end(), swizzles.begin(),
- UnpackViewportSwizzle);
+ std::ranges::transform(viewport_swizzles, swizzles.begin(), UnpackViewportSwizzle);
VkPipelineViewportSwizzleStateCreateInfoNV swizzle_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SWIZZLE_STATE_CREATE_INFO_NV,
.pNext = nullptr,
@@ -311,8 +324,8 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
.rasterizerDiscardEnable =
static_cast<VkBool32>(state.rasterize_enable == 0 ? VK_TRUE : VK_FALSE),
.polygonMode = VK_POLYGON_MODE_FILL,
- .cullMode =
- dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE,
+ .cullMode = static_cast<VkCullModeFlags>(
+ dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE),
.frontFace = MaxwellToVK::FrontFace(dynamic.FrontFace()),
.depthBiasEnable = state.depth_bias_enable,
.depthBiasConstantFactor = 0.0f,
@@ -325,7 +338,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
+ .rasterizationSamples = ConvertMsaaMode(state.msaa_mode),
.sampleShadingEnable = VK_FALSE,
.minSampleShading = 0.0f,
.pSampleMask = nullptr,
@@ -351,8 +364,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
};
std::array<VkPipelineColorBlendAttachmentState, Maxwell::NumRenderTargets> cb_attachments;
- const auto num_attachments = static_cast<std::size_t>(renderpass_params.num_color_attachments);
- for (std::size_t index = 0; index < num_attachments; ++index) {
+ for (std::size_t index = 0; index < num_color_buffers; ++index) {
static constexpr std::array COMPONENT_TABLE{
VK_COLOR_COMPONENT_R_BIT,
VK_COLOR_COMPONENT_G_BIT,
@@ -386,8 +398,9 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
.flags = 0,
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY,
- .attachmentCount = static_cast<u32>(num_attachments),
+ .attachmentCount = num_color_buffers,
.pAttachments = cb_attachments.data(),
+ .blendConstants = {},
};
std::vector dynamic_states{
@@ -400,7 +413,6 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
static constexpr std::array extended{
VK_DYNAMIC_STATE_CULL_MODE_EXT,
VK_DYNAMIC_STATE_FRONT_FACE_EXT,
- VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT,
VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT,
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT,
@@ -446,8 +458,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
stage_ci.pNext = &subgroup_size_ci;
}
}
-
- const VkGraphicsPipelineCreateInfo ci{
+ return device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -467,8 +478,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
.subpass = 0,
.basePipelineHandle = nullptr,
.basePipelineIndex = 0,
- };
- return device.GetLogical().CreateGraphicsPipeline(ci);
+ });
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index 58aa35efd..8b6a98fe0 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -8,20 +8,19 @@
#include <optional>
#include <vector>
+#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
-#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
struct GraphicsPipelineCacheKey {
- RenderPassParams renderpass_params;
- u32 padding;
+ VkRenderPass renderpass;
std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
FixedPipelineState fixed_state;
@@ -34,16 +33,15 @@ struct GraphicsPipelineCacheKey {
}
std::size_t Size() const noexcept {
- return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
+ return sizeof(renderpass) + sizeof(shaders) + fixed_state.Size();
}
};
static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>);
+class Device;
class VKDescriptorPool;
-class VKDevice;
-class VKRenderPassCache;
class VKScheduler;
class VKUpdateDescriptorQueue;
@@ -51,13 +49,12 @@ using SPIRVProgram = std::array<std::optional<SPIRVShader>, Maxwell::MaxShaderSt
class VKGraphicsPipeline final {
public:
- explicit VKGraphicsPipeline(const VKDevice& device, VKScheduler& scheduler,
+ explicit VKGraphicsPipeline(const Device& device_, VKScheduler& scheduler_,
VKDescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
- VKRenderPassCache& renderpass_cache,
+ VKUpdateDescriptorQueue& update_descriptor_queue_,
const GraphicsPipelineCacheKey& key,
vk::Span<VkDescriptorSetLayoutBinding> bindings,
- const SPIRVProgram& program);
+ const SPIRVProgram& program, u32 num_color_buffers);
~VKGraphicsPipeline();
VkDescriptorSet CommitDescriptorSet();
@@ -70,10 +67,6 @@ public:
return *layout;
}
- VkRenderPass GetRenderPass() const {
- return renderpass;
- }
-
GraphicsPipelineCacheKey GetCacheKey() const {
return cache_key;
}
@@ -89,10 +82,10 @@ private:
std::vector<vk::ShaderModule> CreateShaderModules(const SPIRVProgram& program) const;
- vk::Pipeline CreatePipeline(const RenderPassParams& renderpass_params,
- const SPIRVProgram& program) const;
+ vk::Pipeline CreatePipeline(const SPIRVProgram& program, VkRenderPass renderpass,
+ u32 num_color_buffers) const;
- const VKDevice& device;
+ const Device& device;
VKScheduler& scheduler;
const GraphicsPipelineCacheKey cache_key;
const u64 hash;
@@ -104,7 +97,6 @@ private:
vk::DescriptorUpdateTemplateKHR descriptor_template;
std::vector<vk::ShaderModule> modules;
- VkRenderPass renderpass;
vk::Pipeline pipeline;
};
diff --git a/src/video_core/renderer_vulkan/vk_image.cpp b/src/video_core/renderer_vulkan/vk_image.cpp
deleted file mode 100644
index 1c418ea17..000000000
--- a/src/video_core/renderer_vulkan/vk_image.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <memory>
-#include <vector>
-
-#include "common/assert.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_image.h"
-#include "video_core/renderer_vulkan/vk_scheduler.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-VKImage::VKImage(const VKDevice& device, VKScheduler& scheduler, const VkImageCreateInfo& image_ci,
- VkImageAspectFlags aspect_mask)
- : device{device}, scheduler{scheduler}, format{image_ci.format}, aspect_mask{aspect_mask},
- image_num_layers{image_ci.arrayLayers}, image_num_levels{image_ci.mipLevels} {
- UNIMPLEMENTED_IF_MSG(image_ci.queueFamilyIndexCount != 0,
- "Queue family tracking is not implemented");
-
- image = device.GetLogical().CreateImage(image_ci);
-
- const u32 num_ranges = image_num_layers * image_num_levels;
- barriers.resize(num_ranges);
- subrange_states.resize(num_ranges, {{}, image_ci.initialLayout});
-}
-
-VKImage::~VKImage() = default;
-
-void VKImage::Transition(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels,
- VkPipelineStageFlags new_stage_mask, VkAccessFlags new_access,
- VkImageLayout new_layout) {
- if (!HasChanged(base_layer, num_layers, base_level, num_levels, new_access, new_layout)) {
- return;
- }
-
- std::size_t cursor = 0;
- for (u32 layer_it = 0; layer_it < num_layers; ++layer_it) {
- for (u32 level_it = 0; level_it < num_levels; ++level_it, ++cursor) {
- const u32 layer = base_layer + layer_it;
- const u32 level = base_level + level_it;
- auto& state = GetSubrangeState(layer, level);
- auto& barrier = barriers[cursor];
- barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
- barrier.pNext = nullptr;
- barrier.srcAccessMask = state.access;
- barrier.dstAccessMask = new_access;
- barrier.oldLayout = state.layout;
- barrier.newLayout = new_layout;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.image = *image;
- barrier.subresourceRange.aspectMask = aspect_mask;
- barrier.subresourceRange.baseMipLevel = level;
- barrier.subresourceRange.levelCount = 1;
- barrier.subresourceRange.baseArrayLayer = layer;
- barrier.subresourceRange.layerCount = 1;
- state.access = new_access;
- state.layout = new_layout;
- }
- }
-
- scheduler.RequestOutsideRenderPassOperationContext();
-
- scheduler.Record([barriers = barriers, cursor](vk::CommandBuffer cmdbuf) {
- // TODO(Rodrigo): Implement a way to use the latest stage across subresources.
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
- VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, {}, {},
- vk::Span(barriers.data(), cursor));
- });
-}
-
-bool VKImage::HasChanged(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels,
- VkAccessFlags new_access, VkImageLayout new_layout) noexcept {
- const bool is_full_range = base_layer == 0 && num_layers == image_num_layers &&
- base_level == 0 && num_levels == image_num_levels;
- if (!is_full_range) {
- state_diverged = true;
- }
-
- if (!state_diverged) {
- auto& state = GetSubrangeState(0, 0);
- if (state.access != new_access || state.layout != new_layout) {
- return true;
- }
- }
-
- for (u32 layer_it = 0; layer_it < num_layers; ++layer_it) {
- for (u32 level_it = 0; level_it < num_levels; ++level_it) {
- const u32 layer = base_layer + layer_it;
- const u32 level = base_level + level_it;
- auto& state = GetSubrangeState(layer, level);
- if (state.access != new_access || state.layout != new_layout) {
- return true;
- }
- }
- }
- return false;
-}
-
-void VKImage::CreatePresentView() {
- // Image type has to be 2D to be presented.
- present_view = device.GetLogical().CreateImageView({
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .image = *image,
- .viewType = VK_IMAGE_VIEW_TYPE_2D,
- .format = format,
- .components =
- {
- .r = VK_COMPONENT_SWIZZLE_IDENTITY,
- .g = VK_COMPONENT_SWIZZLE_IDENTITY,
- .b = VK_COMPONENT_SWIZZLE_IDENTITY,
- .a = VK_COMPONENT_SWIZZLE_IDENTITY,
- },
- .subresourceRange =
- {
- .aspectMask = aspect_mask,
- .baseMipLevel = 0,
- .levelCount = 1,
- .baseArrayLayer = 0,
- .layerCount = 1,
- },
- });
-}
-
-VKImage::SubrangeState& VKImage::GetSubrangeState(u32 layer, u32 level) noexcept {
- return subrange_states[static_cast<std::size_t>(layer * image_num_levels) +
- static_cast<std::size_t>(level)];
-}
-
-} // namespace Vulkan \ No newline at end of file
diff --git a/src/video_core/renderer_vulkan/vk_image.h b/src/video_core/renderer_vulkan/vk_image.h
deleted file mode 100644
index b4d7229e5..000000000
--- a/src/video_core/renderer_vulkan/vk_image.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "common/common_types.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-class VKDevice;
-class VKScheduler;
-
-class VKImage {
-public:
- explicit VKImage(const VKDevice& device, VKScheduler& scheduler,
- const VkImageCreateInfo& image_ci, VkImageAspectFlags aspect_mask);
- ~VKImage();
-
- /// Records in the passed command buffer an image transition and updates the state of the image.
- void Transition(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels,
- VkPipelineStageFlags new_stage_mask, VkAccessFlags new_access,
- VkImageLayout new_layout);
-
- /// Returns a view compatible with presentation, the image has to be 2D.
- VkImageView GetPresentView() {
- if (!present_view) {
- CreatePresentView();
- }
- return *present_view;
- }
-
- /// Returns the Vulkan image handler.
- const vk::Image& GetHandle() const {
- return image;
- }
-
- /// Returns the Vulkan format for this image.
- VkFormat GetFormat() const {
- return format;
- }
-
- /// Returns the Vulkan aspect mask.
- VkImageAspectFlags GetAspectMask() const {
- return aspect_mask;
- }
-
-private:
- struct SubrangeState final {
- VkAccessFlags access = 0; ///< Current access bits.
- VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; ///< Current image layout.
- };
-
- bool HasChanged(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels,
- VkAccessFlags new_access, VkImageLayout new_layout) noexcept;
-
- /// Creates a presentation view.
- void CreatePresentView();
-
- /// Returns the subrange state for a layer and layer.
- SubrangeState& GetSubrangeState(u32 layer, u32 level) noexcept;
-
- const VKDevice& device; ///< Device handler.
- VKScheduler& scheduler; ///< Device scheduler.
-
- const VkFormat format; ///< Vulkan format.
- const VkImageAspectFlags aspect_mask; ///< Vulkan aspect mask.
- const u32 image_num_layers; ///< Number of layers.
- const u32 image_num_levels; ///< Number of mipmap levels.
-
- vk::Image image; ///< Image handle.
- vk::ImageView present_view; ///< Image view compatible with presentation.
-
- std::vector<VkImageMemoryBarrier> barriers; ///< Pool of barriers.
- std::vector<SubrangeState> subrange_states; ///< Current subrange state.
-
- bool state_diverged = false; ///< True when subresources mismatch in layout.
-};
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
new file mode 100644
index 000000000..56ec5e380
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -0,0 +1,56 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <atomic>
+#include <chrono>
+
+#include "core/settings.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+using namespace std::chrono_literals;
+
+MasterSemaphore::MasterSemaphore(const Device& device) {
+ static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR,
+ .pNext = nullptr,
+ .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE_KHR,
+ .initialValue = 0,
+ };
+ static constexpr VkSemaphoreCreateInfo semaphore_ci{
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ .pNext = &semaphore_type_ci,
+ .flags = 0,
+ };
+ semaphore = device.GetLogical().CreateSemaphore(semaphore_ci);
+
+ if (!Settings::values.renderer_debug) {
+ return;
+ }
+ // Validation layers have a bug where they fail to track resource usage when using timeline
+ // semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have
+ // a separate thread waiting for each timeline semaphore value.
+ debug_thread = std::thread([this] {
+ u64 counter = 0;
+ while (!shutdown) {
+ if (semaphore.Wait(counter, 10'000'000)) {
+ ++counter;
+ }
+ }
+ });
+}
+
+MasterSemaphore::~MasterSemaphore() {
+ shutdown = true;
+
+ // This thread might not be started
+ if (debug_thread.joinable()) {
+ debug_thread.join();
+ }
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
new file mode 100644
index 000000000..2c7ed654d
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -0,0 +1,75 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <thread>
+
+#include "common/common_types.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+class Device;
+
+class MasterSemaphore {
+public:
+ explicit MasterSemaphore(const Device& device);
+ ~MasterSemaphore();
+
+ /// Returns the current logical tick.
+ [[nodiscard]] u64 CurrentTick() const noexcept {
+ return current_tick.load(std::memory_order_relaxed);
+ }
+
+ /// Returns the last known GPU tick.
+ [[nodiscard]] u64 KnownGpuTick() const noexcept {
+ return gpu_tick.load(std::memory_order_relaxed);
+ }
+
+ /// Returns the timeline semaphore handle.
+ [[nodiscard]] VkSemaphore Handle() const noexcept {
+ return *semaphore;
+ }
+
+ /// Returns true when a tick has been hit by the GPU.
+ [[nodiscard]] bool IsFree(u64 tick) {
+ return gpu_tick.load(std::memory_order_relaxed) >= tick;
+ }
+
+ /// Advance to the logical tick.
+ void NextTick() noexcept {
+ ++current_tick;
+ }
+
+ /// Refresh the known GPU tick
+ void Refresh() {
+ gpu_tick.store(semaphore.GetCounter(), std::memory_order_relaxed);
+ }
+
+ /// Waits for a tick to be hit on the GPU
+ void Wait(u64 tick) {
+ // No need to wait if the GPU is ahead of the tick
+ if (IsFree(tick)) {
+ return;
+ }
+ // Update the GPU tick and try again
+ Refresh();
+ if (IsFree(tick)) {
+ return;
+ }
+ // If none of the above is hit, fallback to a regular wait
+ semaphore.Wait(tick);
+ }
+
+private:
+ vk::Semaphore semaphore; ///< Timeline semaphore.
+ std::atomic<u64> gpu_tick{0}; ///< Current known GPU tick.
+ std::atomic<u64> current_tick{1}; ///< Current logical tick.
+ std::atomic<bool> shutdown{false}; ///< True when the object is being destroyed.
+ std::thread debug_thread; ///< Debug thread to workaround validation layer bugs.
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.cpp b/src/video_core/renderer_vulkan/vk_memory_manager.cpp
deleted file mode 100644
index 24c8960ac..000000000
--- a/src/video_core/renderer_vulkan/vk_memory_manager.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <optional>
-#include <tuple>
-#include <vector>
-
-#include "common/alignment.h"
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-namespace {
-
-u64 GetAllocationChunkSize(u64 required_size) {
- static constexpr u64 sizes[] = {16ULL << 20, 32ULL << 20, 64ULL << 20, 128ULL << 20};
- auto it = std::lower_bound(std::begin(sizes), std::end(sizes), required_size);
- return it != std::end(sizes) ? *it : Common::AlignUp(required_size, 256ULL << 20);
-}
-
-} // Anonymous namespace
-
-class VKMemoryAllocation final {
-public:
- explicit VKMemoryAllocation(const VKDevice& device, vk::DeviceMemory memory,
- VkMemoryPropertyFlags properties, u64 allocation_size, u32 type)
- : device{device}, memory{std::move(memory)}, properties{properties},
- allocation_size{allocation_size}, shifted_type{ShiftType(type)} {}
-
- VKMemoryCommit Commit(VkDeviceSize commit_size, VkDeviceSize alignment) {
- auto found = TryFindFreeSection(free_iterator, allocation_size,
- static_cast<u64>(commit_size), static_cast<u64>(alignment));
- if (!found) {
- found = TryFindFreeSection(0, free_iterator, static_cast<u64>(commit_size),
- static_cast<u64>(alignment));
- if (!found) {
- // Signal out of memory, it'll try to do more allocations.
- return nullptr;
- }
- }
- auto commit = std::make_unique<VKMemoryCommitImpl>(device, this, memory, *found,
- *found + commit_size);
- commits.push_back(commit.get());
-
- // Last commit's address is highly probable to be free.
- free_iterator = *found + commit_size;
-
- return commit;
- }
-
- void Free(const VKMemoryCommitImpl* commit) {
- ASSERT(commit);
-
- const auto it = std::find(std::begin(commits), std::end(commits), commit);
- if (it == commits.end()) {
- UNREACHABLE_MSG("Freeing unallocated commit!");
- return;
- }
- commits.erase(it);
- }
-
- /// Returns whether this allocation is compatible with the arguments.
- bool IsCompatible(VkMemoryPropertyFlags wanted_properties, u32 type_mask) const {
- return (wanted_properties & properties) && (type_mask & shifted_type) != 0;
- }
-
-private:
- static constexpr u32 ShiftType(u32 type) {
- return 1U << type;
- }
-
- /// A memory allocator, it may return a free region between "start" and "end" with the solicited
- /// requirements.
- std::optional<u64> TryFindFreeSection(u64 start, u64 end, u64 size, u64 alignment) const {
- u64 iterator = Common::AlignUp(start, alignment);
- while (iterator + size <= end) {
- const u64 try_left = iterator;
- const u64 try_right = try_left + size;
-
- bool overlap = false;
- for (const auto& commit : commits) {
- const auto [commit_left, commit_right] = commit->interval;
- if (try_left < commit_right && commit_left < try_right) {
- // There's an overlap, continue the search where the overlapping commit ends.
- iterator = Common::AlignUp(commit_right, alignment);
- overlap = true;
- break;
- }
- }
- if (!overlap) {
- // A free address has been found.
- return try_left;
- }
- }
-
- // No free regions where found, return an empty optional.
- return std::nullopt;
- }
-
- const VKDevice& device; ///< Vulkan device.
- const vk::DeviceMemory memory; ///< Vulkan memory allocation handler.
- const VkMemoryPropertyFlags properties; ///< Vulkan properties.
- const u64 allocation_size; ///< Size of this allocation.
- const u32 shifted_type; ///< Stored Vulkan type of this allocation, shifted.
-
- /// Hints where the next free region is likely going to be.
- u64 free_iterator{};
-
- /// Stores all commits done from this allocation.
- std::vector<const VKMemoryCommitImpl*> commits;
-};
-
-VKMemoryManager::VKMemoryManager(const VKDevice& device)
- : device{device}, properties{device.GetPhysical().GetMemoryProperties()} {}
-
-VKMemoryManager::~VKMemoryManager() = default;
-
-VKMemoryCommit VKMemoryManager::Commit(const VkMemoryRequirements& requirements,
- bool host_visible) {
- const u64 chunk_size = GetAllocationChunkSize(requirements.size);
-
- // When a host visible commit is asked, search for host visible and coherent, otherwise search
- // for a fast device local type.
- const VkMemoryPropertyFlags wanted_properties =
- host_visible ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
- : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
-
- if (auto commit = TryAllocCommit(requirements, wanted_properties)) {
- return commit;
- }
-
- // Commit has failed, allocate more memory.
- if (!AllocMemory(wanted_properties, requirements.memoryTypeBits, chunk_size)) {
- // TODO(Rodrigo): Handle these situations in some way like flushing to guest memory.
- // Allocation has failed, panic.
- UNREACHABLE_MSG("Ran out of VRAM!");
- return {};
- }
-
- // Commit again, this time it won't fail since there's a fresh allocation above. If it does,
- // there's a bug.
- auto commit = TryAllocCommit(requirements, wanted_properties);
- ASSERT(commit);
- return commit;
-}
-
-VKMemoryCommit VKMemoryManager::Commit(const vk::Buffer& buffer, bool host_visible) {
- auto commit = Commit(device.GetLogical().GetBufferMemoryRequirements(*buffer), host_visible);
- buffer.BindMemory(commit->GetMemory(), commit->GetOffset());
- return commit;
-}
-
-VKMemoryCommit VKMemoryManager::Commit(const vk::Image& image, bool host_visible) {
- auto commit = Commit(device.GetLogical().GetImageMemoryRequirements(*image), host_visible);
- image.BindMemory(commit->GetMemory(), commit->GetOffset());
- return commit;
-}
-
-bool VKMemoryManager::AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask,
- u64 size) {
- const u32 type = [&] {
- for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) {
- const auto flags = properties.memoryTypes[type_index].propertyFlags;
- if ((type_mask & (1U << type_index)) && (flags & wanted_properties)) {
- // The type matches in type and in the wanted properties.
- return type_index;
- }
- }
- UNREACHABLE_MSG("Couldn't find a compatible memory type!");
- return 0U;
- }();
-
- // Try to allocate found type.
- vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
- .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
- .pNext = nullptr,
- .allocationSize = size,
- .memoryTypeIndex = type,
- });
- if (!memory) {
- LOG_CRITICAL(Render_Vulkan, "Device allocation failed!");
- return false;
- }
-
- allocations.push_back(std::make_unique<VKMemoryAllocation>(device, std::move(memory),
- wanted_properties, size, type));
- return true;
-}
-
-VKMemoryCommit VKMemoryManager::TryAllocCommit(const VkMemoryRequirements& requirements,
- VkMemoryPropertyFlags wanted_properties) {
- for (auto& allocation : allocations) {
- if (!allocation->IsCompatible(wanted_properties, requirements.memoryTypeBits)) {
- continue;
- }
- if (auto commit = allocation->Commit(requirements.size, requirements.alignment)) {
- return commit;
- }
- }
- return {};
-}
-
-VKMemoryCommitImpl::VKMemoryCommitImpl(const VKDevice& device, VKMemoryAllocation* allocation,
- const vk::DeviceMemory& memory, u64 begin, u64 end)
- : device{device}, memory{memory}, interval{begin, end}, allocation{allocation} {}
-
-VKMemoryCommitImpl::~VKMemoryCommitImpl() {
- allocation->Free(this);
-}
-
-MemoryMap VKMemoryCommitImpl::Map(u64 size, u64 offset_) const {
- return MemoryMap{this, memory.Map(interval.first + offset_, size)};
-}
-
-void VKMemoryCommitImpl::Unmap() const {
- memory.Unmap();
-}
-
-MemoryMap VKMemoryCommitImpl::Map() const {
- return Map(interval.second - interval.first);
-}
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.h b/src/video_core/renderer_vulkan/vk_memory_manager.h
deleted file mode 100644
index 1af88e3d4..000000000
--- a/src/video_core/renderer_vulkan/vk_memory_manager.h
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <utility>
-#include <vector>
-#include "common/common_types.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-class MemoryMap;
-class VKDevice;
-class VKMemoryAllocation;
-class VKMemoryCommitImpl;
-
-using VKMemoryCommit = std::unique_ptr<VKMemoryCommitImpl>;
-
-class VKMemoryManager final {
-public:
- explicit VKMemoryManager(const VKDevice& device);
- VKMemoryManager(const VKMemoryManager&) = delete;
- ~VKMemoryManager();
-
- /**
- * Commits a memory with the specified requeriments.
- * @param requirements Requirements returned from a Vulkan call.
- * @param host_visible Signals the allocator that it *must* use host visible and coherent
- * memory. When passing false, it will try to allocate device local memory.
- * @returns A memory commit.
- */
- VKMemoryCommit Commit(const VkMemoryRequirements& requirements, bool host_visible);
-
- /// Commits memory required by the buffer and binds it.
- VKMemoryCommit Commit(const vk::Buffer& buffer, bool host_visible);
-
- /// Commits memory required by the image and binds it.
- VKMemoryCommit Commit(const vk::Image& image, bool host_visible);
-
-private:
- /// Allocates a chunk of memory.
- bool AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask, u64 size);
-
- /// Tries to allocate a memory commit.
- VKMemoryCommit TryAllocCommit(const VkMemoryRequirements& requirements,
- VkMemoryPropertyFlags wanted_properties);
-
- const VKDevice& device; ///< Device handler.
- const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
- std::vector<std::unique_ptr<VKMemoryAllocation>> allocations; ///< Current allocations.
-};
-
-class VKMemoryCommitImpl final {
- friend VKMemoryAllocation;
- friend MemoryMap;
-
-public:
- explicit VKMemoryCommitImpl(const VKDevice& device, VKMemoryAllocation* allocation,
- const vk::DeviceMemory& memory, u64 begin, u64 end);
- ~VKMemoryCommitImpl();
-
- /// Maps a memory region and returns a pointer to it.
- /// It's illegal to have more than one memory map at the same time.
- MemoryMap Map(u64 size, u64 offset = 0) const;
-
- /// Maps the whole commit and returns a pointer to it.
- /// It's illegal to have more than one memory map at the same time.
- MemoryMap Map() const;
-
- /// Returns the Vulkan memory handler.
- VkDeviceMemory GetMemory() const {
- return *memory;
- }
-
- /// Returns the start position of the commit relative to the allocation.
- VkDeviceSize GetOffset() const {
- return static_cast<VkDeviceSize>(interval.first);
- }
-
-private:
- /// Unmaps memory.
- void Unmap() const;
-
- const VKDevice& device; ///< Vulkan device.
- const vk::DeviceMemory& memory; ///< Vulkan device memory handler.
- std::pair<u64, u64> interval{}; ///< Interval where the commit exists.
- VKMemoryAllocation* allocation{}; ///< Pointer to the large memory allocation.
-};
-
-/// Holds ownership of a memory map.
-class MemoryMap final {
-public:
- explicit MemoryMap(const VKMemoryCommitImpl* commit, u8* address)
- : commit{commit}, address{address} {}
-
- ~MemoryMap() {
- if (commit) {
- commit->Unmap();
- }
- }
-
- /// Prematurely releases the memory map.
- void Release() {
- commit->Unmap();
- commit = nullptr;
- }
-
- /// Returns the address of the memory map.
- u8* GetAddress() const {
- return address;
- }
-
- /// Returns the address of the memory map;
- operator u8*() const {
- return address;
- }
-
-private:
- const VKMemoryCommitImpl* commit{}; ///< Mapped memory commit.
- u8* address{}; ///< Address to the mapped memory.
-};
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index cfdcdd6ab..8991505ca 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -7,6 +7,8 @@
#include <memory>
#include <vector>
+#include "common/bit_cast.h"
+#include "common/cityhash.h"
#include "common/microprofile.h"
#include "core/core.h"
#include "core/memory.h"
@@ -17,18 +19,17 @@
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
-#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
-#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/shader/compiler_settings.h"
#include "video_core/shader/memory_util.h"
#include "video_core/shader_cache.h"
#include "video_core/shader_notify.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
@@ -51,7 +52,9 @@ constexpr VkDescriptorType STORAGE_TEXEL_BUFFER = VK_DESCRIPTOR_TYPE_STORAGE_TEX
constexpr VkDescriptorType STORAGE_IMAGE = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
constexpr VideoCommon::Shader::CompilerSettings compiler_settings{
- VideoCommon::Shader::CompileDepth::FullDecompile};
+ .depth = VideoCommon::Shader::CompileDepth::FullDecompile,
+ .disable_else_derivation = true,
+};
constexpr std::size_t GetStageFromProgram(std::size_t program) {
return program == 0 ? 0 : program - 1;
@@ -74,7 +77,7 @@ ShaderType GetShaderType(Maxwell::ShaderProgram program) {
case Maxwell::ShaderProgram::Fragment:
return ShaderType::Fragment;
default:
- UNIMPLEMENTED_MSG("program={}", static_cast<u32>(program));
+ UNIMPLEMENTED_MSG("program={}", program);
return ShaderType::Vertex;
}
}
@@ -135,64 +138,54 @@ bool ComputePipelineCacheKey::operator==(const ComputePipelineCacheKey& rhs) con
return std::memcmp(&rhs, this, sizeof *this) == 0;
}
-Shader::Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr,
- VideoCommon::Shader::ProgramCode program_code, u32 main_offset)
- : gpu_addr{gpu_addr}, program_code{std::move(program_code)},
- registry{stage, GetEngine(system, stage)}, shader_ir{this->program_code, main_offset,
- compiler_settings, registry},
- entries{GenerateShaderEntries(shader_ir)} {}
+Shader::Shader(Tegra::Engines::ConstBufferEngineInterface& engine_, ShaderType stage_,
+ GPUVAddr gpu_addr_, VAddr cpu_addr_, ProgramCode program_code_, u32 main_offset_)
+ : gpu_addr(gpu_addr_), program_code(std::move(program_code_)), registry(stage_, engine_),
+ shader_ir(program_code, main_offset_, compiler_settings, registry),
+ entries(GenerateShaderEntries(shader_ir)) {}
Shader::~Shader() = default;
-Tegra::Engines::ConstBufferEngineInterface& Shader::GetEngine(Core::System& system,
- Tegra::Engines::ShaderType stage) {
- if (stage == ShaderType::Compute) {
- return system.GPU().KeplerCompute();
- } else {
- return system.GPU().Maxwell3D();
- }
-}
-
-VKPipelineCache::VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
- VKRenderPassCache& renderpass_cache)
- : VideoCommon::ShaderCache<Shader>{rasterizer}, system{system}, device{device},
- scheduler{scheduler}, descriptor_pool{descriptor_pool},
- update_descriptor_queue{update_descriptor_queue}, renderpass_cache{renderpass_cache} {}
+VKPipelineCache::VKPipelineCache(RasterizerVulkan& rasterizer_, Tegra::GPU& gpu_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_, const Device& device_,
+ VKScheduler& scheduler_, VKDescriptorPool& descriptor_pool_,
+ VKUpdateDescriptorQueue& update_descriptor_queue_)
+ : VideoCommon::ShaderCache<Shader>{rasterizer_}, gpu{gpu_}, maxwell3d{maxwell3d_},
+ kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_}, device{device_},
+ scheduler{scheduler_}, descriptor_pool{descriptor_pool_}, update_descriptor_queue{
+ update_descriptor_queue_} {}
VKPipelineCache::~VKPipelineCache() = default;
std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() {
- const auto& gpu = system.GPU().Maxwell3D();
-
std::array<Shader*, Maxwell::MaxShaderProgram> shaders{};
+
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
const auto program{static_cast<Maxwell::ShaderProgram>(index)};
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
continue;
}
- auto& memory_manager{system.GPU().MemoryManager()};
- const GPUVAddr program_addr{GetShaderAddress(system, program)};
- const std::optional cpu_addr = memory_manager.GpuToCpuAddress(program_addr);
+ const GPUVAddr gpu_addr{GetShaderAddress(maxwell3d, program)};
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
Shader* result = cpu_addr ? TryGet(*cpu_addr) : null_shader.get();
if (!result) {
- const auto host_ptr{memory_manager.GetPointer(program_addr)};
+ const u8* const host_ptr{gpu_memory.GetPointer(gpu_addr)};
// No shader found - create a new one
- constexpr u32 stage_offset = STAGE_MAIN_OFFSET;
+ static constexpr u32 stage_offset = STAGE_MAIN_OFFSET;
const auto stage = static_cast<ShaderType>(index == 0 ? 0 : index - 1);
- ProgramCode code = GetShaderCode(memory_manager, program_addr, host_ptr, false);
+ ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, false);
const std::size_t size_in_bytes = code.size() * sizeof(u64);
- auto shader = std::make_unique<Shader>(system, stage, program_addr, std::move(code),
- stage_offset);
+ auto shader = std::make_unique<Shader>(maxwell3d, stage, gpu_addr, *cpu_addr,
+ std::move(code), stage_offset);
result = shader.get();
if (cpu_addr) {
@@ -207,7 +200,8 @@ std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() {
}
VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline(
- const GraphicsPipelineCacheKey& key, VideoCommon::Shader::AsyncShaders& async_shaders) {
+ const GraphicsPipelineCacheKey& key, u32 num_color_buffers,
+ VideoCommon::Shader::AsyncShaders& async_shaders) {
MICROPROFILE_SCOPE(Vulkan_PipelineCache);
if (last_graphics_pipeline && last_graphics_key == key) {
@@ -215,16 +209,16 @@ VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline(
}
last_graphics_key = key;
- if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(system.GPU())) {
+ if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(gpu)) {
std::unique_lock lock{pipeline_cache};
const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
if (is_cache_miss) {
- system.GPU().ShaderNotify().MarkSharderBuilding();
+ gpu.ShaderNotify().MarkSharderBuilding();
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
const auto [program, bindings] = DecompileShaders(key.fixed_state);
async_shaders.QueueVulkanShader(this, device, scheduler, descriptor_pool,
- update_descriptor_queue, renderpass_cache, bindings,
- program, key);
+ update_descriptor_queue, bindings, program, key,
+ num_color_buffers);
}
last_graphics_pipeline = pair->second.get();
return last_graphics_pipeline;
@@ -233,13 +227,13 @@ VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline(
const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
auto& entry = pair->second;
if (is_cache_miss) {
- system.GPU().ShaderNotify().MarkSharderBuilding();
+ gpu.ShaderNotify().MarkSharderBuilding();
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
const auto [program, bindings] = DecompileShaders(key.fixed_state);
entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool,
- update_descriptor_queue, renderpass_cache, key,
- bindings, program);
- system.GPU().ShaderNotify().MarkShaderComplete();
+ update_descriptor_queue, key, bindings,
+ program, num_color_buffers);
+ gpu.ShaderNotify().MarkShaderComplete();
}
last_graphics_pipeline = entry.get();
return last_graphics_pipeline;
@@ -255,22 +249,21 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
}
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
- auto& memory_manager = system.GPU().MemoryManager();
- const auto program_addr = key.shader;
+ const GPUVAddr gpu_addr = key.shader;
- const auto cpu_addr = memory_manager.GpuToCpuAddress(program_addr);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
ASSERT(cpu_addr);
Shader* shader = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get();
if (!shader) {
// No shader found - create a new one
- const auto host_ptr = memory_manager.GetPointer(program_addr);
+ const auto host_ptr = gpu_memory.GetPointer(gpu_addr);
- ProgramCode code = GetShaderCode(memory_manager, program_addr, host_ptr, true);
+ ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, true);
const std::size_t size_in_bytes = code.size() * sizeof(u64);
- auto shader_info = std::make_unique<Shader>(system, ShaderType::Compute, program_addr,
- std::move(code), KERNEL_MAIN_OFFSET);
+ auto shader_info = std::make_unique<Shader>(kepler_compute, ShaderType::Compute, gpu_addr,
+ *cpu_addr, std::move(code), KERNEL_MAIN_OFFSET);
shader = shader_info.get();
if (cpu_addr) {
@@ -298,7 +291,7 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
}
void VKPipelineCache::EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline) {
- system.GPU().ShaderNotify().MarkShaderComplete();
+ gpu.ShaderNotify().MarkShaderComplete();
std::unique_lock lock{pipeline_cache};
graphics_cache.at(pipeline->GetCacheKey()) = std::move(pipeline);
}
@@ -339,12 +332,8 @@ void VKPipelineCache::OnShaderRemoval(Shader* shader) {
std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>>
VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) {
- auto& memory_manager = system.GPU().MemoryManager();
- const auto& gpu = system.GPU().Maxwell3D();
-
Specialization specialization;
- if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points ||
- device.IsExtExtendedDynamicStateSupported()) {
+ if (fixed_state.topology == Maxwell::PrimitiveTopology::Points) {
float point_size;
std::memcpy(&point_size, &fixed_state.point_size, sizeof(float));
specialization.point_size = point_size;
@@ -356,20 +345,24 @@ VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) {
specialization.attribute_types[i] = attribute.Type();
}
specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one;
+ specialization.early_fragment_tests = fixed_state.early_z;
+
+ // Alpha test
+ specialization.alpha_test_func =
+ FixedPipelineState::UnpackComparisonOp(fixed_state.alpha_test_func.Value());
+ specialization.alpha_test_ref = Common::BitCast<float>(fixed_state.alpha_test_ref);
SPIRVProgram program;
std::vector<VkDescriptorSetLayoutBinding> bindings;
- for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
+ for (std::size_t index = 1; index < Maxwell::MaxShaderProgram; ++index) {
const auto program_enum = static_cast<Maxwell::ShaderProgram>(index);
-
// Skip stages that are not enabled
- if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
continue;
}
-
- const GPUVAddr gpu_addr = GetShaderAddress(system, program_enum);
- const std::optional<VAddr> cpu_addr = memory_manager.GpuToCpuAddress(gpu_addr);
+ const GPUVAddr gpu_addr = GetShaderAddress(maxwell3d, program_enum);
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
Shader* const shader = cpu_addr ? TryGet(*cpu_addr) : null_shader.get();
const std::size_t stage = index == 0 ? 0 : index - 1; // Stage indices are 0 - 5
@@ -377,12 +370,8 @@ VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) {
const auto& entries = shader->GetEntries();
program[stage] = {
Decompile(device, shader->GetIR(), program_type, shader->GetRegistry(), specialization),
- entries};
-
- if (program_enum == Maxwell::ShaderProgram::VertexA) {
- // VertexB was combined with VertexA, so we skip the VertexB iteration
- ++index;
- }
+ entries,
+ };
const u32 old_binding = specialization.base_binding;
specialization.base_binding =
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index c04829e77..89d635a3d 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -19,14 +19,13 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
-#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
-#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/shader/async_shaders.h"
#include "video_core/shader/memory_util.h"
#include "video_core/shader/registry.h"
#include "video_core/shader/shader_ir.h"
#include "video_core/shader_cache.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Core {
class System;
@@ -34,11 +33,10 @@ class System;
namespace Vulkan {
+class Device;
class RasterizerVulkan;
class VKComputePipeline;
class VKDescriptorPool;
-class VKDevice;
-class VKFence;
class VKScheduler;
class VKUpdateDescriptorQueue;
@@ -85,8 +83,9 @@ namespace Vulkan {
class Shader {
public:
- explicit Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr,
- VideoCommon::Shader::ProgramCode program_code, u32 main_offset);
+ explicit Shader(Tegra::Engines::ConstBufferEngineInterface& engine_,
+ Tegra::Engines::ShaderType stage_, GPUVAddr gpu_addr, VAddr cpu_addr_,
+ VideoCommon::Shader::ProgramCode program_code, u32 main_offset_);
~Shader();
GPUVAddr GetGpuAddr() const {
@@ -97,22 +96,19 @@ public:
return shader_ir;
}
- const VideoCommon::Shader::Registry& GetRegistry() const {
- return registry;
- }
-
const VideoCommon::Shader::ShaderIR& GetIR() const {
return shader_ir;
}
+ const VideoCommon::Shader::Registry& GetRegistry() const {
+ return registry;
+ }
+
const ShaderEntries& GetEntries() const {
return entries;
}
private:
- static Tegra::Engines::ConstBufferEngineInterface& GetEngine(Core::System& system,
- Tegra::Engines::ShaderType stage);
-
GPUVAddr gpu_addr{};
VideoCommon::Shader::ProgramCode program_code;
VideoCommon::Shader::Registry registry;
@@ -122,16 +118,18 @@ private:
class VKPipelineCache final : public VideoCommon::ShaderCache<Shader> {
public:
- explicit VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer,
- const VKDevice& device, VKScheduler& scheduler,
- VKDescriptorPool& descriptor_pool,
- VKUpdateDescriptorQueue& update_descriptor_queue,
- VKRenderPassCache& renderpass_cache);
+ explicit VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu,
+ Tegra::Engines::Maxwell3D& maxwell3d,
+ Tegra::Engines::KeplerCompute& kepler_compute,
+ Tegra::MemoryManager& gpu_memory, const Device& device,
+ VKScheduler& scheduler, VKDescriptorPool& descriptor_pool,
+ VKUpdateDescriptorQueue& update_descriptor_queue);
~VKPipelineCache() override;
std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders();
VKGraphicsPipeline* GetGraphicsPipeline(const GraphicsPipelineCacheKey& key,
+ u32 num_color_buffers,
VideoCommon::Shader::AsyncShaders& async_shaders);
VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key);
@@ -145,12 +143,15 @@ private:
std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders(
const FixedPipelineState& fixed_state);
- Core::System& system;
- const VKDevice& device;
+ Tegra::GPU& gpu;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
+
+ const Device& device;
VKScheduler& scheduler;
VKDescriptorPool& descriptor_pool;
VKUpdateDescriptorQueue& update_descriptor_queue;
- VKRenderPassCache& renderpass_cache;
std::unique_ptr<Shader> null_shader;
std::unique_ptr<Shader> null_kernel;
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 6cd63d090..7cadd5147 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -7,37 +7,35 @@
#include <utility>
#include <vector>
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
+using VideoCore::QueryType;
+
namespace {
constexpr std::array QUERY_TARGETS = {VK_QUERY_TYPE_OCCLUSION};
-constexpr VkQueryType GetTarget(VideoCore::QueryType type) {
+constexpr VkQueryType GetTarget(QueryType type) {
return QUERY_TARGETS[static_cast<std::size_t>(type)];
}
} // Anonymous namespace
-QueryPool::QueryPool() : VKFencedPool{GROW_STEP} {}
+QueryPool::QueryPool(const Device& device_, VKScheduler& scheduler, QueryType type_)
+ : ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {}
QueryPool::~QueryPool() = default;
-void QueryPool::Initialize(const VKDevice& device_, VideoCore::QueryType type_) {
- device = &device_;
- type = type_;
-}
-
-std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) {
+std::pair<VkQueryPool, u32> QueryPool::Commit() {
std::size_t index;
do {
- index = CommitResource(fence);
+ index = CommitResource();
} while (usage[index]);
usage[index] = true;
@@ -47,7 +45,7 @@ std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) {
void QueryPool::Allocate(std::size_t begin, std::size_t end) {
usage.resize(end);
- pools.push_back(device->GetLogical().CreateQueryPool({
+ pools.push_back(device.GetLogical().CreateQueryPool({
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -68,32 +66,39 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false;
}
-VKQueryCache::VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKScheduler& scheduler)
- : VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
- QueryPool>{system, rasterizer},
- device{device}, scheduler{scheduler} {
- for (std::size_t i = 0; i < static_cast<std::size_t>(VideoCore::NumQueryTypes); ++i) {
- query_pools[i].Initialize(device, static_cast<VideoCore::QueryType>(i));
+VKQueryCache::VKQueryCache(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_,
+ const Device& device_, VKScheduler& scheduler_)
+ : QueryCacheBase{rasterizer_, maxwell3d_, gpu_memory_}, device{device_}, scheduler{scheduler_},
+ query_pools{
+ QueryPool{device_, scheduler_, QueryType::SamplesPassed},
+ } {}
+
+VKQueryCache::~VKQueryCache() {
+ // TODO(Rodrigo): This is a hack to destroy all HostCounter instances before the base class
+ // destructor is called. The query cache should be redesigned to have a proper ownership model
+ // instead of using shared pointers.
+ for (size_t query_type = 0; query_type < VideoCore::NumQueryTypes; ++query_type) {
+ auto& stream = Stream(static_cast<QueryType>(query_type));
+ stream.Update(false);
+ stream.Reset();
}
}
-VKQueryCache::~VKQueryCache() = default;
-
-std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(VideoCore::QueryType type) {
- return query_pools[static_cast<std::size_t>(type)].Commit(scheduler.GetFence());
+std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(QueryType type) {
+ return query_pools[static_cast<std::size_t>(type)].Commit();
}
-void VKQueryCache::Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query) {
+void VKQueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) {
query_pools[static_cast<std::size_t>(type)].Reserve(query);
}
-HostCounter::HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency,
- VideoCore::QueryType type)
- : VideoCommon::HostCounterBase<VKQueryCache, HostCounter>{std::move(dependency)}, cache{cache},
- type{type}, query{cache.AllocateQuery(type)}, ticks{cache.Scheduler().Ticks()} {
- const vk::Device* logical = &cache.Device().GetLogical();
- cache.Scheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) {
+HostCounter::HostCounter(VKQueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
+ QueryType type_)
+ : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_},
+ query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} {
+ const vk::Device* logical = &cache.GetDevice().GetLogical();
+ cache.GetScheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) {
logical->ResetQueryPoolEXT(query.first, query.second, 1);
cmdbuf.BeginQuery(query.first, query.second, VK_QUERY_CONTROL_PRECISE_BIT);
});
@@ -104,26 +109,28 @@ HostCounter::~HostCounter() {
}
void HostCounter::EndQuery() {
- cache.Scheduler().Record(
+ cache.GetScheduler().Record(
[query = query](vk::CommandBuffer cmdbuf) { cmdbuf.EndQuery(query.first, query.second); });
}
u64 HostCounter::BlockingQuery() const {
- if (ticks >= cache.Scheduler().Ticks()) {
- cache.Scheduler().Flush();
+ if (tick >= cache.GetScheduler().CurrentTick()) {
+ cache.GetScheduler().Flush();
}
+
u64 data;
- const VkResult result = cache.Device().GetLogical().GetQueryResults(
+ const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults(
query.first, query.second, 1, sizeof(data), &data, sizeof(data),
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
- switch (result) {
+
+ switch (query_result) {
case VK_SUCCESS:
return data;
case VK_ERROR_DEVICE_LOST:
- cache.Device().ReportLoss();
+ cache.GetDevice().ReportLoss();
[[fallthrough]];
default:
- throw vk::Exception(result);
+ throw vk::Exception(query_result);
}
}
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index 40119e6d3..7190946b9 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -11,8 +11,8 @@
#include "common/common_types.h"
#include "video_core/query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace VideoCore {
class RasterizerInterface;
@@ -21,21 +21,19 @@ class RasterizerInterface;
namespace Vulkan {
class CachedQuery;
+class Device;
class HostCounter;
-class VKDevice;
class VKQueryCache;
class VKScheduler;
using CounterStream = VideoCommon::CounterStreamBase<VKQueryCache, HostCounter>;
-class QueryPool final : public VKFencedPool {
+class QueryPool final : public ResourcePool {
public:
- explicit QueryPool();
+ explicit QueryPool(const Device& device, VKScheduler& scheduler, VideoCore::QueryType type);
~QueryPool() override;
- void Initialize(const VKDevice& device, VideoCore::QueryType type);
-
- std::pair<VkQueryPool, u32> Commit(VKFence& fence);
+ std::pair<VkQueryPool, u32> Commit();
void Reserve(std::pair<VkQueryPool, u32> query);
@@ -45,42 +43,43 @@ protected:
private:
static constexpr std::size_t GROW_STEP = 512;
- const VKDevice* device = nullptr;
- VideoCore::QueryType type = {};
+ const Device& device;
+ const VideoCore::QueryType type;
std::vector<vk::QueryPool> pools;
std::vector<bool> usage;
};
class VKQueryCache final
- : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
- QueryPool> {
+ : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter> {
public:
- explicit VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKScheduler& scheduler);
+ explicit VKQueryCache(VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_,
+ const Device& device_, VKScheduler& scheduler_);
~VKQueryCache();
std::pair<VkQueryPool, u32> AllocateQuery(VideoCore::QueryType type);
void Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query);
- const VKDevice& Device() const noexcept {
+ const Device& GetDevice() const noexcept {
return device;
}
- VKScheduler& Scheduler() const noexcept {
+ VKScheduler& GetScheduler() const noexcept {
return scheduler;
}
private:
- const VKDevice& device;
+ const Device& device;
VKScheduler& scheduler;
+ std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
};
class HostCounter final : public VideoCommon::HostCounterBase<VKQueryCache, HostCounter> {
public:
- explicit HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency,
- VideoCore::QueryType type);
+ explicit HostCounter(VKQueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
+ VideoCore::QueryType type_);
~HostCounter();
void EndQuery();
@@ -91,13 +90,13 @@ private:
VKQueryCache& cache;
const VideoCore::QueryType type;
const std::pair<VkQueryPool, u32> query;
- const u64 ticks;
+ const u64 tick;
};
class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> {
public:
- explicit CachedQuery(VKQueryCache&, VideoCore::QueryType, VAddr cpu_addr, u8* host_ptr)
- : VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr} {}
+ explicit CachedQuery(VKQueryCache&, VideoCore::QueryType, VAddr cpu_addr_, u8* host_ptr_)
+ : CachedQueryBase{cpu_addr_, host_ptr_} {}
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index ff1b52eab..394a1c4e9 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -8,8 +8,6 @@
#include <mutex>
#include <vector>
-#include <boost/container/static_vector.hpp>
-
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
@@ -19,54 +17,54 @@
#include "core/settings.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_vulkan/blit_image.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
-#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
-#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/vk_sampler_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
-#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/shader_cache.h"
+#include "video_core/texture_cache/texture_cache.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+using VideoCommon::ImageViewId;
+using VideoCommon::ImageViewType;
MICROPROFILE_DEFINE(Vulkan_WaitForWorker, "Vulkan", "Wait for worker", MP_RGB(255, 192, 192));
MICROPROFILE_DEFINE(Vulkan_Drawing, "Vulkan", "Record drawing", MP_RGB(192, 128, 128));
MICROPROFILE_DEFINE(Vulkan_Compute, "Vulkan", "Record compute", MP_RGB(192, 128, 128));
MICROPROFILE_DEFINE(Vulkan_Clearing, "Vulkan", "Record clearing", MP_RGB(192, 128, 128));
-MICROPROFILE_DEFINE(Vulkan_Geometry, "Vulkan", "Setup geometry", MP_RGB(192, 128, 128));
-MICROPROFILE_DEFINE(Vulkan_ConstBuffers, "Vulkan", "Setup constant buffers", MP_RGB(192, 128, 128));
-MICROPROFILE_DEFINE(Vulkan_GlobalBuffers, "Vulkan", "Setup global buffers", MP_RGB(192, 128, 128));
-MICROPROFILE_DEFINE(Vulkan_RenderTargets, "Vulkan", "Setup render targets", MP_RGB(192, 128, 128));
-MICROPROFILE_DEFINE(Vulkan_Textures, "Vulkan", "Setup textures", MP_RGB(192, 128, 128));
-MICROPROFILE_DEFINE(Vulkan_Images, "Vulkan", "Setup images", MP_RGB(192, 128, 128));
MICROPROFILE_DEFINE(Vulkan_PipelineCache, "Vulkan", "Pipeline cache", MP_RGB(192, 128, 128));
namespace {
+struct DrawParams {
+ u32 base_instance;
+ u32 num_instances;
+ u32 base_vertex;
+ u32 num_vertices;
+ bool is_indexed;
+};
-constexpr auto ComputeShaderIndex = static_cast<std::size_t>(Tegra::Engines::ShaderType::Compute);
+constexpr auto COMPUTE_SHADER_INDEX = static_cast<size_t>(Tegra::Engines::ShaderType::Compute);
-VkViewport GetViewportState(const VKDevice& device, const Maxwell& regs, std::size_t index) {
+VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index) {
const auto& src = regs.viewport_transform[index];
const float width = src.scale_x * 2.0f;
const float height = src.scale_y * 2.0f;
const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
-
VkViewport viewport{
.x = src.translate_x - src.scale_x,
.y = src.translate_y - src.scale_y,
@@ -75,16 +73,14 @@ VkViewport GetViewportState(const VKDevice& device, const Maxwell& regs, std::si
.minDepth = src.translate_z - src.scale_z * reduce_z,
.maxDepth = src.translate_z + src.scale_z,
};
-
if (!device.IsExtDepthRangeUnrestrictedSupported()) {
viewport.minDepth = std::clamp(viewport.minDepth, 0.0f, 1.0f);
viewport.maxDepth = std::clamp(viewport.maxDepth, 0.0f, 1.0f);
}
-
return viewport;
}
-VkRect2D GetScissorState(const Maxwell& regs, std::size_t index) {
+VkRect2D GetScissorState(const Maxwell& regs, size_t index) {
const auto& src = regs.scissor_test[index];
VkRect2D scissor;
if (src.enable) {
@@ -104,305 +100,157 @@ VkRect2D GetScissorState(const Maxwell& regs, std::size_t index) {
std::array<GPUVAddr, Maxwell::MaxShaderProgram> GetShaderAddresses(
const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders) {
std::array<GPUVAddr, Maxwell::MaxShaderProgram> addresses;
- for (std::size_t i = 0; i < std::size(addresses); ++i) {
+ for (size_t i = 0; i < std::size(addresses); ++i) {
addresses[i] = shaders[i] ? shaders[i]->GetGpuAddr() : 0;
}
return addresses;
}
-void TransitionImages(const std::vector<ImageView>& views, VkPipelineStageFlags pipeline_stage,
- VkAccessFlags access) {
- for (auto& [view, layout] : views) {
- view->Transition(*layout, pipeline_stage, access);
+struct TextureHandle {
+ constexpr TextureHandle(u32 data, bool via_header_index) {
+ const Tegra::Texture::TextureHandle handle{data};
+ image = handle.tic_id;
+ sampler = via_header_index ? image : handle.tsc_id.Value();
}
-}
+
+ u32 image;
+ u32 sampler;
+};
template <typename Engine, typename Entry>
-Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry,
- std::size_t stage, std::size_t index = 0) {
- const auto stage_type = static_cast<Tegra::Engines::ShaderType>(stage);
+TextureHandle GetTextureInfo(const Engine& engine, bool via_header_index, const Entry& entry,
+ size_t stage, size_t index = 0) {
+ const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage);
if constexpr (std::is_same_v<Entry, SamplerEntry>) {
if (entry.is_separated) {
const u32 buffer_1 = entry.buffer;
const u32 buffer_2 = entry.secondary_buffer;
const u32 offset_1 = entry.offset;
const u32 offset_2 = entry.secondary_offset;
- const u32 handle_1 = engine.AccessConstBuffer32(stage_type, buffer_1, offset_1);
- const u32 handle_2 = engine.AccessConstBuffer32(stage_type, buffer_2, offset_2);
- return engine.GetTextureInfo(handle_1 | handle_2);
+ const u32 handle_1 = engine.AccessConstBuffer32(shader_type, buffer_1, offset_1);
+ const u32 handle_2 = engine.AccessConstBuffer32(shader_type, buffer_2, offset_2);
+ return TextureHandle(handle_1 | handle_2, via_header_index);
}
}
if (entry.is_bindless) {
- const auto tex_handle = engine.AccessConstBuffer32(stage_type, entry.buffer, entry.offset);
- return engine.GetTextureInfo(tex_handle);
- }
- const auto& gpu_profile = engine.AccessGuestDriverProfile();
- const u32 entry_offset = static_cast<u32>(index * gpu_profile.GetTextureHandlerSize());
- const u32 offset = entry.offset + entry_offset;
- if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) {
- return engine.GetStageTexture(stage_type, offset);
- } else {
- return engine.GetTexture(offset);
- }
-}
-
-/// @brief Determine if an attachment to be updated has to preserve contents
-/// @param is_clear True when a clear is being executed
-/// @param regs 3D registers
-/// @return True when the contents have to be preserved
-bool HasToPreserveColorContents(bool is_clear, const Maxwell& regs) {
- if (!is_clear) {
- return true;
- }
- // First we have to make sure all clear masks are enabled.
- if (!regs.clear_buffers.R || !regs.clear_buffers.G || !regs.clear_buffers.B ||
- !regs.clear_buffers.A) {
- return true;
- }
- // If scissors are disabled, the whole screen is cleared
- if (!regs.clear_flags.scissor) {
- return false;
- }
- // Then we have to confirm scissor testing clears the whole image
- const std::size_t index = regs.clear_buffers.RT;
- const auto& scissor = regs.scissor_test[0];
- return scissor.min_x > 0 || scissor.min_y > 0 || scissor.max_x < regs.rt[index].width ||
- scissor.max_y < regs.rt[index].height;
-}
-
-/// @brief Determine if an attachment to be updated has to preserve contents
-/// @param is_clear True when a clear is being executed
-/// @param regs 3D registers
-/// @return True when the contents have to be preserved
-bool HasToPreserveDepthContents(bool is_clear, const Maxwell& regs) {
- // If we are not clearing, the contents have to be preserved
- if (!is_clear) {
- return true;
- }
- // For depth stencil clears we only have to confirm scissor test covers the whole image
- if (!regs.clear_flags.scissor) {
- return false;
+ const u32 raw = engine.AccessConstBuffer32(shader_type, entry.buffer, entry.offset);
+ return TextureHandle(raw, via_header_index);
+ }
+ const u32 buffer = engine.GetBoundBuffer();
+ const u64 offset = (entry.offset + index) * sizeof(u32);
+ return TextureHandle(engine.AccessConstBuffer32(shader_type, buffer, offset), via_header_index);
+}
+
+ImageViewType ImageViewTypeFromEntry(const SamplerEntry& entry) {
+ if (entry.is_buffer) {
+ return ImageViewType::e2D;
+ }
+ switch (entry.type) {
+ case Tegra::Shader::TextureType::Texture1D:
+ return entry.is_array ? ImageViewType::e1DArray : ImageViewType::e1D;
+ case Tegra::Shader::TextureType::Texture2D:
+ return entry.is_array ? ImageViewType::e2DArray : ImageViewType::e2D;
+ case Tegra::Shader::TextureType::Texture3D:
+ return ImageViewType::e3D;
+ case Tegra::Shader::TextureType::TextureCube:
+ return entry.is_array ? ImageViewType::CubeArray : ImageViewType::Cube;
+ }
+ UNREACHABLE();
+ return ImageViewType::e2D;
+}
+
+ImageViewType ImageViewTypeFromEntry(const ImageEntry& entry) {
+ switch (entry.type) {
+ case Tegra::Shader::ImageType::Texture1D:
+ return ImageViewType::e1D;
+ case Tegra::Shader::ImageType::Texture1DArray:
+ return ImageViewType::e1DArray;
+ case Tegra::Shader::ImageType::Texture2D:
+ return ImageViewType::e2D;
+ case Tegra::Shader::ImageType::Texture2DArray:
+ return ImageViewType::e2DArray;
+ case Tegra::Shader::ImageType::Texture3D:
+ return ImageViewType::e3D;
+ case Tegra::Shader::ImageType::TextureBuffer:
+ return ImageViewType::Buffer;
+ }
+ UNREACHABLE();
+ return ImageViewType::e2D;
+}
+
+void PushImageDescriptors(const ShaderEntries& entries, TextureCache& texture_cache,
+ VKUpdateDescriptorQueue& update_descriptor_queue,
+ ImageViewId*& image_view_id_ptr, VkSampler*& sampler_ptr) {
+ for ([[maybe_unused]] const auto& entry : entries.uniform_texels) {
+ const ImageViewId image_view_id = *image_view_id_ptr++;
+ const ImageView& image_view = texture_cache.GetImageView(image_view_id);
+ update_descriptor_queue.AddTexelBuffer(image_view.BufferView());
}
- // Make sure the clear cover the whole image
- const auto& scissor = regs.scissor_test[0];
- return scissor.min_x > 0 || scissor.min_y > 0 || scissor.max_x < regs.zeta_width ||
- scissor.max_y < regs.zeta_height;
-}
-
-template <std::size_t N>
-std::array<VkDeviceSize, N> ExpandStrides(const std::array<u16, N>& strides) {
- std::array<VkDeviceSize, N> expanded;
- std::copy(strides.begin(), strides.end(), expanded.begin());
- return expanded;
-}
-
-} // Anonymous namespace
-
-class BufferBindings final {
-public:
- void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, u32 stride) {
- vertex.buffers[vertex.num_buffers] = buffer;
- vertex.offsets[vertex.num_buffers] = offset;
- vertex.sizes[vertex.num_buffers] = size;
- vertex.strides[vertex.num_buffers] = static_cast<u16>(stride);
- ++vertex.num_buffers;
- }
-
- void SetIndexBinding(VkBuffer buffer, VkDeviceSize offset, VkIndexType type) {
- index.buffer = buffer;
- index.offset = offset;
- index.type = type;
- }
-
- void Bind(const VKDevice& device, VKScheduler& scheduler) const {
- // Use this large switch case to avoid dispatching more memory in the record lambda than
- // what we need. It looks horrible, but it's the best we can do on standard C++.
- switch (vertex.num_buffers) {
- case 0:
- return BindStatic<0>(device, scheduler);
- case 1:
- return BindStatic<1>(device, scheduler);
- case 2:
- return BindStatic<2>(device, scheduler);
- case 3:
- return BindStatic<3>(device, scheduler);
- case 4:
- return BindStatic<4>(device, scheduler);
- case 5:
- return BindStatic<5>(device, scheduler);
- case 6:
- return BindStatic<6>(device, scheduler);
- case 7:
- return BindStatic<7>(device, scheduler);
- case 8:
- return BindStatic<8>(device, scheduler);
- case 9:
- return BindStatic<9>(device, scheduler);
- case 10:
- return BindStatic<10>(device, scheduler);
- case 11:
- return BindStatic<11>(device, scheduler);
- case 12:
- return BindStatic<12>(device, scheduler);
- case 13:
- return BindStatic<13>(device, scheduler);
- case 14:
- return BindStatic<14>(device, scheduler);
- case 15:
- return BindStatic<15>(device, scheduler);
- case 16:
- return BindStatic<16>(device, scheduler);
- case 17:
- return BindStatic<17>(device, scheduler);
- case 18:
- return BindStatic<18>(device, scheduler);
- case 19:
- return BindStatic<19>(device, scheduler);
- case 20:
- return BindStatic<20>(device, scheduler);
- case 21:
- return BindStatic<21>(device, scheduler);
- case 22:
- return BindStatic<22>(device, scheduler);
- case 23:
- return BindStatic<23>(device, scheduler);
- case 24:
- return BindStatic<24>(device, scheduler);
- case 25:
- return BindStatic<25>(device, scheduler);
- case 26:
- return BindStatic<26>(device, scheduler);
- case 27:
- return BindStatic<27>(device, scheduler);
- case 28:
- return BindStatic<28>(device, scheduler);
- case 29:
- return BindStatic<29>(device, scheduler);
- case 30:
- return BindStatic<30>(device, scheduler);
- case 31:
- return BindStatic<31>(device, scheduler);
- case 32:
- return BindStatic<32>(device, scheduler);
- }
- UNREACHABLE();
- }
-
-private:
- // Some of these fields are intentionally left uninitialized to avoid initializing them twice.
- struct {
- std::size_t num_buffers = 0;
- std::array<VkBuffer, Maxwell::NumVertexArrays> buffers;
- std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets;
- std::array<VkDeviceSize, Maxwell::NumVertexArrays> sizes;
- std::array<u16, Maxwell::NumVertexArrays> strides;
- } vertex;
-
- struct {
- VkBuffer buffer = nullptr;
- VkDeviceSize offset;
- VkIndexType type;
- } index;
-
- template <std::size_t N>
- void BindStatic(const VKDevice& device, VKScheduler& scheduler) const {
- if (device.IsExtExtendedDynamicStateSupported()) {
- if (index.buffer) {
- BindStatic<N, true, true>(scheduler);
- } else {
- BindStatic<N, false, true>(scheduler);
- }
- } else {
- if (index.buffer) {
- BindStatic<N, true, false>(scheduler);
- } else {
- BindStatic<N, false, false>(scheduler);
- }
+ for (const auto& entry : entries.samplers) {
+ for (size_t i = 0; i < entry.size; ++i) {
+ const VkSampler sampler = *sampler_ptr++;
+ const ImageViewId image_view_id = *image_view_id_ptr++;
+ const ImageView& image_view = texture_cache.GetImageView(image_view_id);
+ const VkImageView handle = image_view.Handle(ImageViewTypeFromEntry(entry));
+ update_descriptor_queue.AddSampledImage(handle, sampler);
}
}
-
- template <std::size_t N, bool is_indexed, bool has_extended_dynamic_state>
- void BindStatic(VKScheduler& scheduler) const {
- static_assert(N <= Maxwell::NumVertexArrays);
- if constexpr (N == 0) {
- return;
- }
-
- std::array<VkBuffer, N> buffers;
- std::array<VkDeviceSize, N> offsets;
- std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin());
- std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin());
-
- if constexpr (has_extended_dynamic_state) {
- // With extended dynamic states we can specify the length and stride of a vertex buffer
- std::array<VkDeviceSize, N> sizes;
- std::array<u16, N> strides;
- std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin());
- std::copy(vertex.strides.begin(), vertex.strides.begin() + N, strides.begin());
-
- if constexpr (is_indexed) {
- scheduler.Record(
- [buffers, offsets, sizes, strides, index = index](vk::CommandBuffer cmdbuf) {
- cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type);
- cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
- offsets.data(), sizes.data(),
- ExpandStrides(strides).data());
- });
- } else {
- scheduler.Record([buffers, offsets, sizes, strides](vk::CommandBuffer cmdbuf) {
- cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
- offsets.data(), sizes.data(),
- ExpandStrides(strides).data());
- });
- }
- return;
- }
-
- if constexpr (is_indexed) {
- // Indexed draw
- scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) {
- cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type);
- cmdbuf.BindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data());
- });
- } else {
- // Array draw
- scheduler.Record([buffers, offsets](vk::CommandBuffer cmdbuf) {
- cmdbuf.BindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data());
- });
- }
+ for ([[maybe_unused]] const auto& entry : entries.storage_texels) {
+ const ImageViewId image_view_id = *image_view_id_ptr++;
+ const ImageView& image_view = texture_cache.GetImageView(image_view_id);
+ update_descriptor_queue.AddTexelBuffer(image_view.BufferView());
}
-};
-
-void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf) const {
- if (is_indexed) {
- cmdbuf.DrawIndexed(num_vertices, num_instances, 0, base_vertex, base_instance);
- } else {
- cmdbuf.Draw(num_vertices, num_instances, base_vertex, base_instance);
+ for (const auto& entry : entries.images) {
+ // TODO: Mark as modified
+ const ImageViewId image_view_id = *image_view_id_ptr++;
+ const ImageView& image_view = texture_cache.GetImageView(image_view_id);
+ const VkImageView handle = image_view.Handle(ImageViewTypeFromEntry(entry));
+ update_descriptor_queue.AddImage(handle);
+ }
+}
+
+DrawParams MakeDrawParams(const Maxwell& regs, u32 num_instances, bool is_instanced,
+ bool is_indexed) {
+ DrawParams params{
+ .base_instance = regs.vb_base_instance,
+ .num_instances = is_instanced ? num_instances : 1,
+ .base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first,
+ .num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count,
+ .is_indexed = is_indexed,
+ };
+ if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
+ // 6 triangle vertices per quad, base vertex is part of the index
+ // See BindQuadArrayIndexBuffer for more details
+ params.num_vertices = (params.num_vertices / 4) * 6;
+ params.base_vertex = 0;
+ params.is_indexed = true;
}
+ return params;
}
+} // Anonymous namespace
-RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& renderer,
- VKScreenInfo& screen_info, const VKDevice& device,
- VKResourceManager& resource_manager,
- VKMemoryManager& memory_manager, StateTracker& state_tracker,
- VKScheduler& scheduler)
- : RasterizerAccelerated{system.Memory()}, system{system}, render_window{renderer},
- screen_info{screen_info}, device{device}, resource_manager{resource_manager},
- memory_manager{memory_manager}, state_tracker{state_tracker}, scheduler{scheduler},
- staging_pool(device, memory_manager, scheduler), descriptor_pool(device),
- update_descriptor_queue(device, scheduler), renderpass_cache(device),
- quad_array_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
- quad_indexed_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
- uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
- texture_cache(system, *this, device, resource_manager, memory_manager, scheduler,
- staging_pool),
- pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue,
- renderpass_cache),
- buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool),
- sampler_cache(device),
- fence_manager(system, *this, device, scheduler, texture_cache, buffer_cache, query_cache),
- query_cache(system, *this, device, scheduler),
- wfi_event{device.GetLogical().CreateNewEvent()}, async_shaders{renderer} {
+RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
+ Tegra::MemoryManager& gpu_memory_,
+ Core::Memory::Memory& cpu_memory_, VKScreenInfo& screen_info_,
+ const Device& device_, MemoryAllocator& memory_allocator_,
+ StateTracker& state_tracker_, VKScheduler& scheduler_)
+ : RasterizerAccelerated{cpu_memory_}, gpu{gpu_},
+ gpu_memory{gpu_memory_}, maxwell3d{gpu.Maxwell3D()}, kepler_compute{gpu.KeplerCompute()},
+ screen_info{screen_info_}, device{device_}, memory_allocator{memory_allocator_},
+ state_tracker{state_tracker_}, scheduler{scheduler_},
+ staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler),
+ update_descriptor_queue(device, scheduler),
+ blit_image(device, scheduler, state_tracker, descriptor_pool),
+ texture_cache_runtime{device, scheduler, memory_allocator, staging_pool, blit_image},
+ texture_cache(texture_cache_runtime, *this, maxwell3d, kepler_compute, gpu_memory),
+ buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool,
+ update_descriptor_queue, descriptor_pool),
+ buffer_cache(*this, maxwell3d, kepler_compute, gpu_memory, cpu_memory_, buffer_cache_runtime),
+ pipeline_cache(*this, gpu, maxwell3d, kepler_compute, gpu_memory, device, scheduler,
+ descriptor_pool, update_descriptor_queue),
+ query_cache{*this, maxwell3d, gpu_memory, device, scheduler},
+ fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
+ wfi_event(device.GetLogical().CreateEvent()), async_shaders(emu_window_) {
scheduler.SetQueryCache(query_cache);
if (device.UseAsynchronousShaders()) {
async_shaders.AllocateWorkers();
@@ -414,64 +262,57 @@ RasterizerVulkan::~RasterizerVulkan() = default;
void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
MICROPROFILE_SCOPE(Vulkan_Drawing);
+ SCOPE_EXIT({ gpu.TickWork(); });
FlushWork();
query_cache.UpdateCounters();
- SCOPE_EXIT({ system.GPU().TickWork(); });
-
- const auto& gpu = system.GPU().Maxwell3D();
- GraphicsPipelineCacheKey key;
- key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported());
-
- buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
+ graphics_key.fixed_state.Refresh(maxwell3d, device.IsExtExtendedDynamicStateSupported());
- BufferBindings buffer_bindings;
- const DrawParameters draw_params =
- SetupGeometry(key.fixed_state, buffer_bindings, is_indexed, is_instanced);
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
- update_descriptor_queue.Acquire();
- sampled_views.clear();
- image_views.clear();
+ texture_cache.SynchronizeGraphicsDescriptors();
+ texture_cache.UpdateRenderTargets(false);
const auto shaders = pipeline_cache.GetShaders();
- key.shaders = GetShaderAddresses(shaders);
- SetupShaderDescriptors(shaders);
-
- buffer_cache.Unmap();
+ graphics_key.shaders = GetShaderAddresses(shaders);
- const Texceptions texceptions = UpdateAttachments(false);
- SetupImageTransitions(texceptions, color_attachments, zeta_attachment);
+ graphics_key.shaders = GetShaderAddresses(shaders);
+ SetupShaderDescriptors(shaders, is_indexed);
- key.renderpass_params = GetRenderPassParams(texceptions);
- key.padding = 0;
+ const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
+ graphics_key.renderpass = framebuffer->RenderPass();
- auto* pipeline = pipeline_cache.GetGraphicsPipeline(key, async_shaders);
+ VKGraphicsPipeline* const pipeline = pipeline_cache.GetGraphicsPipeline(
+ graphics_key, framebuffer->NumColorBuffers(), async_shaders);
if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) {
// Async graphics pipeline was not ready.
return;
}
- scheduler.BindGraphicsPipeline(pipeline->GetHandle());
-
- const auto renderpass = pipeline->GetRenderPass();
- const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass);
- scheduler.RequestRenderpass(renderpass, framebuffer, render_area);
+ BeginTransformFeedback();
+ scheduler.RequestRenderpass(framebuffer);
+ scheduler.BindGraphicsPipeline(pipeline->GetHandle());
UpdateDynamicStates();
- buffer_bindings.Bind(device, scheduler);
-
- BeginTransformFeedback();
-
- const auto pipeline_layout = pipeline->GetLayout();
- const auto descriptor_set = pipeline->CommitDescriptorSet();
+ const auto& regs = maxwell3d.regs;
+ const u32 num_instances = maxwell3d.mme_draw.instance_count;
+ const DrawParams draw_params = MakeDrawParams(regs, num_instances, is_instanced, is_indexed);
+ const VkPipelineLayout pipeline_layout = pipeline->GetLayout();
+ const VkDescriptorSet descriptor_set = pipeline->CommitDescriptorSet();
scheduler.Record([pipeline_layout, descriptor_set, draw_params](vk::CommandBuffer cmdbuf) {
if (descriptor_set) {
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout,
- DESCRIPTOR_SET, descriptor_set, {});
+ DESCRIPTOR_SET, descriptor_set, nullptr);
+ }
+ if (draw_params.is_indexed) {
+ cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances, 0,
+ draw_params.base_vertex, draw_params.base_instance);
+ } else {
+ cmdbuf.Draw(draw_params.num_vertices, draw_params.num_instances,
+ draw_params.base_vertex, draw_params.base_instance);
}
- draw_params.Draw(cmdbuf);
});
EndTransformFeedback();
@@ -480,17 +321,13 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
void RasterizerVulkan::Clear() {
MICROPROFILE_SCOPE(Vulkan_Clearing);
- const auto& gpu = system.GPU().Maxwell3D();
- if (!system.GPU().Maxwell3D().ShouldExecute()) {
+ if (!maxwell3d.ShouldExecute()) {
return;
}
- sampled_views.clear();
- image_views.clear();
-
query_cache.UpdateCounters();
- const auto& regs = gpu.regs;
+ const auto& regs = maxwell3d.regs;
const bool use_color = regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
regs.clear_buffers.A;
const bool use_depth = regs.clear_buffers.Z;
@@ -499,20 +336,24 @@ void RasterizerVulkan::Clear() {
return;
}
- [[maybe_unused]] const auto texceptions = UpdateAttachments(true);
- DEBUG_ASSERT(texceptions.none());
- SetupImageTransitions(0, color_attachments, zeta_attachment);
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.UpdateRenderTargets(true);
+ const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
+ const VkExtent2D render_area = framebuffer->RenderArea();
+ scheduler.RequestRenderpass(framebuffer);
- const VkRenderPass renderpass = renderpass_cache.GetRenderPass(GetRenderPassParams(0));
- const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass);
- scheduler.RequestRenderpass(renderpass, framebuffer, render_area);
-
- VkClearRect clear_rect;
- clear_rect.baseArrayLayer = regs.clear_buffers.layer;
- clear_rect.layerCount = 1;
- clear_rect.rect = GetScissorState(regs, 0);
- clear_rect.rect.extent.width = std::min(clear_rect.rect.extent.width, render_area.width);
- clear_rect.rect.extent.height = std::min(clear_rect.rect.extent.height, render_area.height);
+ VkClearRect clear_rect{
+ .rect = GetScissorState(regs, 0),
+ .baseArrayLayer = regs.clear_buffers.layer,
+ .layerCount = 1,
+ };
+ if (clear_rect.rect.extent.width == 0 || clear_rect.rect.extent.height == 0) {
+ return;
+ }
+ clear_rect.rect.extent = VkExtent2D{
+ .width = std::min(clear_rect.rect.extent.width, render_area.width),
+ .height = std::min(clear_rect.rect.extent.height, render_area.height),
+ };
if (use_color) {
VkClearValue clear_value;
@@ -539,7 +380,6 @@ void RasterizerVulkan::Clear() {
if (use_stencil) {
aspect_flags |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
-
scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil,
clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) {
VkClearAttachment attachment;
@@ -553,51 +393,69 @@ void RasterizerVulkan::Clear() {
void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
MICROPROFILE_SCOPE(Vulkan_Compute);
- update_descriptor_queue.Acquire();
- sampled_views.clear();
- image_views.clear();
query_cache.UpdateCounters();
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const auto& launch_desc = kepler_compute.launch_description;
auto& pipeline = pipeline_cache.GetComputePipeline({
.shader = code_addr,
.shared_memory_size = launch_desc.shared_alloc,
- .workgroup_size =
- {
- launch_desc.block_dim_x,
- launch_desc.block_dim_y,
- launch_desc.block_dim_z,
- },
+ .workgroup_size{
+ launch_desc.block_dim_x,
+ launch_desc.block_dim_y,
+ launch_desc.block_dim_z,
+ },
});
// Compute dispatches can't be executed inside a renderpass
scheduler.RequestOutsideRenderPassOperationContext();
- buffer_cache.Map(CalculateComputeStreamBufferSize());
+ image_view_indices.clear();
+ sampler_handles.clear();
+
+ std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
const auto& entries = pipeline.GetEntries();
- SetupComputeConstBuffers(entries);
- SetupComputeGlobalBuffers(entries);
+ buffer_cache.SetEnabledComputeUniformBuffers(entries.enabled_uniform_buffers);
+ buffer_cache.UnbindComputeStorageBuffers();
+ u32 ssbo_index = 0;
+ for (const auto& buffer : entries.global_buffers) {
+ buffer_cache.BindComputeStorageBuffer(ssbo_index, buffer.cbuf_index, buffer.cbuf_offset,
+ buffer.is_written);
+ ++ssbo_index;
+ }
+ buffer_cache.UpdateComputeBuffers();
+
+ texture_cache.SynchronizeComputeDescriptors();
+
SetupComputeUniformTexels(entries);
SetupComputeTextures(entries);
SetupComputeStorageTexels(entries);
SetupComputeImages(entries);
- buffer_cache.Unmap();
+ const std::span indices_span(image_view_indices.data(), image_view_indices.size());
+ texture_cache.FillComputeImageViews(indices_span, image_view_ids);
+
+ update_descriptor_queue.Acquire();
- TransitionImages(sampled_views, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
- VK_ACCESS_SHADER_READ_BIT);
- TransitionImages(image_views, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
- VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
+ buffer_cache.BindHostComputeBuffers();
+ ImageViewId* image_view_id_ptr = image_view_ids.data();
+ VkSampler* sampler_ptr = sampler_handles.data();
+ PushImageDescriptors(entries, texture_cache, update_descriptor_queue, image_view_id_ptr,
+ sampler_ptr);
+
+ const VkPipeline pipeline_handle = pipeline.GetHandle();
+ const VkPipelineLayout pipeline_layout = pipeline.GetLayout();
+ const VkDescriptorSet descriptor_set = pipeline.CommitDescriptorSet();
scheduler.Record([grid_x = launch_desc.grid_dim_x, grid_y = launch_desc.grid_dim_y,
- grid_z = launch_desc.grid_dim_z, pipeline_handle = pipeline.GetHandle(),
- layout = pipeline.GetLayout(),
- descriptor_set = pipeline.CommitDescriptorSet()](vk::CommandBuffer cmdbuf) {
+ grid_z = launch_desc.grid_dim_z, pipeline_handle, pipeline_layout,
+ descriptor_set](vk::CommandBuffer cmdbuf) {
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_handle);
- cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, DESCRIPTOR_SET,
- descriptor_set, {});
+ if (descriptor_set) {
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout,
+ DESCRIPTOR_SET, descriptor_set, nullptr);
+ }
cmdbuf.Dispatch(grid_x, grid_y, grid_z);
});
}
@@ -611,31 +469,50 @@ void RasterizerVulkan::Query(GPUVAddr gpu_addr, VideoCore::QueryType type,
query_cache.Query(gpu_addr, type, timestamp);
}
+void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
+ u32 size) {
+ buffer_cache.BindGraphicsUniformBuffer(stage, index, gpu_addr, size);
+}
+
void RasterizerVulkan::FlushAll() {}
void RasterizerVulkan::FlushRegion(VAddr addr, u64 size) {
if (addr == 0 || size == 0) {
return;
}
- texture_cache.FlushRegion(addr, size);
- buffer_cache.FlushRegion(addr, size);
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.DownloadMemory(addr, size);
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.DownloadMemory(addr, size);
+ }
query_cache.FlushRegion(addr, size);
}
bool RasterizerVulkan::MustFlushRegion(VAddr addr, u64 size) {
+ std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex};
if (!Settings::IsGPULevelHigh()) {
- return buffer_cache.MustFlushRegion(addr, size);
+ return buffer_cache.IsRegionGpuModified(addr, size);
}
- return texture_cache.MustFlushRegion(addr, size) || buffer_cache.MustFlushRegion(addr, size);
+ return texture_cache.IsRegionGpuModified(addr, size) ||
+ buffer_cache.IsRegionGpuModified(addr, size);
}
void RasterizerVulkan::InvalidateRegion(VAddr addr, u64 size) {
if (addr == 0 || size == 0) {
return;
}
- texture_cache.InvalidateRegion(addr, size);
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.WriteMemory(addr, size);
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.WriteMemory(addr, size);
+ }
pipeline_cache.InvalidateRegion(addr, size);
- buffer_cache.InvalidateRegion(addr, size);
query_cache.InvalidateRegion(addr, size);
}
@@ -643,28 +520,46 @@ void RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) {
if (addr == 0 || size == 0) {
return;
}
- texture_cache.OnCPUWrite(addr, size);
pipeline_cache.OnCPUWrite(addr, size);
- buffer_cache.OnCPUWrite(addr, size);
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.WriteMemory(addr, size);
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.CachedWriteMemory(addr, size);
+ }
}
void RasterizerVulkan::SyncGuestHost() {
- texture_cache.SyncGuestHost();
- buffer_cache.SyncGuestHost();
pipeline_cache.SyncGuestHost();
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.FlushCachedWrites();
+ }
+}
+
+void RasterizerVulkan::UnmapMemory(VAddr addr, u64 size) {
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.UnmapMemory(addr, size);
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.WriteMemory(addr, size);
+ }
+ pipeline_cache.OnCPUWrite(addr, size);
}
void RasterizerVulkan::SignalSemaphore(GPUVAddr addr, u32 value) {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
- gpu.MemoryManager().Write<u32>(addr, value);
+ gpu_memory.Write<u32>(addr, value);
return;
}
fence_manager.SignalSemaphore(addr, value);
}
void RasterizerVulkan::SignalSyncPoint(u32 value) {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
gpu.IncrementSyncPoint(value);
return;
@@ -673,7 +568,6 @@ void RasterizerVulkan::SignalSyncPoint(u32 value) {
}
void RasterizerVulkan::ReleaseFences() {
- auto& gpu{system.GPU()};
if (!gpu.IsAsync()) {
return;
}
@@ -707,6 +601,14 @@ void RasterizerVulkan::WaitForIdle() {
});
}
+void RasterizerVulkan::FragmentBarrier() {
+ // We already put barriers when a render pass finishes
+}
+
+void RasterizerVulkan::TiledCacheBarrier() {
+ // TODO: Implementing tiled barriers requires rewriting a good chunk of the Vulkan backend
+}
+
void RasterizerVulkan::FlushCommands() {
if (draw_counter > 0) {
draw_counter = 0;
@@ -717,14 +619,23 @@ void RasterizerVulkan::FlushCommands() {
void RasterizerVulkan::TickFrame() {
draw_counter = 0;
update_descriptor_queue.TickFrame();
- buffer_cache.TickFrame();
+ fence_manager.TickFrame();
staging_pool.TickFrame();
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.TickFrame();
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ buffer_cache.TickFrame();
+ }
}
-bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
- const Tegra::Engines::Fermi2D::Regs::Surface& dst,
+bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
+ const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Config& copy_config) {
- texture_cache.DoFermiCopy(src, dst, copy_config);
+ std::scoped_lock lock{texture_cache.mutex};
+ texture_cache.BlitImage(dst, src, copy_config);
return true;
}
@@ -733,28 +644,18 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
if (!framebuffer_addr) {
return false;
}
-
- const auto surface{texture_cache.TryFindFramebufferSurface(framebuffer_addr)};
- if (!surface) {
+ std::scoped_lock lock{texture_cache.mutex};
+ ImageView* const image_view = texture_cache.TryFindFramebufferImageView(framebuffer_addr);
+ if (!image_view) {
return false;
}
-
- // Verify that the cached surface is the same size and format as the requested framebuffer
- const auto& params{surface->GetSurfaceParams()};
- ASSERT_MSG(params.width == config.width, "Framebuffer width is different");
- ASSERT_MSG(params.height == config.height, "Framebuffer height is different");
-
- screen_info.image = &surface->GetImage();
- screen_info.width = params.width;
- screen_info.height = params.height;
- screen_info.is_srgb = surface->GetSurfaceParams().srgb_conversion;
+ screen_info.image_view = image_view->Handle(VideoCommon::ImageViewType::e2D);
+ screen_info.width = image_view->size.width;
+ screen_info.height = image_view->size.height;
+ screen_info.is_srgb = VideoCore::Surface::IsPixelFormatSRGB(image_view->format);
return true;
}
-void RasterizerVulkan::SetupDirtyFlags() {
- state_tracker.Initialize();
-}
-
void RasterizerVulkan::FlushWork() {
static constexpr u32 DRAWS_TO_DISPATCH = 4096;
@@ -776,178 +677,54 @@ void RasterizerVulkan::FlushWork() {
draw_counter = 0;
}
-RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments(bool is_clear) {
- MICROPROFILE_SCOPE(Vulkan_RenderTargets);
- auto& maxwell3d = system.GPU().Maxwell3D();
- auto& dirty = maxwell3d.dirty.flags;
- auto& regs = maxwell3d.regs;
-
- const bool update_rendertargets = dirty[VideoCommon::Dirty::RenderTargets];
- dirty[VideoCommon::Dirty::RenderTargets] = false;
-
- texture_cache.GuardRenderTargets(true);
-
- Texceptions texceptions;
- for (std::size_t rt = 0; rt < Maxwell::NumRenderTargets; ++rt) {
- if (update_rendertargets) {
- const bool preserve_contents = HasToPreserveColorContents(is_clear, regs);
- color_attachments[rt] = texture_cache.GetColorBufferSurface(rt, preserve_contents);
- }
- if (color_attachments[rt] && WalkAttachmentOverlaps(*color_attachments[rt])) {
- texceptions[rt] = true;
- }
- }
-
- if (update_rendertargets) {
- const bool preserve_contents = HasToPreserveDepthContents(is_clear, regs);
- zeta_attachment = texture_cache.GetDepthBufferSurface(preserve_contents);
- }
- if (zeta_attachment && WalkAttachmentOverlaps(*zeta_attachment)) {
- texceptions[ZETA_TEXCEPTION_INDEX] = true;
- }
-
- texture_cache.GuardRenderTargets(false);
-
- return texceptions;
-}
-
-bool RasterizerVulkan::WalkAttachmentOverlaps(const CachedSurfaceView& attachment) {
- bool overlap = false;
- for (auto& [view, layout] : sampled_views) {
- if (!attachment.IsSameSurface(*view)) {
- continue;
- }
- overlap = true;
- *layout = VK_IMAGE_LAYOUT_GENERAL;
- }
- return overlap;
-}
-
-std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers(
- VkRenderPass renderpass) {
- FramebufferCacheKey key{
- .renderpass = renderpass,
- .width = std::numeric_limits<u32>::max(),
- .height = std::numeric_limits<u32>::max(),
- .layers = std::numeric_limits<u32>::max(),
- .views = {},
- };
-
- const auto try_push = [&key](const View& view) {
- if (!view) {
- return false;
- }
- key.views.push_back(view->GetAttachment());
- key.width = std::min(key.width, view->GetWidth());
- key.height = std::min(key.height, view->GetHeight());
- key.layers = std::min(key.layers, view->GetNumLayers());
- return true;
- };
-
- const auto& regs = system.GPU().Maxwell3D().regs;
- const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count);
- for (std::size_t index = 0; index < num_attachments; ++index) {
- if (try_push(color_attachments[index])) {
- texture_cache.MarkColorBufferInUse(index);
- }
- }
- if (try_push(zeta_attachment)) {
- texture_cache.MarkDepthBufferInUse();
- }
-
- const auto [fbentry, is_cache_miss] = framebuffer_cache.try_emplace(key);
- auto& framebuffer = fbentry->second;
- if (is_cache_miss) {
- framebuffer = device.GetLogical().CreateFramebuffer({
- .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .renderPass = key.renderpass,
- .attachmentCount = static_cast<u32>(key.views.size()),
- .pAttachments = key.views.data(),
- .width = key.width,
- .height = key.height,
- .layers = key.layers,
- });
- }
-
- return {*framebuffer, VkExtent2D{key.width, key.height}};
-}
-
-RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineState& fixed_state,
- BufferBindings& buffer_bindings,
- bool is_indexed,
- bool is_instanced) {
- MICROPROFILE_SCOPE(Vulkan_Geometry);
-
- const auto& gpu = system.GPU().Maxwell3D();
- const auto& regs = gpu.regs;
-
- SetupVertexArrays(buffer_bindings);
-
- const u32 base_instance = regs.vb_base_instance;
- const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
- const u32 base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first;
- const u32 num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count;
-
- DrawParameters params{base_instance, num_instances, base_vertex, num_vertices, is_indexed};
- SetupIndexBuffer(buffer_bindings, params, is_indexed);
-
- return params;
-}
-
void RasterizerVulkan::SetupShaderDescriptors(
- const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders) {
- texture_cache.GuardSamplers(true);
-
- for (std::size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) {
- // Skip VertexA stage
+ const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders, bool is_indexed) {
+ image_view_indices.clear();
+ sampler_handles.clear();
+ for (size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) {
Shader* const shader = shaders[stage + 1];
if (!shader) {
continue;
}
- const auto& entries = shader->GetEntries();
- SetupGraphicsConstBuffers(entries, stage);
- SetupGraphicsGlobalBuffers(entries, stage);
+ const ShaderEntries& entries = shader->GetEntries();
SetupGraphicsUniformTexels(entries, stage);
SetupGraphicsTextures(entries, stage);
SetupGraphicsStorageTexels(entries, stage);
SetupGraphicsImages(entries, stage);
+
+ buffer_cache.SetEnabledUniformBuffers(stage, entries.enabled_uniform_buffers);
+ buffer_cache.UnbindGraphicsStorageBuffers(stage);
+ u32 ssbo_index = 0;
+ for (const auto& buffer : entries.global_buffers) {
+ buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, buffer.cbuf_index,
+ buffer.cbuf_offset, buffer.is_written);
+ ++ssbo_index;
+ }
}
- texture_cache.GuardSamplers(false);
-}
+ const std::span indices_span(image_view_indices.data(), image_view_indices.size());
+ buffer_cache.UpdateGraphicsBuffers(is_indexed);
+ texture_cache.FillGraphicsImageViews(indices_span, image_view_ids);
+
+ buffer_cache.BindHostGeometryBuffers(is_indexed);
-void RasterizerVulkan::SetupImageTransitions(
- Texceptions texceptions, const std::array<View, Maxwell::NumRenderTargets>& color_attachments,
- const View& zeta_attachment) {
- TransitionImages(sampled_views, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT);
- TransitionImages(image_views, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT,
- VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
+ update_descriptor_queue.Acquire();
- for (std::size_t rt = 0; rt < std::size(color_attachments); ++rt) {
- const auto color_attachment = color_attachments[rt];
- if (color_attachment == nullptr) {
+ ImageViewId* image_view_id_ptr = image_view_ids.data();
+ VkSampler* sampler_ptr = sampler_handles.data();
+ for (size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) {
+ // Skip VertexA stage
+ Shader* const shader = shaders[stage + 1];
+ if (!shader) {
continue;
}
- const auto image_layout =
- texceptions[rt] ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- color_attachment->Transition(image_layout, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
- VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
- VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
- }
-
- if (zeta_attachment != nullptr) {
- const auto image_layout = texceptions[ZETA_TEXCEPTION_INDEX]
- ? VK_IMAGE_LAYOUT_GENERAL
- : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- zeta_attachment->Transition(image_layout, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
- VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
- VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
+ buffer_cache.BindHostStageBuffers(stage);
+ PushImageDescriptors(shader->GetEntries(), texture_cache, update_descriptor_queue,
+ image_view_id_ptr, sampler_ptr);
}
}
void RasterizerVulkan::UpdateDynamicStates() {
- auto& regs = system.GPU().Maxwell3D().regs;
+ auto& regs = maxwell3d.regs;
UpdateViewportsState(regs);
UpdateScissorsState(regs);
UpdateDepthBias(regs);
@@ -961,14 +738,13 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateDepthWriteEnable(regs);
UpdateDepthCompareOp(regs);
UpdateFrontFace(regs);
- UpdatePrimitiveTopology(regs);
UpdateStencilOp(regs);
UpdateStencilTestEnable(regs);
}
}
void RasterizerVulkan::BeginTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
@@ -976,322 +752,108 @@ void RasterizerVulkan::BeginTransformFeedback() {
LOG_ERROR(Render_Vulkan, "Transform feedbacks used but not supported");
return;
}
-
UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationControl) ||
regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationEval) ||
regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::Geometry));
-
- UNIMPLEMENTED_IF(regs.tfb_bindings[1].buffer_enable);
- UNIMPLEMENTED_IF(regs.tfb_bindings[2].buffer_enable);
- UNIMPLEMENTED_IF(regs.tfb_bindings[3].buffer_enable);
-
- const auto& binding = regs.tfb_bindings[0];
- UNIMPLEMENTED_IF(binding.buffer_enable == 0);
- UNIMPLEMENTED_IF(binding.buffer_offset != 0);
-
- const GPUVAddr gpu_addr = binding.Address();
- const VkDeviceSize size = static_cast<VkDeviceSize>(binding.buffer_size);
- const auto info = buffer_cache.UploadMemory(gpu_addr, size, 4, true);
-
- scheduler.Record([buffer = info.handle, offset = info.offset, size](vk::CommandBuffer cmdbuf) {
- cmdbuf.BindTransformFeedbackBuffersEXT(0, 1, &buffer, &offset, &size);
- cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr);
- });
+ scheduler.Record(
+ [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); });
}
void RasterizerVulkan::EndTransformFeedback() {
- const auto& regs = system.GPU().Maxwell3D().regs;
+ const auto& regs = maxwell3d.regs;
if (regs.tfb_enabled == 0) {
return;
}
if (!device.IsExtTransformFeedbackSupported()) {
return;
}
-
scheduler.Record(
[](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
}
-void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) {
- const auto& regs = system.GPU().Maxwell3D().regs;
-
- for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
- const auto& vertex_array = regs.vertex_array[index];
- if (!vertex_array.IsEnabled()) {
- continue;
- }
- const GPUVAddr start{vertex_array.StartAddress()};
- const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
-
- ASSERT(end >= start);
- const std::size_t size = end - start;
- if (size == 0) {
- buffer_bindings.AddVertexBinding(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE, 0);
- continue;
- }
- const auto info = buffer_cache.UploadMemory(start, size);
- buffer_bindings.AddVertexBinding(info.handle, info.offset, size, vertex_array.stride);
- }
-}
-
-void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params,
- bool is_indexed) {
- if (params.num_vertices == 0) {
- return;
- }
- const auto& regs = system.GPU().Maxwell3D().regs;
- switch (regs.draw.topology) {
- case Maxwell::PrimitiveTopology::Quads: {
- if (!params.is_indexed) {
- const auto [buffer, offset] =
- quad_array_pass.Assemble(params.num_vertices, params.base_vertex);
- buffer_bindings.SetIndexBinding(buffer, offset, VK_INDEX_TYPE_UINT32);
- params.base_vertex = 0;
- params.num_vertices = params.num_vertices * 6 / 4;
- params.is_indexed = true;
- break;
- }
- const GPUVAddr gpu_addr = regs.index_array.IndexStart();
- const auto info = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize());
- VkBuffer buffer = info.handle;
- u64 offset = info.offset;
- std::tie(buffer, offset) = quad_indexed_pass.Assemble(
- regs.index_array.format, params.num_vertices, params.base_vertex, buffer, offset);
-
- buffer_bindings.SetIndexBinding(buffer, offset, VK_INDEX_TYPE_UINT32);
- params.num_vertices = (params.num_vertices / 4) * 6;
- params.base_vertex = 0;
- break;
- }
- default: {
- if (!is_indexed) {
- break;
- }
- const GPUVAddr gpu_addr = regs.index_array.IndexStart();
- const auto info = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize());
- VkBuffer buffer = info.handle;
- u64 offset = info.offset;
-
- auto format = regs.index_array.format;
- const bool is_uint8 = format == Maxwell::IndexFormat::UnsignedByte;
- if (is_uint8 && !device.IsExtIndexTypeUint8Supported()) {
- std::tie(buffer, offset) = uint8_pass.Assemble(params.num_vertices, buffer, offset);
- format = Maxwell::IndexFormat::UnsignedShort;
- }
-
- buffer_bindings.SetIndexBinding(buffer, offset, MaxwellToVK::IndexFormat(device, format));
- break;
- }
- }
-}
-
-void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage) {
- MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
- const auto& gpu = system.GPU().Maxwell3D();
- const auto& shader_stage = gpu.state.shader_stages[stage];
- for (const auto& entry : entries.const_buffers) {
- SetupConstBuffer(entry, shader_stage.const_buffers[entry.GetIndex()]);
- }
-}
-
-void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage) {
- MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
- auto& gpu{system.GPU()};
- const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage]};
-
- for (const auto& entry : entries.global_buffers) {
- const auto addr = cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset();
- SetupGlobalBuffer(entry, addr);
- }
-}
-
-void RasterizerVulkan::SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage) {
- MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().Maxwell3D();
+void RasterizerVulkan::SetupGraphicsUniformTexels(const ShaderEntries& entries, size_t stage) {
+ const auto& regs = maxwell3d.regs;
+ const bool via_header_index = regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex;
for (const auto& entry : entries.uniform_texels) {
- const auto image = GetTextureInfo(gpu, entry, stage).tic;
- SetupUniformTexels(image, entry);
+ const TextureHandle handle = GetTextureInfo(maxwell3d, via_header_index, entry, stage);
+ image_view_indices.push_back(handle.image);
}
}
-void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage) {
- MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().Maxwell3D();
+void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, size_t stage) {
+ const auto& regs = maxwell3d.regs;
+ const bool via_header_index = regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex;
for (const auto& entry : entries.samplers) {
- for (std::size_t i = 0; i < entry.size; ++i) {
- const auto texture = GetTextureInfo(gpu, entry, stage, i);
- SetupTexture(texture, entry);
+ for (size_t index = 0; index < entry.size; ++index) {
+ const TextureHandle handle =
+ GetTextureInfo(maxwell3d, via_header_index, entry, stage, index);
+ image_view_indices.push_back(handle.image);
+
+ Sampler* const sampler = texture_cache.GetGraphicsSampler(handle.sampler);
+ sampler_handles.push_back(sampler->Handle());
}
}
}
-void RasterizerVulkan::SetupGraphicsStorageTexels(const ShaderEntries& entries, std::size_t stage) {
- MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().Maxwell3D();
+void RasterizerVulkan::SetupGraphicsStorageTexels(const ShaderEntries& entries, size_t stage) {
+ const auto& regs = maxwell3d.regs;
+ const bool via_header_index = regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex;
for (const auto& entry : entries.storage_texels) {
- const auto image = GetTextureInfo(gpu, entry, stage).tic;
- SetupStorageTexel(image, entry);
+ const TextureHandle handle = GetTextureInfo(maxwell3d, via_header_index, entry, stage);
+ image_view_indices.push_back(handle.image);
}
}
-void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage) {
- MICROPROFILE_SCOPE(Vulkan_Images);
- const auto& gpu = system.GPU().Maxwell3D();
+void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, size_t stage) {
+ const auto& regs = maxwell3d.regs;
+ const bool via_header_index = regs.sampler_index == Maxwell::SamplerIndex::ViaHeaderIndex;
for (const auto& entry : entries.images) {
- const auto tic = GetTextureInfo(gpu, entry, stage).tic;
- SetupImage(tic, entry);
- }
-}
-
-void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) {
- MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
- const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
- for (const auto& entry : entries.const_buffers) {
- const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
- const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
- Tegra::Engines::ConstBufferInfo buffer;
- buffer.address = config.Address();
- buffer.size = config.size;
- buffer.enabled = mask[entry.GetIndex()];
- SetupConstBuffer(entry, buffer);
- }
-}
-
-void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) {
- MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
- const auto cbufs{system.GPU().KeplerCompute().launch_description.const_buffer_config};
- for (const auto& entry : entries.global_buffers) {
- const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()};
- SetupGlobalBuffer(entry, addr);
+ const TextureHandle handle = GetTextureInfo(maxwell3d, via_header_index, entry, stage);
+ image_view_indices.push_back(handle.image);
}
}
void RasterizerVulkan::SetupComputeUniformTexels(const ShaderEntries& entries) {
- MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().KeplerCompute();
+ const bool via_header_index = kepler_compute.launch_description.linked_tsc;
for (const auto& entry : entries.uniform_texels) {
- const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
- SetupUniformTexels(image, entry);
+ const TextureHandle handle =
+ GetTextureInfo(kepler_compute, via_header_index, entry, COMPUTE_SHADER_INDEX);
+ image_view_indices.push_back(handle.image);
}
}
void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) {
- MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().KeplerCompute();
+ const bool via_header_index = kepler_compute.launch_description.linked_tsc;
for (const auto& entry : entries.samplers) {
- for (std::size_t i = 0; i < entry.size; ++i) {
- const auto texture = GetTextureInfo(gpu, entry, ComputeShaderIndex, i);
- SetupTexture(texture, entry);
+ for (size_t index = 0; index < entry.size; ++index) {
+ const TextureHandle handle = GetTextureInfo(kepler_compute, via_header_index, entry,
+ COMPUTE_SHADER_INDEX, index);
+ image_view_indices.push_back(handle.image);
+
+ Sampler* const sampler = texture_cache.GetComputeSampler(handle.sampler);
+ sampler_handles.push_back(sampler->Handle());
}
}
}
void RasterizerVulkan::SetupComputeStorageTexels(const ShaderEntries& entries) {
- MICROPROFILE_SCOPE(Vulkan_Textures);
- const auto& gpu = system.GPU().KeplerCompute();
+ const bool via_header_index = kepler_compute.launch_description.linked_tsc;
for (const auto& entry : entries.storage_texels) {
- const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
- SetupStorageTexel(image, entry);
+ const TextureHandle handle =
+ GetTextureInfo(kepler_compute, via_header_index, entry, COMPUTE_SHADER_INDEX);
+ image_view_indices.push_back(handle.image);
}
}
void RasterizerVulkan::SetupComputeImages(const ShaderEntries& entries) {
- MICROPROFILE_SCOPE(Vulkan_Images);
- const auto& gpu = system.GPU().KeplerCompute();
+ const bool via_header_index = kepler_compute.launch_description.linked_tsc;
for (const auto& entry : entries.images) {
- const auto tic = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
- SetupImage(tic, entry);
+ const TextureHandle handle =
+ GetTextureInfo(kepler_compute, via_header_index, entry, COMPUTE_SHADER_INDEX);
+ image_view_indices.push_back(handle.image);
}
}
-void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry,
- const Tegra::Engines::ConstBufferInfo& buffer) {
- if (!buffer.enabled) {
- // Set values to zero to unbind buffers
- update_descriptor_queue.AddBuffer(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE);
- return;
- }
-
- // Align the size to avoid bad std140 interactions
- const std::size_t size =
- Common::AlignUp(CalculateConstBufferSize(entry, buffer), 4 * sizeof(float));
- ASSERT(size <= MaxConstbufferSize);
-
- const auto info =
- buffer_cache.UploadMemory(buffer.address, size, device.GetUniformBufferAlignment());
- update_descriptor_queue.AddBuffer(info.handle, info.offset, size);
-}
-
-void RasterizerVulkan::SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address) {
- auto& memory_manager{system.GPU().MemoryManager()};
- const auto actual_addr = memory_manager.Read<u64>(address);
- const auto size = memory_manager.Read<u32>(address + 8);
-
- if (size == 0) {
- // Sometimes global memory pointers don't have a proper size. Upload a dummy entry
- // because Vulkan doesn't like empty buffers.
- // Note: Do *not* use DefaultBuffer() here, storage buffers can be written breaking the
- // default buffer.
- static constexpr std::size_t dummy_size = 4;
- const auto info = buffer_cache.GetEmptyBuffer(dummy_size);
- update_descriptor_queue.AddBuffer(info.handle, info.offset, dummy_size);
- return;
- }
-
- const auto info = buffer_cache.UploadMemory(
- actual_addr, size, device.GetStorageBufferAlignment(), entry.IsWritten());
- update_descriptor_queue.AddBuffer(info.handle, info.offset, size);
-}
-
-void RasterizerVulkan::SetupUniformTexels(const Tegra::Texture::TICEntry& tic,
- const UniformTexelEntry& entry) {
- const auto view = texture_cache.GetTextureSurface(tic, entry);
- ASSERT(view->IsBufferView());
-
- update_descriptor_queue.AddTexelBuffer(view->GetBufferView());
-}
-
-void RasterizerVulkan::SetupTexture(const Tegra::Texture::FullTextureInfo& texture,
- const SamplerEntry& entry) {
- auto view = texture_cache.GetTextureSurface(texture.tic, entry);
- ASSERT(!view->IsBufferView());
-
- const VkImageView image_view = view->GetImageView(texture.tic.x_source, texture.tic.y_source,
- texture.tic.z_source, texture.tic.w_source);
- const auto sampler = sampler_cache.GetSampler(texture.tsc);
- update_descriptor_queue.AddSampledImage(sampler, image_view);
-
- VkImageLayout* const image_layout = update_descriptor_queue.LastImageLayout();
- *image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
- sampled_views.push_back(ImageView{std::move(view), image_layout});
-}
-
-void RasterizerVulkan::SetupStorageTexel(const Tegra::Texture::TICEntry& tic,
- const StorageTexelEntry& entry) {
- const auto view = texture_cache.GetImageSurface(tic, entry);
- ASSERT(view->IsBufferView());
-
- update_descriptor_queue.AddTexelBuffer(view->GetBufferView());
-}
-
-void RasterizerVulkan::SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry) {
- auto view = texture_cache.GetImageSurface(tic, entry);
-
- if (entry.is_written) {
- view->MarkAsModified(texture_cache.Tick());
- }
-
- UNIMPLEMENTED_IF(tic.IsBuffer());
-
- const VkImageView image_view =
- view->GetImageView(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
- update_descriptor_queue.AddImage(image_view);
-
- VkImageLayout* const image_layout = update_descriptor_queue.LastImageLayout();
- *image_layout = VK_IMAGE_LAYOUT_GENERAL;
- image_views.push_back(ImageView{std::move(view), image_layout});
-}
-
void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchViewports()) {
return;
@@ -1304,7 +866,8 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg
GetViewportState(device, regs, 8), GetViewportState(device, regs, 9),
GetViewportState(device, regs, 10), GetViewportState(device, regs, 11),
GetViewportState(device, regs, 12), GetViewportState(device, regs, 13),
- GetViewportState(device, regs, 14), GetViewportState(device, regs, 15)};
+ GetViewportState(device, regs, 14), GetViewportState(device, regs, 15),
+ };
scheduler.Record([viewports](vk::CommandBuffer cmdbuf) { cmdbuf.SetViewport(0, viewports); });
}
@@ -1312,13 +875,14 @@ void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs
if (!state_tracker.TouchScissors()) {
return;
}
- const std::array scissors = {
+ const std::array scissors{
GetScissorState(regs, 0), GetScissorState(regs, 1), GetScissorState(regs, 2),
GetScissorState(regs, 3), GetScissorState(regs, 4), GetScissorState(regs, 5),
GetScissorState(regs, 6), GetScissorState(regs, 7), GetScissorState(regs, 8),
GetScissorState(regs, 9), GetScissorState(regs, 10), GetScissorState(regs, 11),
GetScissorState(regs, 12), GetScissorState(regs, 13), GetScissorState(regs, 14),
- GetScissorState(regs, 15)};
+ GetScissorState(regs, 15),
+ };
scheduler.Record([scissors](vk::CommandBuffer cmdbuf) { cmdbuf.SetScissor(0, scissors); });
}
@@ -1442,16 +1006,6 @@ void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) {
[front_face](vk::CommandBuffer cmdbuf) { cmdbuf.SetFrontFaceEXT(front_face); });
}
-void RasterizerVulkan::UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs) {
- const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value();
- if (!state_tracker.ChangePrimitiveTopology(primitive_topology)) {
- return;
- }
- scheduler.Record([this, primitive_topology](vk::CommandBuffer cmdbuf) {
- cmdbuf.SetPrimitiveTopologyEXT(MaxwellToVK::PrimitiveTopology(device, primitive_topology));
- });
-}
-
void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchStencilOp()) {
return;
@@ -1493,101 +1047,4 @@ void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs&
});
}
-std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const {
- std::size_t size = CalculateVertexArraysSize();
- if (is_indexed) {
- size = Common::AlignUp(size, 4) + CalculateIndexBufferSize();
- }
- size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + device.GetUniformBufferAlignment());
- return size;
-}
-
-std::size_t RasterizerVulkan::CalculateComputeStreamBufferSize() const {
- return Tegra::Engines::KeplerCompute::NumConstBuffers *
- (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment());
-}
-
-std::size_t RasterizerVulkan::CalculateVertexArraysSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
-
- std::size_t size = 0;
- for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
- // This implementation assumes that all attributes are used in the shader.
- const GPUVAddr start{regs.vertex_array[index].StartAddress()};
- const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
- DEBUG_ASSERT(end >= start);
-
- size += (end - start) * regs.vertex_array[index].enable;
- }
- return size;
-}
-
-std::size_t RasterizerVulkan::CalculateIndexBufferSize() const {
- const auto& regs = system.GPU().Maxwell3D().regs;
- return static_cast<std::size_t>(regs.index_array.count) *
- static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
-}
-
-std::size_t RasterizerVulkan::CalculateConstBufferSize(
- const ConstBufferEntry& entry, const Tegra::Engines::ConstBufferInfo& buffer) const {
- if (entry.IsIndirect()) {
- // Buffer is accessed indirectly, so upload the entire thing
- return buffer.size;
- } else {
- // Buffer is accessed directly, upload just what we use
- return entry.GetSize();
- }
-}
-
-RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions) const {
- const auto& regs = system.GPU().Maxwell3D().regs;
- const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count);
-
- RenderPassParams params;
- params.color_formats = {};
- std::size_t color_texceptions = 0;
-
- std::size_t index = 0;
- for (std::size_t rt = 0; rt < num_attachments; ++rt) {
- const auto& rendertarget = regs.rt[rt];
- if (rendertarget.Address() == 0 || rendertarget.format == Tegra::RenderTargetFormat::NONE) {
- continue;
- }
- params.color_formats[index] = static_cast<u8>(rendertarget.format);
- color_texceptions |= (texceptions[rt] ? 1ULL : 0ULL) << index;
- ++index;
- }
- params.num_color_attachments = static_cast<u8>(index);
- params.texceptions = static_cast<u8>(color_texceptions);
-
- params.zeta_format = regs.zeta_enable ? static_cast<u8>(regs.zeta.format) : 0;
- params.zeta_texception = texceptions[ZETA_TEXCEPTION_INDEX];
- return params;
-}
-
-VkBuffer RasterizerVulkan::DefaultBuffer() {
- if (default_buffer) {
- return *default_buffer;
- }
-
- default_buffer = device.GetLogical().CreateBuffer({
- .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .size = DEFAULT_BUFFER_SIZE,
- .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
- VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
- .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
- .queueFamilyIndexCount = 0,
- .pQueueFamilyIndices = nullptr,
- });
- default_buffer_commit = memory_manager.Commit(default_buffer, false);
-
- scheduler.RequestOutsideRenderPassOperationContext();
- scheduler.Record([buffer = *default_buffer](vk::CommandBuffer cmdbuf) {
- cmdbuf.FillBuffer(buffer, 0, DEFAULT_BUFFER_SIZE, 0);
- });
- return *default_buffer;
-}
-
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index f640ba649..acea1ba2d 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -11,28 +11,25 @@
#include <vector>
#include <boost/container/static_vector.hpp>
-#include <boost/functional/hash.hpp>
#include "common/common_types.h"
#include "video_core/rasterizer_accelerated.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_vulkan/blit_image.h"
#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
-#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_fence_manager.h"
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
+#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
-#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/vk_sampler_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
-#include "video_core/renderer_vulkan/wrapper.h"
#include "video_core/shader/async_shaders.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Core {
class System;
@@ -50,66 +47,15 @@ namespace Vulkan {
struct VKScreenInfo;
-using ImageViewsPack = boost::container::static_vector<VkImageView, Maxwell::NumRenderTargets + 1>;
-
-struct FramebufferCacheKey {
- VkRenderPass renderpass{};
- u32 width = 0;
- u32 height = 0;
- u32 layers = 0;
- ImageViewsPack views;
-
- std::size_t Hash() const noexcept {
- std::size_t hash = 0;
- boost::hash_combine(hash, static_cast<VkRenderPass>(renderpass));
- for (const auto& view : views) {
- boost::hash_combine(hash, static_cast<VkImageView>(view));
- }
- boost::hash_combine(hash, width);
- boost::hash_combine(hash, height);
- boost::hash_combine(hash, layers);
- return hash;
- }
-
- bool operator==(const FramebufferCacheKey& rhs) const noexcept {
- return std::tie(renderpass, views, width, height, layers) ==
- std::tie(rhs.renderpass, rhs.views, rhs.width, rhs.height, rhs.layers);
- }
-
- bool operator!=(const FramebufferCacheKey& rhs) const noexcept {
- return !operator==(rhs);
- }
-};
-
-} // namespace Vulkan
-
-namespace std {
-
-template <>
-struct hash<Vulkan::FramebufferCacheKey> {
- std::size_t operator()(const Vulkan::FramebufferCacheKey& k) const noexcept {
- return k.Hash();
- }
-};
-
-} // namespace std
-
-namespace Vulkan {
-
class StateTracker;
-class BufferBindings;
-
-struct ImageView {
- View view;
- VkImageLayout* layout = nullptr;
-};
class RasterizerVulkan final : public VideoCore::RasterizerAccelerated {
public:
- explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window,
- VKScreenInfo& screen_info, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- StateTracker& state_tracker, VKScheduler& scheduler);
+ explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
+ Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
+ VKScreenInfo& screen_info_, const Device& device_,
+ MemoryAllocator& memory_allocator_, StateTracker& state_tracker_,
+ VKScheduler& scheduler_);
~RasterizerVulkan() override;
void Draw(bool is_indexed, bool is_instanced) override;
@@ -117,25 +63,28 @@ public:
void DispatchCompute(GPUVAddr code_addr) override;
void ResetCounter(VideoCore::QueryType type) override;
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
+ void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override;
void FlushAll() override;
void FlushRegion(VAddr addr, u64 size) override;
bool MustFlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void OnCPUWrite(VAddr addr, u64 size) override;
void SyncGuestHost() override;
+ void UnmapMemory(VAddr addr, u64 size) override;
void SignalSemaphore(GPUVAddr addr, u32 value) override;
void SignalSyncPoint(u32 value) override;
void ReleaseFences() override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
void WaitForIdle() override;
+ void FragmentBarrier() override;
+ void TiledCacheBarrier() override;
void FlushCommands() override;
void TickFrame() override;
- bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
- const Tegra::Engines::Fermi2D::Regs::Surface& dst,
+ bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
+ const Tegra::Engines::Fermi2D::Surface& dst,
const Tegra::Engines::Fermi2D::Config& copy_config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
- void SetupDirtyFlags() override;
VideoCommon::Shader::AsyncShaders& GetAsyncShaders() {
return async_shaders;
@@ -146,45 +95,22 @@ public:
}
/// Maximum supported size that a constbuffer can have in bytes.
- static constexpr std::size_t MaxConstbufferSize = 0x10000;
+ static constexpr size_t MaxConstbufferSize = 0x10000;
static_assert(MaxConstbufferSize % (4 * sizeof(float)) == 0,
"The maximum size of a constbuffer must be a multiple of the size of GLvec4");
private:
- struct DrawParameters {
- void Draw(vk::CommandBuffer cmdbuf) const;
+ static constexpr size_t MAX_TEXTURES = 192;
+ static constexpr size_t MAX_IMAGES = 48;
+ static constexpr size_t MAX_IMAGE_VIEWS = MAX_TEXTURES + MAX_IMAGES;
- u32 base_instance = 0;
- u32 num_instances = 0;
- u32 base_vertex = 0;
- u32 num_vertices = 0;
- bool is_indexed = 0;
- };
-
- using Texceptions = std::bitset<Maxwell::NumRenderTargets + 1>;
-
- static constexpr std::size_t ZETA_TEXCEPTION_INDEX = 8;
static constexpr VkDeviceSize DEFAULT_BUFFER_SIZE = 4 * sizeof(float);
void FlushWork();
- /// @brief Updates the currently bound attachments
- /// @param is_clear True when the framebuffer is updated as a clear
- /// @return Bitfield of attachments being used as sampled textures
- Texceptions UpdateAttachments(bool is_clear);
-
- std::tuple<VkFramebuffer, VkExtent2D> ConfigureFramebuffers(VkRenderPass renderpass);
-
- /// Setups geometry buffers and state.
- DrawParameters SetupGeometry(FixedPipelineState& fixed_state, BufferBindings& buffer_bindings,
- bool is_indexed, bool is_instanced);
-
/// Setup descriptors in the graphics pipeline.
- void SetupShaderDescriptors(const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders);
-
- void SetupImageTransitions(Texceptions texceptions,
- const std::array<View, Maxwell::NumRenderTargets>& color_attachments,
- const View& zeta_attachment);
+ void SetupShaderDescriptors(const std::array<Shader*, Maxwell::MaxShaderProgram>& shaders,
+ bool is_indexed);
void UpdateDynamicStates();
@@ -192,18 +118,6 @@ private:
void EndTransformFeedback();
- bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment);
-
- void SetupVertexArrays(BufferBindings& buffer_bindings);
-
- void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed);
-
- /// Setup constant buffers in the graphics pipeline.
- void SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage);
-
- /// Setup global buffers in the graphics pipeline.
- void SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage);
-
/// Setup uniform texels in the graphics pipeline.
void SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage);
@@ -216,12 +130,6 @@ private:
/// Setup images in the graphics pipeline.
void SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage);
- /// Setup constant buffers in the compute pipeline.
- void SetupComputeConstBuffers(const ShaderEntries& entries);
-
- /// Setup global buffers in the compute pipeline.
- void SetupComputeGlobalBuffers(const ShaderEntries& entries);
-
/// Setup texel buffers in the compute pipeline.
void SetupComputeUniformTexels(const ShaderEntries& entries);
@@ -234,19 +142,6 @@ private:
/// Setup images in the compute pipeline.
void SetupComputeImages(const ShaderEntries& entries);
- void SetupConstBuffer(const ConstBufferEntry& entry,
- const Tegra::Engines::ConstBufferInfo& buffer);
-
- void SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address);
-
- void SetupUniformTexels(const Tegra::Texture::TICEntry& image, const UniformTexelEntry& entry);
-
- void SetupTexture(const Tegra::Texture::FullTextureInfo& texture, const SamplerEntry& entry);
-
- void SetupStorageTexel(const Tegra::Texture::TICEntry& tic, const StorageTexelEntry& entry);
-
- void SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry);
-
void UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateScissorsState(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthBias(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -260,64 +155,43 @@ private:
void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs);
- void UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
- std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const;
-
- std::size_t CalculateComputeStreamBufferSize() const;
-
- std::size_t CalculateVertexArraysSize() const;
-
- std::size_t CalculateIndexBufferSize() const;
-
- std::size_t CalculateConstBufferSize(const ConstBufferEntry& entry,
- const Tegra::Engines::ConstBufferInfo& buffer) const;
-
- RenderPassParams GetRenderPassParams(Texceptions texceptions) const;
+ Tegra::GPU& gpu;
+ Tegra::MemoryManager& gpu_memory;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
- VkBuffer DefaultBuffer();
-
- Core::System& system;
- Core::Frontend::EmuWindow& render_window;
VKScreenInfo& screen_info;
- const VKDevice& device;
- VKResourceManager& resource_manager;
- VKMemoryManager& memory_manager;
+ const Device& device;
+ MemoryAllocator& memory_allocator;
StateTracker& state_tracker;
VKScheduler& scheduler;
- VKStagingBufferPool staging_pool;
+ StagingBufferPool staging_pool;
VKDescriptorPool descriptor_pool;
VKUpdateDescriptorQueue update_descriptor_queue;
- VKRenderPassCache renderpass_cache;
- QuadArrayPass quad_array_pass;
- QuadIndexedPass quad_indexed_pass;
- Uint8Pass uint8_pass;
+ BlitImageHelper blit_image;
+
+ GraphicsPipelineCacheKey graphics_key;
- VKTextureCache texture_cache;
+ TextureCacheRuntime texture_cache_runtime;
+ TextureCache texture_cache;
+ BufferCacheRuntime buffer_cache_runtime;
+ BufferCache buffer_cache;
VKPipelineCache pipeline_cache;
- VKBufferCache buffer_cache;
- VKSamplerCache sampler_cache;
- VKFenceManager fence_manager;
VKQueryCache query_cache;
+ VKFenceManager fence_manager;
- vk::Buffer default_buffer;
- VKMemoryCommit default_buffer_commit;
vk::Event wfi_event;
VideoCommon::Shader::AsyncShaders async_shaders;
- std::array<View, Maxwell::NumRenderTargets> color_attachments;
- View zeta_attachment;
-
- std::vector<ImageView> sampled_views;
- std::vector<ImageView> image_views;
+ boost::container::static_vector<u32, MAX_IMAGE_VIEWS> image_view_indices;
+ std::array<VideoCommon::ImageViewId, MAX_IMAGE_VIEWS> image_view_ids;
+ boost::container::static_vector<VkSampler, MAX_TEXTURES> sampler_handles;
u32 draw_counter = 0;
-
- // TODO(Rodrigo): Invalidate on image destruction
- std::unordered_map<FramebufferCacheKey, vk::Framebuffer> framebuffer_cache;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp b/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp
deleted file mode 100644
index 80284cf92..000000000
--- a/src/video_core/renderer_vulkan/vk_renderpass_cache.cpp
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <cstring>
-#include <memory>
-#include <vector>
-
-#include "common/cityhash.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/renderer_vulkan/maxwell_to_vk.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-std::size_t RenderPassParams::Hash() const noexcept {
- const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this);
- return static_cast<std::size_t>(hash);
-}
-
-bool RenderPassParams::operator==(const RenderPassParams& rhs) const noexcept {
- return std::memcmp(&rhs, this, sizeof *this) == 0;
-}
-
-VKRenderPassCache::VKRenderPassCache(const VKDevice& device) : device{device} {}
-
-VKRenderPassCache::~VKRenderPassCache() = default;
-
-VkRenderPass VKRenderPassCache::GetRenderPass(const RenderPassParams& params) {
- const auto [pair, is_cache_miss] = cache.try_emplace(params);
- auto& entry = pair->second;
- if (is_cache_miss) {
- entry = CreateRenderPass(params);
- }
- return *entry;
-}
-
-vk::RenderPass VKRenderPassCache::CreateRenderPass(const RenderPassParams& params) const {
- using namespace VideoCore::Surface;
- const std::size_t num_attachments = static_cast<std::size_t>(params.num_color_attachments);
-
- std::vector<VkAttachmentDescription> descriptors;
- descriptors.reserve(num_attachments);
-
- std::vector<VkAttachmentReference> color_references;
- color_references.reserve(num_attachments);
-
- for (std::size_t rt = 0; rt < num_attachments; ++rt) {
- const auto guest_format = static_cast<Tegra::RenderTargetFormat>(params.color_formats[rt]);
- const PixelFormat pixel_format = PixelFormatFromRenderTargetFormat(guest_format);
- const auto format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, pixel_format);
- ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}",
- static_cast<int>(pixel_format));
-
- // TODO(Rodrigo): Add MAY_ALIAS_BIT when it's needed.
- const VkImageLayout color_layout = ((params.texceptions >> rt) & 1) != 0
- ? VK_IMAGE_LAYOUT_GENERAL
- : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
- descriptors.push_back({
- .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT,
- .format = format.format,
- .samples = VK_SAMPLE_COUNT_1_BIT,
- .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
- .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
- .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
- .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
- .initialLayout = color_layout,
- .finalLayout = color_layout,
- });
-
- color_references.push_back({
- .attachment = static_cast<u32>(rt),
- .layout = color_layout,
- });
- }
-
- VkAttachmentReference zeta_attachment_ref;
- const bool has_zeta = params.zeta_format != 0;
- if (has_zeta) {
- const auto guest_format = static_cast<Tegra::DepthFormat>(params.zeta_format);
- const PixelFormat pixel_format = PixelFormatFromDepthFormat(guest_format);
- const auto format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, pixel_format);
- ASSERT_MSG(format.attachable, "Trying to attach a non-attachable format with format={}",
- static_cast<int>(pixel_format));
-
- const VkImageLayout zeta_layout = params.zeta_texception != 0
- ? VK_IMAGE_LAYOUT_GENERAL
- : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
- descriptors.push_back({
- .flags = 0,
- .format = format.format,
- .samples = VK_SAMPLE_COUNT_1_BIT,
- .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
- .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
- .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
- .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE,
- .initialLayout = zeta_layout,
- .finalLayout = zeta_layout,
- });
-
- zeta_attachment_ref = {
- .attachment = static_cast<u32>(num_attachments),
- .layout = zeta_layout,
- };
- }
-
- const VkSubpassDescription subpass_description{
- .flags = 0,
- .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
- .inputAttachmentCount = 0,
- .pInputAttachments = nullptr,
- .colorAttachmentCount = static_cast<u32>(color_references.size()),
- .pColorAttachments = color_references.data(),
- .pResolveAttachments = nullptr,
- .pDepthStencilAttachment = has_zeta ? &zeta_attachment_ref : nullptr,
- .preserveAttachmentCount = 0,
- .pPreserveAttachments = nullptr,
- };
-
- VkAccessFlags access = 0;
- VkPipelineStageFlags stage = 0;
- if (!color_references.empty()) {
- access |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
- stage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
- }
-
- if (has_zeta) {
- access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
- VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
- stage |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
- }
-
- const VkSubpassDependency subpass_dependency{
- .srcSubpass = VK_SUBPASS_EXTERNAL,
- .dstSubpass = 0,
- .srcStageMask = stage,
- .dstStageMask = stage,
- .srcAccessMask = 0,
- .dstAccessMask = access,
- .dependencyFlags = 0,
- };
-
- return device.GetLogical().CreateRenderPass({
- .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .attachmentCount = static_cast<u32>(descriptors.size()),
- .pAttachments = descriptors.data(),
- .subpassCount = 1,
- .pSubpasses = &subpass_description,
- .dependencyCount = 1,
- .pDependencies = &subpass_dependency,
- });
-}
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_renderpass_cache.h b/src/video_core/renderer_vulkan/vk_renderpass_cache.h
deleted file mode 100644
index 8b0fec720..000000000
--- a/src/video_core/renderer_vulkan/vk_renderpass_cache.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <type_traits>
-#include <unordered_map>
-
-#include <boost/container/static_vector.hpp>
-#include <boost/functional/hash.hpp>
-
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-#include "video_core/surface.h"
-
-namespace Vulkan {
-
-class VKDevice;
-
-struct RenderPassParams {
- std::array<u8, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> color_formats;
- u8 num_color_attachments;
- u8 texceptions;
-
- u8 zeta_format;
- u8 zeta_texception;
-
- std::size_t Hash() const noexcept;
-
- bool operator==(const RenderPassParams& rhs) const noexcept;
-
- bool operator!=(const RenderPassParams& rhs) const noexcept {
- return !operator==(rhs);
- }
-};
-static_assert(std::has_unique_object_representations_v<RenderPassParams>);
-static_assert(std::is_trivially_copyable_v<RenderPassParams>);
-static_assert(std::is_trivially_constructible_v<RenderPassParams>);
-
-} // namespace Vulkan
-
-namespace std {
-
-template <>
-struct hash<Vulkan::RenderPassParams> {
- std::size_t operator()(const Vulkan::RenderPassParams& k) const noexcept {
- return k.Hash();
- }
-};
-
-} // namespace std
-
-namespace Vulkan {
-
-class VKRenderPassCache final {
-public:
- explicit VKRenderPassCache(const VKDevice& device);
- ~VKRenderPassCache();
-
- VkRenderPass GetRenderPass(const RenderPassParams& params);
-
-private:
- vk::RenderPass CreateRenderPass(const RenderPassParams& params) const;
-
- const VKDevice& device;
- std::unordered_map<RenderPassParams, vk::RenderPass> cache;
-};
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
deleted file mode 100644
index f19330a36..000000000
--- a/src/video_core/renderer_vulkan/vk_resource_manager.cpp
+++ /dev/null
@@ -1,311 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <optional>
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-namespace {
-
-// TODO(Rodrigo): Fine tune these numbers.
-constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 0x1000;
-constexpr std::size_t FENCES_GROW_STEP = 0x40;
-
-constexpr VkFenceCreateInfo BuildFenceCreateInfo() {
- return {
- .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- };
-}
-
-} // Anonymous namespace
-
-class CommandBufferPool final : public VKFencedPool {
-public:
- explicit CommandBufferPool(const VKDevice& device)
- : VKFencedPool(COMMAND_BUFFER_POOL_SIZE), device{device} {}
-
- void Allocate(std::size_t begin, std::size_t end) override {
- // Command buffers are going to be commited, recorded, executed every single usage cycle.
- // They are also going to be reseted when commited.
- Pool& pool = pools.emplace_back();
- pool.handle = device.GetLogical().CreateCommandPool({
- .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
- .pNext = nullptr,
- .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
- VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
- .queueFamilyIndex = device.GetGraphicsFamily(),
- });
- pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE);
- }
-
- VkCommandBuffer Commit(VKFence& fence) {
- const std::size_t index = CommitResource(fence);
- const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
- const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
- return pools[pool_index].cmdbufs[sub_index];
- }
-
-private:
- struct Pool {
- vk::CommandPool handle;
- vk::CommandBuffers cmdbufs;
- };
-
- const VKDevice& device;
- std::vector<Pool> pools;
-};
-
-VKResource::VKResource() = default;
-
-VKResource::~VKResource() = default;
-
-VKFence::VKFence(const VKDevice& device)
- : device{device}, handle{device.GetLogical().CreateFence(BuildFenceCreateInfo())} {}
-
-VKFence::~VKFence() = default;
-
-void VKFence::Wait() {
- switch (const VkResult result = handle.Wait()) {
- case VK_SUCCESS:
- return;
- case VK_ERROR_DEVICE_LOST:
- device.ReportLoss();
- [[fallthrough]];
- default:
- throw vk::Exception(result);
- }
-}
-
-void VKFence::Release() {
- ASSERT(is_owned);
- is_owned = false;
-}
-
-void VKFence::Commit() {
- is_owned = true;
- is_used = true;
-}
-
-bool VKFence::Tick(bool gpu_wait, bool owner_wait) {
- if (!is_used) {
- // If a fence is not used it's always free.
- return true;
- }
- if (is_owned && !owner_wait) {
- // The fence is still being owned (Release has not been called) and ownership wait has
- // not been asked.
- return false;
- }
-
- if (gpu_wait) {
- // Wait for the fence if it has been requested.
- (void)handle.Wait();
- } else {
- if (handle.GetStatus() != VK_SUCCESS) {
- // Vulkan fence is not ready, not much it can do here
- return false;
- }
- }
-
- // Broadcast resources their free state.
- for (auto* resource : protected_resources) {
- resource->OnFenceRemoval(this);
- }
- protected_resources.clear();
-
- // Prepare fence for reusage.
- handle.Reset();
- is_used = false;
- return true;
-}
-
-void VKFence::Protect(VKResource* resource) {
- protected_resources.push_back(resource);
-}
-
-void VKFence::Unprotect(VKResource* resource) {
- const auto it = std::find(protected_resources.begin(), protected_resources.end(), resource);
- ASSERT(it != protected_resources.end());
-
- resource->OnFenceRemoval(this);
- protected_resources.erase(it);
-}
-
-void VKFence::RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept {
- std::replace(std::begin(protected_resources), std::end(protected_resources), old_resource,
- new_resource);
-}
-
-VKFenceWatch::VKFenceWatch() = default;
-
-VKFenceWatch::VKFenceWatch(VKFence& initial_fence) {
- Watch(initial_fence);
-}
-
-VKFenceWatch::VKFenceWatch(VKFenceWatch&& rhs) noexcept {
- fence = std::exchange(rhs.fence, nullptr);
- if (fence) {
- fence->RedirectProtection(&rhs, this);
- }
-}
-
-VKFenceWatch& VKFenceWatch::operator=(VKFenceWatch&& rhs) noexcept {
- fence = std::exchange(rhs.fence, nullptr);
- if (fence) {
- fence->RedirectProtection(&rhs, this);
- }
- return *this;
-}
-
-VKFenceWatch::~VKFenceWatch() {
- if (fence) {
- fence->Unprotect(this);
- }
-}
-
-void VKFenceWatch::Wait() {
- if (fence == nullptr) {
- return;
- }
- fence->Wait();
- fence->Unprotect(this);
-}
-
-void VKFenceWatch::Watch(VKFence& new_fence) {
- Wait();
- fence = &new_fence;
- fence->Protect(this);
-}
-
-bool VKFenceWatch::TryWatch(VKFence& new_fence) {
- if (fence) {
- return false;
- }
- fence = &new_fence;
- fence->Protect(this);
- return true;
-}
-
-void VKFenceWatch::OnFenceRemoval(VKFence* signaling_fence) {
- ASSERT_MSG(signaling_fence == fence, "Removing the wrong fence");
- fence = nullptr;
-}
-
-VKFencedPool::VKFencedPool(std::size_t grow_step) : grow_step{grow_step} {}
-
-VKFencedPool::~VKFencedPool() = default;
-
-std::size_t VKFencedPool::CommitResource(VKFence& fence) {
- const auto Search = [&](std::size_t begin, std::size_t end) -> std::optional<std::size_t> {
- for (std::size_t iterator = begin; iterator < end; ++iterator) {
- if (watches[iterator]->TryWatch(fence)) {
- // The resource is now being watched, a free resource was successfully found.
- return iterator;
- }
- }
- return {};
- };
- // Try to find a free resource from the hinted position to the end.
- auto found = Search(free_iterator, watches.size());
- if (!found) {
- // Search from beginning to the hinted position.
- found = Search(0, free_iterator);
- if (!found) {
- // Both searches failed, the pool is full; handle it.
- const std::size_t free_resource = ManageOverflow();
-
- // Watch will wait for the resource to be free.
- watches[free_resource]->Watch(fence);
- found = free_resource;
- }
- }
- // Free iterator is hinted to the resource after the one that's been commited.
- free_iterator = (*found + 1) % watches.size();
- return *found;
-}
-
-std::size_t VKFencedPool::ManageOverflow() {
- const std::size_t old_capacity = watches.size();
- Grow();
-
- // The last entry is guaranted to be free, since it's the first element of the freshly
- // allocated resources.
- return old_capacity;
-}
-
-void VKFencedPool::Grow() {
- const std::size_t old_capacity = watches.size();
- watches.resize(old_capacity + grow_step);
- std::generate(watches.begin() + old_capacity, watches.end(),
- []() { return std::make_unique<VKFenceWatch>(); });
- Allocate(old_capacity, old_capacity + grow_step);
-}
-
-VKResourceManager::VKResourceManager(const VKDevice& device) : device{device} {
- GrowFences(FENCES_GROW_STEP);
- command_buffer_pool = std::make_unique<CommandBufferPool>(device);
-}
-
-VKResourceManager::~VKResourceManager() = default;
-
-VKFence& VKResourceManager::CommitFence() {
- const auto StepFences = [&](bool gpu_wait, bool owner_wait) -> VKFence* {
- const auto Tick = [=](auto& fence) { return fence->Tick(gpu_wait, owner_wait); };
- const auto hinted = fences.begin() + fences_iterator;
-
- auto it = std::find_if(hinted, fences.end(), Tick);
- if (it == fences.end()) {
- it = std::find_if(fences.begin(), hinted, Tick);
- if (it == hinted) {
- return nullptr;
- }
- }
- fences_iterator = std::distance(fences.begin(), it) + 1;
- if (fences_iterator >= fences.size())
- fences_iterator = 0;
-
- auto& fence = *it;
- fence->Commit();
- return fence.get();
- };
-
- VKFence* found_fence = StepFences(false, false);
- if (!found_fence) {
- // Try again, this time waiting.
- found_fence = StepFences(true, false);
-
- if (!found_fence) {
- // Allocate new fences and try again.
- LOG_INFO(Render_Vulkan, "Allocating new fences {} -> {}", fences.size(),
- fences.size() + FENCES_GROW_STEP);
-
- GrowFences(FENCES_GROW_STEP);
- found_fence = StepFences(true, false);
- ASSERT(found_fence != nullptr);
- }
- }
- return *found_fence;
-}
-
-VkCommandBuffer VKResourceManager::CommitCommandBuffer(VKFence& fence) {
- return command_buffer_pool->Commit(fence);
-}
-
-void VKResourceManager::GrowFences(std::size_t new_fences_count) {
- const std::size_t previous_size = fences.size();
- fences.resize(previous_size + new_fences_count);
-
- std::generate(fences.begin() + previous_size, fences.end(),
- [this] { return std::make_unique<VKFence>(device); });
-}
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.h b/src/video_core/renderer_vulkan/vk_resource_manager.h
deleted file mode 100644
index f683d2276..000000000
--- a/src/video_core/renderer_vulkan/vk_resource_manager.h
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstddef>
-#include <memory>
-#include <vector>
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-class VKDevice;
-class VKFence;
-class VKResourceManager;
-
-class CommandBufferPool;
-
-/// Interface for a Vulkan resource
-class VKResource {
-public:
- explicit VKResource();
- virtual ~VKResource();
-
- /**
- * Signals the object that an owning fence has been signaled.
- * @param signaling_fence Fence that signals its usage end.
- */
- virtual void OnFenceRemoval(VKFence* signaling_fence) = 0;
-};
-
-/**
- * Fences take ownership of objects, protecting them from GPU-side or driver-side concurrent access.
- * They must be commited from the resource manager. Their usage flow is: commit the fence from the
- * resource manager, protect resources with it and use them, send the fence to an execution queue
- * and Wait for it if needed and then call Release. Used resources will automatically be signaled
- * when they are free to be reused.
- * @brief Protects resources for concurrent usage and signals its release.
- */
-class VKFence {
- friend class VKResourceManager;
-
-public:
- explicit VKFence(const VKDevice& device);
- ~VKFence();
-
- /**
- * Waits for the fence to be signaled.
- * @warning You must have ownership of the fence and it has to be previously sent to a queue to
- * call this function.
- */
- void Wait();
-
- /**
- * Releases ownership of the fence. Pass after it has been sent to an execution queue.
- * Unmanaged usage of the fence after the call will result in undefined behavior because it may
- * be being used for something else.
- */
- void Release();
-
- /// Protects a resource with this fence.
- void Protect(VKResource* resource);
-
- /// Removes protection for a resource.
- void Unprotect(VKResource* resource);
-
- /// Redirects one protected resource to a new address.
- void RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept;
-
- /// Retreives the fence.
- operator VkFence() const {
- return *handle;
- }
-
-private:
- /// Take ownership of the fence.
- void Commit();
-
- /**
- * Updates the fence status.
- * @warning Waiting for the owner might soft lock the execution.
- * @param gpu_wait Wait for the fence to be signaled by the driver.
- * @param owner_wait Wait for the owner to signal its freedom.
- * @returns True if the fence is free. Waiting for gpu and owner will always return true.
- */
- bool Tick(bool gpu_wait, bool owner_wait);
-
- const VKDevice& device; ///< Device handler
- vk::Fence handle; ///< Vulkan fence
- std::vector<VKResource*> protected_resources; ///< List of resources protected by this fence
- bool is_owned = false; ///< The fence has been commited but not released yet.
- bool is_used = false; ///< The fence has been commited but it has not been checked to be free.
-};
-
-/**
- * A fence watch is used to keep track of the usage of a fence and protect a resource or set of
- * resources without having to inherit VKResource from their handlers.
- */
-class VKFenceWatch final : public VKResource {
-public:
- explicit VKFenceWatch();
- VKFenceWatch(VKFence& initial_fence);
- VKFenceWatch(VKFenceWatch&&) noexcept;
- VKFenceWatch(const VKFenceWatch&) = delete;
- ~VKFenceWatch() override;
-
- VKFenceWatch& operator=(VKFenceWatch&&) noexcept;
-
- /// Waits for the fence to be released.
- void Wait();
-
- /**
- * Waits for a previous fence and watches a new one.
- * @param new_fence New fence to wait to.
- */
- void Watch(VKFence& new_fence);
-
- /**
- * Checks if it's currently being watched and starts watching it if it's available.
- * @returns True if a watch has started, false if it's being watched.
- */
- bool TryWatch(VKFence& new_fence);
-
- void OnFenceRemoval(VKFence* signaling_fence) override;
-
- /**
- * Do not use it paired with Watch. Use TryWatch instead.
- * Returns true when the watch is free.
- */
- bool IsUsed() const {
- return fence != nullptr;
- }
-
-private:
- VKFence* fence{}; ///< Fence watching this resource. nullptr when the watch is free.
-};
-
-/**
- * Handles a pool of resources protected by fences. Manages resource overflow allocating more
- * resources.
- */
-class VKFencedPool {
-public:
- explicit VKFencedPool(std::size_t grow_step);
- virtual ~VKFencedPool();
-
-protected:
- /**
- * Commits a free resource and protects it with a fence. It may allocate new resources.
- * @param fence Fence that protects the commited resource.
- * @returns Index of the resource commited.
- */
- std::size_t CommitResource(VKFence& fence);
-
- /// Called when a chunk of resources have to be allocated.
- virtual void Allocate(std::size_t begin, std::size_t end) = 0;
-
-private:
- /// Manages pool overflow allocating new resources.
- std::size_t ManageOverflow();
-
- /// Allocates a new page of resources.
- void Grow();
-
- std::size_t grow_step = 0; ///< Number of new resources created after an overflow
- std::size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found
- std::vector<std::unique_ptr<VKFenceWatch>> watches; ///< Set of watched resources
-};
-
-/**
- * The resource manager handles all resources that can be protected with a fence avoiding
- * driver-side or GPU-side concurrent usage. Usage is documented in VKFence.
- */
-class VKResourceManager final {
-public:
- explicit VKResourceManager(const VKDevice& device);
- ~VKResourceManager();
-
- /// Commits a fence. It has to be sent to a queue and released.
- VKFence& CommitFence();
-
- /// Commits an unused command buffer and protects it with a fence.
- VkCommandBuffer CommitCommandBuffer(VKFence& fence);
-
-private:
- /// Allocates new fences.
- void GrowFences(std::size_t new_fences_count);
-
- const VKDevice& device; ///< Device handler.
- std::size_t fences_iterator = 0; ///< Index where a free fence is likely to be found.
- std::vector<std::unique_ptr<VKFence>> fences; ///< Pool of fences.
- std::unique_ptr<CommandBufferPool> command_buffer_pool; ///< Pool of command buffers.
-};
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
new file mode 100644
index 000000000..a8bf7bda8
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
@@ -0,0 +1,63 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <optional>
+
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+
+namespace Vulkan {
+
+ResourcePool::ResourcePool(MasterSemaphore& master_semaphore_, size_t grow_step_)
+ : master_semaphore{master_semaphore_}, grow_step{grow_step_} {}
+
+ResourcePool::~ResourcePool() = default;
+
+size_t ResourcePool::CommitResource() {
+ // Refresh semaphore to query updated results
+ master_semaphore.Refresh();
+ const u64 gpu_tick = master_semaphore.KnownGpuTick();
+ const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional<size_t> {
+ for (size_t iterator = begin; iterator < end; ++iterator) {
+ if (gpu_tick >= ticks[iterator]) {
+ ticks[iterator] = master_semaphore.CurrentTick();
+ return iterator;
+ }
+ }
+ return std::nullopt;
+ };
+ // Try to find a free resource from the hinted position to the end.
+ std::optional<size_t> found = search(hint_iterator, ticks.size());
+ if (!found) {
+ // Search from beginning to the hinted position.
+ found = search(0, hint_iterator);
+ if (!found) {
+ // Both searches failed, the pool is full; handle it.
+ const size_t free_resource = ManageOverflow();
+
+ ticks[free_resource] = master_semaphore.CurrentTick();
+ found = free_resource;
+ }
+ }
+ // Free iterator is hinted to the resource after the one that's been commited.
+ hint_iterator = (*found + 1) % ticks.size();
+ return *found;
+}
+
+size_t ResourcePool::ManageOverflow() {
+ const size_t old_capacity = ticks.size();
+ Grow();
+
+ // The last entry is guaranted to be free, since it's the first element of the freshly
+ // allocated resources.
+ return old_capacity;
+}
+
+void ResourcePool::Grow() {
+ const size_t old_capacity = ticks.size();
+ ticks.resize(old_capacity + grow_step);
+ Allocate(old_capacity, old_capacity + grow_step);
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h
new file mode 100644
index 000000000..9d0bb3b4d
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.h
@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Vulkan {
+
+class MasterSemaphore;
+
+/**
+ * Handles a pool of resources protected by fences. Manages resource overflow allocating more
+ * resources.
+ */
+class ResourcePool {
+public:
+ explicit ResourcePool(MasterSemaphore& master_semaphore, size_t grow_step);
+ virtual ~ResourcePool();
+
+protected:
+ size_t CommitResource();
+
+ /// Called when a chunk of resources have to be allocated.
+ virtual void Allocate(size_t begin, size_t end) = 0;
+
+private:
+ /// Manages pool overflow allocating new resources.
+ size_t ManageOverflow();
+
+ /// Allocates a new page of resources.
+ void Grow();
+
+ MasterSemaphore& master_semaphore;
+ size_t grow_step = 0; ///< Number of new resources created after an overflow
+ size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found
+ std::vector<u64> ticks; ///< Ticks for each resource
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
deleted file mode 100644
index b068888f9..000000000
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <unordered_map>
-
-#include "video_core/renderer_vulkan/maxwell_to_vk.h"
-#include "video_core/renderer_vulkan/vk_sampler_cache.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-#include "video_core/textures/texture.h"
-
-using Tegra::Texture::TextureMipmapFilter;
-
-namespace Vulkan {
-
-namespace {
-
-VkBorderColor ConvertBorderColor(std::array<float, 4> color) {
- // TODO(Rodrigo): Manage integer border colors
- if (color == std::array<float, 4>{0, 0, 0, 0}) {
- return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
- } else if (color == std::array<float, 4>{0, 0, 0, 1}) {
- return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
- } else if (color == std::array<float, 4>{1, 1, 1, 1}) {
- return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
- }
- if (color[0] + color[1] + color[2] > 1.35f) {
- // If color elements are brighter than roughly 0.5 average, use white border
- return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
- } else if (color[3] > 0.5f) {
- return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
- } else {
- return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
- }
-}
-
-} // Anonymous namespace
-
-VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {}
-
-VKSamplerCache::~VKSamplerCache() = default;
-
-vk::Sampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) const {
- const bool arbitrary_borders = device.IsExtCustomBorderColorSupported();
- const std::array color = tsc.GetBorderColor();
-
- VkSamplerCustomBorderColorCreateInfoEXT border{
- .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT,
- .pNext = nullptr,
- .customBorderColor = {},
- .format = VK_FORMAT_UNDEFINED,
- };
- std::memcpy(&border.customBorderColor, color.data(), sizeof(color));
-
- return device.GetLogical().CreateSampler({
- .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
- .pNext = arbitrary_borders ? &border : nullptr,
- .flags = 0,
- .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter),
- .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter),
- .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
- .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
- .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
- .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
- .mipLodBias = tsc.GetLodBias(),
- .anisotropyEnable =
- static_cast<VkBool32>(tsc.GetMaxAnisotropy() > 1.0f ? VK_TRUE : VK_FALSE),
- .maxAnisotropy = tsc.GetMaxAnisotropy(),
- .compareEnable = tsc.depth_compare_enabled,
- .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
- .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.GetMinLod(),
- .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.GetMaxLod(),
- .borderColor =
- arbitrary_borders ? VK_BORDER_COLOR_INT_CUSTOM_EXT : ConvertBorderColor(color),
- .unnormalizedCoordinates = VK_FALSE,
- });
-}
-
-VkSampler VKSamplerCache::ToSamplerType(const vk::Sampler& sampler) const {
- return *sampler;
-}
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.h b/src/video_core/renderer_vulkan/vk_sampler_cache.h
deleted file mode 100644
index a33d1c0ee..000000000
--- a/src/video_core/renderer_vulkan/vk_sampler_cache.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "video_core/renderer_vulkan/wrapper.h"
-#include "video_core/sampler_cache.h"
-#include "video_core/textures/texture.h"
-
-namespace Vulkan {
-
-class VKDevice;
-
-class VKSamplerCache final : public VideoCommon::SamplerCache<VkSampler, vk::Sampler> {
-public:
- explicit VKSamplerCache(const VKDevice& device);
- ~VKSamplerCache();
-
-protected:
- vk::Sampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const override;
-
- VkSampler ToSamplerType(const vk::Sampler& sampler) const override;
-
-private:
- const VKDevice& device;
-};
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index dbbd0961a..f35c120b0 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -10,12 +10,14 @@
#include "common/microprofile.h"
#include "common/thread.h"
-#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_command_pool.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
#include "video_core/renderer_vulkan/vk_query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
@@ -35,10 +37,10 @@ void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
last = nullptr;
}
-VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_manager,
- StateTracker& state_tracker)
- : device{device}, resource_manager{resource_manager}, state_tracker{state_tracker},
- next_fence{&resource_manager.CommitFence()} {
+VKScheduler::VKScheduler(const Device& device_, StateTracker& state_tracker_)
+ : device{device_}, state_tracker{state_tracker_},
+ master_semaphore{std::make_unique<MasterSemaphore>(device)},
+ command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} {
AcquireNewChunk();
AllocateNewContext();
worker_thread = std::thread(&VKScheduler::WorkerThread, this);
@@ -50,20 +52,15 @@ VKScheduler::~VKScheduler() {
worker_thread.join();
}
-void VKScheduler::Flush(bool release_fence, VkSemaphore semaphore) {
+void VKScheduler::Flush(VkSemaphore semaphore) {
SubmitExecution(semaphore);
- if (release_fence) {
- current_fence->Release();
- }
AllocateNewContext();
}
-void VKScheduler::Finish(bool release_fence, VkSemaphore semaphore) {
+void VKScheduler::Finish(VkSemaphore semaphore) {
+ const u64 presubmit_tick = CurrentTick();
SubmitExecution(semaphore);
- current_fence->Wait();
- if (release_fence) {
- current_fence->Release();
- }
+ Wait(presubmit_tick);
AllocateNewContext();
}
@@ -88,38 +85,39 @@ void VKScheduler::DispatchWork() {
AcquireNewChunk();
}
-void VKScheduler::RequestRenderpass(VkRenderPass renderpass, VkFramebuffer framebuffer,
- VkExtent2D render_area) {
- if (renderpass == state.renderpass && framebuffer == state.framebuffer &&
+void VKScheduler::RequestRenderpass(const Framebuffer* framebuffer) {
+ const VkRenderPass renderpass = framebuffer->RenderPass();
+ const VkFramebuffer framebuffer_handle = framebuffer->Handle();
+ const VkExtent2D render_area = framebuffer->RenderArea();
+ if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer &&
render_area.width == state.render_area.width &&
render_area.height == state.render_area.height) {
return;
}
- const bool end_renderpass = state.renderpass != nullptr;
+ EndRenderPass();
state.renderpass = renderpass;
- state.framebuffer = framebuffer;
+ state.framebuffer = framebuffer_handle;
state.render_area = render_area;
- const VkRenderPassBeginInfo renderpass_bi{
- .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
- .pNext = nullptr,
- .renderPass = renderpass,
- .framebuffer = framebuffer,
- .renderArea =
- {
- .offset = {.x = 0, .y = 0},
- .extent = render_area,
- },
- .clearValueCount = 0,
- .pClearValues = nullptr,
- };
-
- Record([renderpass_bi, end_renderpass](vk::CommandBuffer cmdbuf) {
- if (end_renderpass) {
- cmdbuf.EndRenderPass();
- }
+ Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) {
+ const VkRenderPassBeginInfo renderpass_bi{
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .pNext = nullptr,
+ .renderPass = renderpass,
+ .framebuffer = framebuffer_handle,
+ .renderArea =
+ {
+ .offset = {.x = 0, .y = 0},
+ .extent = render_area,
+ },
+ .clearValueCount = 0,
+ .pClearValues = nullptr,
+ };
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
});
+ num_renderpass_images = framebuffer->NumImages();
+ renderpass_images = framebuffer->Images();
+ renderpass_image_ranges = framebuffer->ImageRanges();
}
void VKScheduler::RequestOutsideRenderPassOperationContext() {
@@ -160,18 +158,38 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) {
current_cmdbuf.End();
+ const VkSemaphore timeline_semaphore = master_semaphore->Handle();
+ const u32 num_signal_semaphores = semaphore ? 2U : 1U;
+
+ const u64 signal_value = master_semaphore->CurrentTick();
+ const u64 wait_value = signal_value - 1;
+ const VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+
+ master_semaphore->NextTick();
+
+ const std::array signal_values{signal_value, u64(0)};
+ const std::array signal_semaphores{timeline_semaphore, semaphore};
+
+ const VkTimelineSemaphoreSubmitInfoKHR timeline_si{
+ .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR,
+ .pNext = nullptr,
+ .waitSemaphoreValueCount = 1,
+ .pWaitSemaphoreValues = &wait_value,
+ .signalSemaphoreValueCount = num_signal_semaphores,
+ .pSignalSemaphoreValues = signal_values.data(),
+ };
const VkSubmitInfo submit_info{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
- .pNext = nullptr,
- .waitSemaphoreCount = 0,
- .pWaitSemaphores = nullptr,
- .pWaitDstStageMask = nullptr,
+ .pNext = &timeline_si,
+ .waitSemaphoreCount = 1,
+ .pWaitSemaphores = &timeline_semaphore,
+ .pWaitDstStageMask = &wait_stage_mask,
.commandBufferCount = 1,
.pCommandBuffers = current_cmdbuf.address(),
- .signalSemaphoreCount = semaphore ? 1U : 0U,
- .pSignalSemaphores = &semaphore,
+ .signalSemaphoreCount = num_signal_semaphores,
+ .pSignalSemaphores = signal_semaphores.data(),
};
- switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info, *current_fence)) {
+ switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) {
case VK_SUCCESS:
break;
case VK_ERROR_DEVICE_LOST:
@@ -183,14 +201,9 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) {
}
void VKScheduler::AllocateNewContext() {
- ++ticks;
-
std::unique_lock lock{mutex};
- current_fence = next_fence;
- next_fence = &resource_manager.CommitFence();
- current_cmdbuf = vk::CommandBuffer(resource_manager.CommitCommandBuffer(*current_fence),
- device.GetDispatchLoader());
+ current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
current_cmdbuf.Begin({
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.pNext = nullptr,
@@ -218,8 +231,37 @@ void VKScheduler::EndRenderPass() {
if (!state.renderpass) {
return;
}
+ Record([num_images = num_renderpass_images, images = renderpass_images,
+ ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
+ std::array<VkImageMemoryBarrier, 9> barriers;
+ for (size_t i = 0; i < num_images; ++i) {
+ barriers[i] = VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = images[i],
+ .subresourceRange = ranges[i],
+ };
+ }
+ cmdbuf.EndRenderPass();
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
+ VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, nullptr,
+ vk::Span(barriers.data(), num_images));
+ });
state.renderpass = nullptr;
- Record([](vk::CommandBuffer cmdbuf) { cmdbuf.EndRenderPass(); });
+ num_renderpass_images = 0;
}
void VKScheduler::AcquireNewChunk() {
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 970a65566..3ce48e9d2 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -6,52 +6,37 @@
#include <atomic>
#include <condition_variable>
+#include <cstddef>
#include <memory>
#include <stack>
#include <thread>
#include <utility>
+#include "common/alignment.h"
#include "common/common_types.h"
#include "common/threadsafe_queue.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
+class CommandPool;
+class Device;
+class Framebuffer;
class StateTracker;
-class VKDevice;
-class VKFence;
class VKQueryCache;
-class VKResourceManager;
-
-class VKFenceView {
-public:
- VKFenceView() = default;
- VKFenceView(VKFence* const& fence) : fence{fence} {}
-
- VKFence* operator->() const noexcept {
- return fence;
- }
-
- operator VKFence&() const noexcept {
- return *fence;
- }
-
-private:
- VKFence* const& fence;
-};
/// The scheduler abstracts command buffer and fence management with an interface that's able to do
/// OpenGL-like operations on Vulkan command buffers.
class VKScheduler {
public:
- explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager,
- StateTracker& state_tracker);
+ explicit VKScheduler(const Device& device, StateTracker& state_tracker);
~VKScheduler();
/// Sends the current execution context to the GPU.
- void Flush(bool release_fence = true, VkSemaphore semaphore = nullptr);
+ void Flush(VkSemaphore semaphore = nullptr);
/// Sends the current execution context to the GPU and waits for it to complete.
- void Finish(bool release_fence = true, VkSemaphore semaphore = nullptr);
+ void Finish(VkSemaphore semaphore = nullptr);
/// Waits for the worker thread to finish executing everything. After this function returns it's
/// safe to touch worker resources.
@@ -61,8 +46,7 @@ public:
void DispatchWork();
/// Requests to begin a renderpass.
- void RequestRenderpass(VkRenderPass renderpass, VkFramebuffer framebuffer,
- VkExtent2D render_area);
+ void RequestRenderpass(const Framebuffer* framebuffer);
/// Requests the current executino context to be able to execute operations only allowed outside
/// of a renderpass.
@@ -71,6 +55,9 @@ public:
/// Binds a pipeline to the current execution context.
void BindGraphicsPipeline(VkPipeline pipeline);
+ /// Invalidates current command buffer state except for render passes
+ void InvalidateState();
+
/// Assigns the query cache.
void SetQueryCache(VKQueryCache& query_cache_) {
query_cache = &query_cache_;
@@ -86,14 +73,24 @@ public:
(void)chunk->Record(command);
}
- /// Gets a reference to the current fence.
- VKFenceView GetFence() const {
- return current_fence;
+ /// Returns the current command buffer tick.
+ [[nodiscard]] u64 CurrentTick() const noexcept {
+ return master_semaphore->CurrentTick();
}
- /// Returns the current command buffer tick.
- u64 Ticks() const {
- return ticks;
+ /// Returns true when a tick has been triggered by the GPU.
+ [[nodiscard]] bool IsFree(u64 tick) const noexcept {
+ return master_semaphore->IsFree(tick);
+ }
+
+ /// Waits for the given tick to trigger on the GPU.
+ void Wait(u64 tick) {
+ master_semaphore->Wait(tick);
+ }
+
+ /// Returns the master timeline semaphore.
+ [[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept {
+ return *master_semaphore;
}
private:
@@ -118,7 +115,7 @@ private:
template <typename T>
class TypedCommand final : public Command {
public:
- explicit TypedCommand(T&& command) : command{std::move(command)} {}
+ explicit TypedCommand(T&& command_) : command{std::move(command_)} {}
~TypedCommand() override = default;
TypedCommand(TypedCommand&&) = delete;
@@ -141,12 +138,11 @@ private:
using FuncType = TypedCommand<T>;
static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large");
+ command_offset = Common::AlignUp(command_offset, alignof(FuncType));
if (command_offset > sizeof(data) - sizeof(FuncType)) {
return false;
}
-
- Command* current_last = last;
-
+ Command* const current_last = last;
last = new (data.data() + command_offset) FuncType(std::move(command));
if (current_last) {
@@ -154,7 +150,6 @@ private:
} else {
first = last;
}
-
command_offset += sizeof(FuncType);
return true;
}
@@ -167,8 +162,15 @@ private:
Command* first = nullptr;
Command* last = nullptr;
- std::size_t command_offset = 0;
- std::array<u8, 0x8000> data{};
+ size_t command_offset = 0;
+ alignas(std::max_align_t) std::array<u8, 0x8000> data{};
+ };
+
+ struct State {
+ VkRenderPass renderpass = nullptr;
+ VkFramebuffer framebuffer = nullptr;
+ VkExtent2D render_area = {0, 0};
+ VkPipeline graphics_pipeline = nullptr;
};
void WorkerThread();
@@ -177,39 +179,35 @@ private:
void AllocateNewContext();
- void InvalidateState();
-
void EndPendingOperations();
void EndRenderPass();
void AcquireNewChunk();
- const VKDevice& device;
- VKResourceManager& resource_manager;
+ const Device& device;
StateTracker& state_tracker;
+ std::unique_ptr<MasterSemaphore> master_semaphore;
+ std::unique_ptr<CommandPool> command_pool;
+
VKQueryCache* query_cache = nullptr;
vk::CommandBuffer current_cmdbuf;
- VKFence* current_fence = nullptr;
- VKFence* next_fence = nullptr;
-
- struct State {
- VkRenderPass renderpass = nullptr;
- VkFramebuffer framebuffer = nullptr;
- VkExtent2D render_area = {0, 0};
- VkPipeline graphics_pipeline = nullptr;
- } state;
std::unique_ptr<CommandChunk> chunk;
std::thread worker_thread;
+ State state;
+
+ u32 num_renderpass_images = 0;
+ std::array<VkImage, 9> renderpass_images{};
+ std::array<VkImageSubresourceRange, 9> renderpass_image_ranges{};
+
Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_queue;
Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve;
std::mutex mutex;
std::condition_variable cv;
- std::atomic<u64> ticks = 0;
bool quit = false;
};
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index cd7d7a4e4..40e2e0d38 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -22,11 +22,11 @@
#include "video_core/engines/shader_bytecode.h"
#include "video_core/engines/shader_header.h"
#include "video_core/engines/shader_type.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_shader_decompiler.h"
#include "video_core/shader/node.h"
#include "video_core/shader/shader_ir.h"
#include "video_core/shader/transform_feedback.h"
+#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
@@ -55,8 +55,8 @@ enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat };
class Expression final {
public:
- Expression(Id id, Type type) : id{id}, type{type} {
- ASSERT(type != Type::Void);
+ Expression(Id id_, Type type_) : id{id_}, type{type_} {
+ ASSERT(type_ != Type::Void);
}
Expression() : type{Type::Void} {}
@@ -102,7 +102,7 @@ struct GenericVaryingDescription {
bool is_scalar = false;
};
-spv::Dim GetSamplerDim(const Sampler& sampler) {
+spv::Dim GetSamplerDim(const SamplerEntry& sampler) {
ASSERT(!sampler.is_buffer);
switch (sampler.type) {
case Tegra::Shader::TextureType::Texture1D:
@@ -114,12 +114,12 @@ spv::Dim GetSamplerDim(const Sampler& sampler) {
case Tegra::Shader::TextureType::TextureCube:
return spv::Dim::Cube;
default:
- UNIMPLEMENTED_MSG("Unimplemented sampler type={}", static_cast<int>(sampler.type));
+ UNIMPLEMENTED_MSG("Unimplemented sampler type={}", sampler.type);
return spv::Dim::Dim2D;
}
}
-std::pair<spv::Dim, bool> GetImageDim(const Image& image) {
+std::pair<spv::Dim, bool> GetImageDim(const ImageEntry& image) {
switch (image.type) {
case Tegra::Shader::ImageType::Texture1D:
return {spv::Dim::Dim1D, false};
@@ -134,7 +134,7 @@ std::pair<spv::Dim, bool> GetImageDim(const Image& image) {
case Tegra::Shader::ImageType::Texture3D:
return {spv::Dim::Dim3D, false};
default:
- UNIMPLEMENTED_MSG("Unimplemented image type={}", static_cast<int>(image.type));
+ UNIMPLEMENTED_MSG("Unimplemented image type={}", image.type);
return {spv::Dim::Dim2D, false};
}
}
@@ -274,12 +274,12 @@ bool IsPrecise(Operation operand) {
class SPIRVDecompiler final : public Sirit::Module {
public:
- explicit SPIRVDecompiler(const VKDevice& device, const ShaderIR& ir, ShaderType stage,
- const Registry& registry, const Specialization& specialization)
- : Module(0x00010300), device{device}, ir{ir}, stage{stage}, header{ir.GetHeader()},
- registry{registry}, specialization{specialization} {
- if (stage != ShaderType::Compute) {
- transform_feedback = BuildTransformFeedback(registry.GetGraphicsInfo());
+ explicit SPIRVDecompiler(const Device& device_, const ShaderIR& ir_, ShaderType stage_,
+ const Registry& registry_, const Specialization& specialization_)
+ : Module(0x00010300), device{device_}, ir{ir_}, stage{stage_}, header{ir_.GetHeader()},
+ registry{registry_}, specialization{specialization_} {
+ if (stage_ != ShaderType::Compute) {
+ transform_feedback = BuildTransformFeedback(registry_.GetGraphicsInfo());
}
AddCapability(spv::Capability::Shader);
@@ -293,6 +293,7 @@ public:
AddCapability(spv::Capability::DrawParameters);
AddCapability(spv::Capability::SubgroupBallotKHR);
AddCapability(spv::Capability::SubgroupVoteKHR);
+ AddExtension("SPV_KHR_16bit_storage");
AddExtension("SPV_KHR_shader_ballot");
AddExtension("SPV_KHR_subgroup_vote");
AddExtension("SPV_KHR_storage_buffer_storage_class");
@@ -307,7 +308,6 @@ public:
"supported on this device");
}
}
-
if (ir.UsesLayer() || ir.UsesViewportIndex()) {
if (ir.UsesViewportIndex()) {
AddCapability(spv::Capability::MultiViewport);
@@ -317,15 +317,13 @@ public:
AddCapability(spv::Capability::ShaderViewportIndexLayerEXT);
}
}
-
if (device.IsFormatlessImageLoadSupported()) {
AddCapability(spv::Capability::StorageImageReadWithoutFormat);
}
-
if (device.IsFloat16Supported()) {
AddCapability(spv::Capability::Float16);
}
- t_scalar_half = Name(TypeFloat(device.IsFloat16Supported() ? 16 : 32), "scalar_half");
+ t_scalar_half = Name(TypeFloat(device_.IsFloat16Supported() ? 16 : 32), "scalar_half");
t_half = Name(TypeVector(t_scalar_half, 2), "half");
const Id main = Decompile();
@@ -369,6 +367,9 @@ public:
if (header.ps.omap.depth) {
AddExecutionMode(main, spv::ExecutionMode::DepthReplacing);
}
+ if (specialization.early_fragment_tests) {
+ AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests);
+ }
break;
case ShaderType::Compute:
const auto workgroup_size = specialization.workgroup_size;
@@ -972,7 +973,7 @@ private:
return binding;
}
- void DeclareImage(const Image& image, u32& binding) {
+ void DeclareImage(const ImageEntry& image, u32& binding) {
const auto [dim, arrayed] = GetImageDim(image);
constexpr int depth = 0;
constexpr bool ms = false;
@@ -1080,9 +1081,9 @@ private:
indices.point_size = AddBuiltIn(t_float, spv::BuiltIn::PointSize, "point_size");
}
- const auto& output_attributes = ir.GetOutputAttributes();
- const bool declare_clip_distances =
- std::any_of(output_attributes.begin(), output_attributes.end(), [](const auto& index) {
+ const auto& ir_output_attributes = ir.GetOutputAttributes();
+ const bool declare_clip_distances = std::any_of(
+ ir_output_attributes.begin(), ir_output_attributes.end(), [](const auto& index) {
return index == Attribute::Index::ClipDistances0123 ||
index == Attribute::Index::ClipDistances4567;
});
@@ -1246,7 +1247,7 @@ private:
const Id pointer = ArrayPass(type_descriptor.scalar, attribute_id, elements);
return {OpLoad(GetTypeDefinition(type), pointer), type};
}
- UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
+ UNIMPLEMENTED_MSG("Unhandled input attribute: {}", attribute);
return {v_float_zero, Type::Float};
}
@@ -1333,7 +1334,10 @@ private:
}
if (const auto comment = std::get_if<CommentNode>(&*node)) {
- Name(OpUndef(t_void), comment->GetText());
+ if (device.HasDebuggingToolAttached()) {
+ // We should insert comments with OpString instead of using named variables
+ Name(OpUndef(t_int), comment->GetText());
+ }
return {};
}
@@ -1882,7 +1886,7 @@ private:
case Tegra::Shader::TextureType::Texture3D:
return 3;
default:
- UNREACHABLE_MSG("Invalid texture type={}", static_cast<int>(type));
+ UNREACHABLE_MSG("Invalid texture type={}", type);
return 2;
}
}();
@@ -2067,6 +2071,46 @@ private:
return {};
}
+ Id MaxwellToSpirvComparison(Maxwell::ComparisonOp compare_op, Id operand_1, Id operand_2) {
+ using Compare = Maxwell::ComparisonOp;
+ switch (compare_op) {
+ case Compare::NeverOld:
+ return v_false; // Never let the test pass
+ case Compare::LessOld:
+ return OpFOrdLessThan(t_bool, operand_1, operand_2);
+ case Compare::EqualOld:
+ return OpFOrdEqual(t_bool, operand_1, operand_2);
+ case Compare::LessEqualOld:
+ return OpFOrdLessThanEqual(t_bool, operand_1, operand_2);
+ case Compare::GreaterOld:
+ return OpFOrdGreaterThan(t_bool, operand_1, operand_2);
+ case Compare::NotEqualOld:
+ return OpFOrdNotEqual(t_bool, operand_1, operand_2);
+ case Compare::GreaterEqualOld:
+ return OpFOrdGreaterThanEqual(t_bool, operand_1, operand_2);
+ default:
+ UNREACHABLE();
+ return v_true;
+ }
+ }
+
+ void AlphaTest(Id pointer) {
+ if (specialization.alpha_test_func == Maxwell::ComparisonOp::AlwaysOld) {
+ return;
+ }
+ const Id true_label = OpLabel();
+ const Id discard_label = OpLabel();
+ const Id alpha_reference = Constant(t_float, specialization.alpha_test_ref);
+ const Id alpha_value = OpLoad(t_float, pointer);
+ const Id condition =
+ MaxwellToSpirvComparison(specialization.alpha_test_func, alpha_value, alpha_reference);
+
+ OpBranchConditional(condition, true_label, discard_label);
+ AddLabel(discard_label);
+ OpKill();
+ AddLabel(true_label);
+ }
+
void PreExit() {
if (stage == ShaderType::Vertex && specialization.ndc_minus_one_to_one) {
const u32 position_index = out_indices.position.value();
@@ -2078,8 +2122,7 @@ private:
OpStore(z_pointer, depth);
}
if (stage == ShaderType::Fragment) {
- const auto SafeGetRegister = [&](u32 reg) {
- // TODO(Rodrigo): Replace with contains once C++20 releases
+ const auto SafeGetRegister = [this](u32 reg) {
if (const auto it = registers.find(reg); it != registers.end()) {
return OpLoad(t_float, it->second);
}
@@ -2089,8 +2132,6 @@ private:
UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0,
"Sample mask write is unimplemented");
- // TODO(Rodrigo): Alpha testing
-
// Write the color outputs using the data in the shader registers, disabled
// rendertargets/components are skipped in the register assignment.
u32 current_reg = 0;
@@ -2102,6 +2143,9 @@ private:
}
const Id pointer = AccessElement(t_out_float, frag_colors[rt], component);
OpStore(pointer, SafeGetRegister(current_reg));
+ if (rt == 0 && component == 3) {
+ AlphaTest(pointer);
+ }
++current_reg;
}
}
@@ -2701,7 +2745,7 @@ private:
};
static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount));
- const VKDevice& device;
+ const Device& device;
const ShaderIR& ir;
const ShaderType stage;
const Tegra::Shader::Header header;
@@ -2843,7 +2887,7 @@ private:
class ExprDecompiler {
public:
- explicit ExprDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
+ explicit ExprDecompiler(SPIRVDecompiler& decomp_) : decomp{decomp_} {}
Id operator()(const ExprAnd& expr) {
const Id type_def = decomp.GetTypeDefinition(Type::Bool);
@@ -2899,7 +2943,7 @@ private:
class ASTDecompiler {
public:
- explicit ASTDecompiler(SPIRVDecompiler& decomp) : decomp{decomp} {}
+ explicit ASTDecompiler(SPIRVDecompiler& decomp_) : decomp{decomp_} {}
void operator()(const ASTProgram& ast) {
ASTNode current = ast.nodes.GetFirst();
@@ -3062,7 +3106,11 @@ ShaderEntries GenerateShaderEntries(const VideoCommon::Shader::ShaderIR& ir) {
entries.const_buffers.emplace_back(cbuf.second, cbuf.first);
}
for (const auto& [base, usage] : ir.GetGlobalMemory()) {
- entries.global_buffers.emplace_back(base.cbuf_index, base.cbuf_offset, usage.is_written);
+ entries.global_buffers.emplace_back(GlobalBufferEntry{
+ .cbuf_index = base.cbuf_index,
+ .cbuf_offset = base.cbuf_offset,
+ .is_written = usage.is_written,
+ });
}
for (const auto& sampler : ir.GetSamplers()) {
if (sampler.is_buffer) {
@@ -3083,13 +3131,16 @@ ShaderEntries GenerateShaderEntries(const VideoCommon::Shader::ShaderIR& ir) {
entries.attributes.insert(GetGenericAttributeLocation(attribute));
}
}
+ for (const auto& buffer : entries.const_buffers) {
+ entries.enabled_uniform_buffers |= 1U << buffer.GetIndex();
+ }
entries.clip_distances = ir.GetClipDistances();
entries.shader_length = ir.GetLength();
entries.uses_warps = ir.UsesWarps();
return entries;
}
-std::vector<u32> Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir,
+std::vector<u32> Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
ShaderType stage, const VideoCommon::Shader::Registry& registry,
const Specialization& specialization) {
return SPIRVDecompiler(device, ir, stage, registry, specialization).Assemble();
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.h b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
index 2b0e90396..5d94132a5 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.h
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.h
@@ -15,23 +15,21 @@
#include "video_core/shader/shader_ir.h"
namespace Vulkan {
-class VKDevice;
-}
-namespace Vulkan {
+class Device;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-using UniformTexelEntry = VideoCommon::Shader::Sampler;
-using SamplerEntry = VideoCommon::Shader::Sampler;
-using StorageTexelEntry = VideoCommon::Shader::Image;
-using ImageEntry = VideoCommon::Shader::Image;
+using UniformTexelEntry = VideoCommon::Shader::SamplerEntry;
+using SamplerEntry = VideoCommon::Shader::SamplerEntry;
+using StorageTexelEntry = VideoCommon::Shader::ImageEntry;
+using ImageEntry = VideoCommon::Shader::ImageEntry;
constexpr u32 DESCRIPTOR_SET = 0;
class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer {
public:
- explicit constexpr ConstBufferEntry(const VideoCommon::Shader::ConstBuffer& entry, u32 index)
- : VideoCommon::Shader::ConstBuffer{entry}, index{index} {}
+ explicit constexpr ConstBufferEntry(const ConstBuffer& entry_, u32 index_)
+ : ConstBuffer{entry_}, index{index_} {}
constexpr u32 GetIndex() const {
return index;
@@ -41,24 +39,7 @@ private:
u32 index{};
};
-class GlobalBufferEntry {
-public:
- constexpr explicit GlobalBufferEntry(u32 cbuf_index, u32 cbuf_offset, bool is_written)
- : cbuf_index{cbuf_index}, cbuf_offset{cbuf_offset}, is_written{is_written} {}
-
- constexpr u32 GetCbufIndex() const {
- return cbuf_index;
- }
-
- constexpr u32 GetCbufOffset() const {
- return cbuf_offset;
- }
-
- constexpr bool IsWritten() const {
- return is_written;
- }
-
-private:
+struct GlobalBufferEntry {
u32 cbuf_index{};
u32 cbuf_offset{};
bool is_written{};
@@ -80,6 +61,7 @@ struct ShaderEntries {
std::set<u32> attributes;
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
std::size_t shader_length{};
+ u32 enabled_uniform_buffers{};
bool uses_warps{};
};
@@ -95,6 +77,9 @@ struct Specialization final {
std::bitset<Maxwell::NumVertexAttributes> enabled_attributes;
std::array<Maxwell::VertexAttribute::Type, Maxwell::NumVertexAttributes> attribute_types{};
bool ndc_minus_one_to_one{};
+ bool early_fragment_tests{};
+ float alpha_test_ref{};
+ Maxwell::ComparisonOp alpha_test_func{};
};
// Old gcc versions don't consider this trivially copyable.
// static_assert(std::is_trivially_copyable_v<Specialization>);
@@ -106,7 +91,7 @@ struct SPIRVShader {
ShaderEntries GenerateShaderEntries(const VideoCommon::Shader::ShaderIR& ir);
-std::vector<u32> Decompile(const VKDevice& device, const VideoCommon::Shader::ShaderIR& ir,
+std::vector<u32> Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
Tegra::Engines::ShaderType stage,
const VideoCommon::Shader::Registry& registry,
const Specialization& specialization);
diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp
index c1a218d76..aaad4f292 100644
--- a/src/video_core/renderer_vulkan/vk_shader_util.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp
@@ -7,24 +7,19 @@
#include "common/assert.h"
#include "common/common_types.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-vk::ShaderModule BuildShader(const VKDevice& device, std::size_t code_size, const u8* code_data) {
- // Avoid undefined behavior by copying to a staging allocation
- ASSERT(code_size % sizeof(u32) == 0);
- const auto data = std::make_unique<u32[]>(code_size / sizeof(u32));
- std::memcpy(data.get(), code_data, code_size);
-
+vk::ShaderModule BuildShader(const Device& device, std::span<const u32> code) {
return device.GetLogical().CreateShaderModule({
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .codeSize = code_size,
- .pCode = data.get(),
+ .codeSize = static_cast<u32>(code.size_bytes()),
+ .pCode = code.data(),
});
}
diff --git a/src/video_core/renderer_vulkan/vk_shader_util.h b/src/video_core/renderer_vulkan/vk_shader_util.h
index d1d3f3cae..9517cbe84 100644
--- a/src/video_core/renderer_vulkan/vk_shader_util.h
+++ b/src/video_core/renderer_vulkan/vk_shader_util.h
@@ -4,13 +4,15 @@
#pragma once
+#include <span>
+
#include "common/common_types.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-class VKDevice;
+class Device;
-vk::ShaderModule BuildShader(const VKDevice& device, std::size_t code_size, const u8* code_data);
+vk::ShaderModule BuildShader(const Device& device, std::span<const u32> code);
} // namespace Vulkan
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 5eca0ab91..7a1232497 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -3,76 +3,214 @@
// Refer to the license.txt file included.
#include <algorithm>
-#include <unordered_map>
#include <utility>
#include <vector>
+#include <fmt/format.h>
+
+#include "common/alignment.h"
+#include "common/assert.h"
#include "common/bit_util.h"
#include "common/common_types.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
+namespace {
+// Maximum potential alignment of a Vulkan buffer
+constexpr VkDeviceSize MAX_ALIGNMENT = 256;
+// Maximum size to put elements in the stream buffer
+constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8 * 1024 * 1024;
+// Stream buffer size in bytes
+constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
+constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
-VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence,
- u64 last_epoch)
- : buffer{std::move(buffer)}, watch{fence}, last_epoch{last_epoch} {}
+constexpr VkMemoryPropertyFlags HOST_FLAGS =
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+constexpr VkMemoryPropertyFlags STREAM_FLAGS = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | HOST_FLAGS;
-VKStagingBufferPool::StagingBuffer::StagingBuffer(StagingBuffer&& rhs) noexcept {
- buffer = std::move(rhs.buffer);
- watch = std::move(rhs.watch);
- last_epoch = rhs.last_epoch;
+bool IsStreamHeap(VkMemoryHeap heap) noexcept {
+ return STREAM_BUFFER_SIZE < (heap.size * 2) / 3;
}
-VKStagingBufferPool::StagingBuffer::~StagingBuffer() = default;
+std::optional<u32> FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_mask,
+ VkMemoryPropertyFlags flags) noexcept {
+ for (u32 type_index = 0; type_index < props.memoryTypeCount; ++type_index) {
+ if (((type_mask >> type_index) & 1) == 0) {
+ // Memory type is incompatible
+ continue;
+ }
+ const VkMemoryType& memory_type = props.memoryTypes[type_index];
+ if ((memory_type.propertyFlags & flags) != flags) {
+ // Memory type doesn't have the flags we want
+ continue;
+ }
+ if (!IsStreamHeap(props.memoryHeaps[memory_type.heapIndex])) {
+ // Memory heap is not suitable for streaming
+ continue;
+ }
+ // Success!
+ return type_index;
+ }
+ return std::nullopt;
+}
-VKStagingBufferPool::StagingBuffer& VKStagingBufferPool::StagingBuffer::operator=(
- StagingBuffer&& rhs) noexcept {
- buffer = std::move(rhs.buffer);
- watch = std::move(rhs.watch);
- last_epoch = rhs.last_epoch;
- return *this;
+u32 FindMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties& props, u32 type_mask) {
+ // Try to find a DEVICE_LOCAL_BIT type, Nvidia and AMD have a dedicated heap for this
+ std::optional<u32> type = FindMemoryTypeIndex(props, type_mask, STREAM_FLAGS);
+ if (type) {
+ return *type;
+ }
+ // Otherwise try without the DEVICE_LOCAL_BIT
+ type = FindMemoryTypeIndex(props, type_mask, HOST_FLAGS);
+ if (type) {
+ return *type;
+ }
+ // This should never happen, and in case it does, signal it as an out of memory situation
+ throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY);
}
-VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager,
- VKScheduler& scheduler)
- : device{device}, memory_manager{memory_manager}, scheduler{scheduler} {}
+size_t Region(size_t iterator) noexcept {
+ return iterator / REGION_SIZE;
+}
+} // Anonymous namespace
-VKStagingBufferPool::~VKStagingBufferPool() = default;
+StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
+ VKScheduler& scheduler_)
+ : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} {
+ const vk::Device& dev = device.GetLogical();
+ stream_buffer = dev.CreateBuffer(VkBufferCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .size = STREAM_BUFFER_SIZE,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
+ VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ });
+ if (device.HasDebuggingToolAttached()) {
+ stream_buffer.SetObjectNameEXT("Stream Buffer");
+ }
+ VkMemoryDedicatedRequirements dedicated_reqs{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS,
+ .pNext = nullptr,
+ .prefersDedicatedAllocation = VK_FALSE,
+ .requiresDedicatedAllocation = VK_FALSE,
+ };
+ const auto requirements = dev.GetBufferMemoryRequirements(*stream_buffer, &dedicated_reqs);
+ const bool make_dedicated = dedicated_reqs.prefersDedicatedAllocation == VK_TRUE ||
+ dedicated_reqs.requiresDedicatedAllocation == VK_TRUE;
+ const VkMemoryDedicatedAllocateInfo dedicated_info{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
+ .pNext = nullptr,
+ .image = nullptr,
+ .buffer = *stream_buffer,
+ };
+ const auto memory_properties = device.GetPhysical().GetMemoryProperties();
+ stream_memory = dev.AllocateMemory(VkMemoryAllocateInfo{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .pNext = make_dedicated ? &dedicated_info : nullptr,
+ .allocationSize = requirements.size,
+ .memoryTypeIndex = FindMemoryTypeIndex(memory_properties, requirements.memoryTypeBits),
+ });
+ if (device.HasDebuggingToolAttached()) {
+ stream_memory.SetObjectNameEXT("Stream Buffer Memory");
+ }
+ stream_buffer.BindMemory(*stream_memory, 0);
+ stream_pointer = stream_memory.Map(0, STREAM_BUFFER_SIZE);
+}
-VKBuffer& VKStagingBufferPool::GetUnusedBuffer(std::size_t size, bool host_visible) {
- if (const auto buffer = TryGetReservedBuffer(size, host_visible)) {
- return *buffer;
+StagingBufferPool::~StagingBufferPool() = default;
+
+StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage) {
+ if (usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) {
+ return GetStreamBuffer(size);
}
- return CreateStagingBuffer(size, host_visible);
+ return GetStagingBuffer(size, usage);
}
-void VKStagingBufferPool::TickFrame() {
- ++epoch;
- current_delete_level = (current_delete_level + 1) % NumLevels;
+void StagingBufferPool::TickFrame() {
+ current_delete_level = (current_delete_level + 1) % NUM_LEVELS;
- ReleaseCache(true);
- ReleaseCache(false);
+ ReleaseCache(MemoryUsage::DeviceLocal);
+ ReleaseCache(MemoryUsage::Upload);
+ ReleaseCache(MemoryUsage::Download);
}
-VKBuffer* VKStagingBufferPool::TryGetReservedBuffer(std::size_t size, bool host_visible) {
- for (auto& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) {
- if (entry.watch.TryWatch(scheduler.GetFence())) {
- entry.last_epoch = epoch;
- return &*entry.buffer;
+StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
+ if (AreRegionsActive(Region(free_iterator) + 1,
+ std::min(Region(iterator + size) + 1, NUM_SYNCS))) {
+ // Avoid waiting for the previous usages to be free
+ return GetStagingBuffer(size, MemoryUsage::Upload);
+ }
+ const u64 current_tick = scheduler.CurrentTick();
+ std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator),
+ current_tick);
+ used_iterator = iterator;
+ free_iterator = std::max(free_iterator, iterator + size);
+
+ if (iterator + size >= STREAM_BUFFER_SIZE) {
+ std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
+ current_tick);
+ used_iterator = 0;
+ iterator = 0;
+ free_iterator = size;
+
+ if (AreRegionsActive(0, Region(size) + 1)) {
+ // Avoid waiting for the previous usages to be free
+ return GetStagingBuffer(size, MemoryUsage::Upload);
}
}
- return nullptr;
+ const size_t offset = iterator;
+ iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT);
+ return StagingBufferRef{
+ .buffer = *stream_buffer,
+ .offset = static_cast<VkDeviceSize>(offset),
+ .mapped_span = std::span<u8>(stream_pointer + offset, size),
+ };
}
-VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_visible) {
- const u32 log2 = Common::Log2Ceil64(size);
+bool StagingBufferPool::AreRegionsActive(size_t region_begin, size_t region_end) const {
+ const u64 gpu_tick = scheduler.GetMasterSemaphore().KnownGpuTick();
+ return std::any_of(sync_ticks.begin() + region_begin, sync_ticks.begin() + region_end,
+ [gpu_tick](u64 sync_tick) { return gpu_tick < sync_tick; });
+};
- auto buffer = std::make_unique<VKBuffer>();
- buffer->handle = device.GetLogical().CreateBuffer({
+StagingBufferRef StagingBufferPool::GetStagingBuffer(size_t size, MemoryUsage usage) {
+ if (const std::optional<StagingBufferRef> ref = TryGetReservedBuffer(size, usage)) {
+ return *ref;
+ }
+ return CreateStagingBuffer(size, usage);
+}
+
+std::optional<StagingBufferRef> StagingBufferPool::TryGetReservedBuffer(size_t size,
+ MemoryUsage usage) {
+ StagingBuffers& cache_level = GetCache(usage)[Common::Log2Ceil64(size)];
+
+ const auto is_free = [this](const StagingBuffer& entry) {
+ return scheduler.IsFree(entry.tick);
+ };
+ auto& entries = cache_level.entries;
+ const auto hint_it = entries.begin() + cache_level.iterate_index;
+ auto it = std::find_if(entries.begin() + cache_level.iterate_index, entries.end(), is_free);
+ if (it == entries.end()) {
+ it = std::find_if(entries.begin(), hint_it, is_free);
+ if (it == hint_it) {
+ return std::nullopt;
+ }
+ }
+ cache_level.iterate_index = std::distance(entries.begin(), it) + 1;
+ it->tick = scheduler.CurrentTick();
+ return it->Ref();
+}
+
+StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage) {
+ const u32 log2 = Common::Log2Ceil64(size);
+ vk::Buffer buffer = device.GetLogical().CreateBuffer({
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@@ -84,48 +222,63 @@ VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_v
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
});
- buffer->commit = memory_manager.Commit(buffer->handle, host_visible);
-
- auto& entries = GetCache(host_visible)[log2].entries;
- return *entries.emplace_back(std::move(buffer), scheduler.GetFence(), epoch).buffer;
-}
+ if (device.HasDebuggingToolAttached()) {
+ ++buffer_index;
+ buffer.SetObjectNameEXT(fmt::format("Staging Buffer {}", buffer_index).c_str());
+ }
+ MemoryCommit commit = memory_allocator.Commit(buffer, usage);
+ const std::span<u8> mapped_span = IsHostVisible(usage) ? commit.Map() : std::span<u8>{};
-VKStagingBufferPool::StagingBuffersCache& VKStagingBufferPool::GetCache(bool host_visible) {
- return host_visible ? host_staging_buffers : device_staging_buffers;
+ StagingBuffer& entry = GetCache(usage)[log2].entries.emplace_back(StagingBuffer{
+ .buffer = std::move(buffer),
+ .commit = std::move(commit),
+ .mapped_span = mapped_span,
+ .tick = scheduler.CurrentTick(),
+ });
+ return entry.Ref();
}
-void VKStagingBufferPool::ReleaseCache(bool host_visible) {
- auto& cache = GetCache(host_visible);
- const u64 size = ReleaseLevel(cache, current_delete_level);
- if (size == 0) {
- return;
+StagingBufferPool::StagingBuffersCache& StagingBufferPool::GetCache(MemoryUsage usage) {
+ switch (usage) {
+ case MemoryUsage::DeviceLocal:
+ return device_local_cache;
+ case MemoryUsage::Upload:
+ return upload_cache;
+ case MemoryUsage::Download:
+ return download_cache;
+ default:
+ UNREACHABLE_MSG("Invalid memory usage={}", usage);
+ return upload_cache;
}
}
-u64 VKStagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, std::size_t log2) {
- static constexpr std::size_t deletions_per_tick = 16;
+void StagingBufferPool::ReleaseCache(MemoryUsage usage) {
+ ReleaseLevel(GetCache(usage), current_delete_level);
+}
+void StagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, size_t log2) {
+ constexpr size_t deletions_per_tick = 16;
auto& staging = cache[log2];
auto& entries = staging.entries;
- const std::size_t old_size = entries.size();
+ const size_t old_size = entries.size();
- const auto is_deleteable = [this](const auto& entry) {
- static constexpr u64 epochs_to_destroy = 180;
- return entry.last_epoch + epochs_to_destroy < epoch && !entry.watch.IsUsed();
+ const auto is_deleteable = [this](const StagingBuffer& entry) {
+ return scheduler.IsFree(entry.tick);
};
- const std::size_t begin_offset = staging.delete_index;
- const std::size_t end_offset = std::min(begin_offset + deletions_per_tick, old_size);
- const auto begin = std::begin(entries) + begin_offset;
- const auto end = std::begin(entries) + end_offset;
+ const size_t begin_offset = staging.delete_index;
+ const size_t end_offset = std::min(begin_offset + deletions_per_tick, old_size);
+ const auto begin = entries.begin() + begin_offset;
+ const auto end = entries.begin() + end_offset;
entries.erase(std::remove_if(begin, end, is_deleteable), end);
- const std::size_t new_size = entries.size();
+ const size_t new_size = entries.size();
staging.delete_index += deletions_per_tick;
if (staging.delete_index >= new_size) {
staging.delete_index = 0;
}
-
- return (1ULL << log2) * (old_size - new_size);
+ if (staging.iterate_index > new_size) {
+ staging.iterate_index = 0;
+ }
}
} // namespace Vulkan
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 3c4901437..69f7618de 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -9,73 +9,97 @@
#include "common/common_types.h"
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-class VKDevice;
-class VKFenceWatch;
+class Device;
class VKScheduler;
-struct VKBuffer final {
- vk::Buffer handle;
- VKMemoryCommit commit;
+struct StagingBufferRef {
+ VkBuffer buffer;
+ VkDeviceSize offset;
+ std::span<u8> mapped_span;
};
-class VKStagingBufferPool final {
+class StagingBufferPool {
public:
- explicit VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager,
- VKScheduler& scheduler);
- ~VKStagingBufferPool();
+ static constexpr size_t NUM_SYNCS = 16;
- VKBuffer& GetUnusedBuffer(std::size_t size, bool host_visible);
+ explicit StagingBufferPool(const Device& device, MemoryAllocator& memory_allocator,
+ VKScheduler& scheduler);
+ ~StagingBufferPool();
+
+ StagingBufferRef Request(size_t size, MemoryUsage usage);
void TickFrame();
private:
- struct StagingBuffer final {
- explicit StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence, u64 last_epoch);
- StagingBuffer(StagingBuffer&& rhs) noexcept;
- StagingBuffer(const StagingBuffer&) = delete;
- ~StagingBuffer();
-
- StagingBuffer& operator=(StagingBuffer&& rhs) noexcept;
+ struct StreamBufferCommit {
+ size_t upper_bound;
+ u64 tick;
+ };
- std::unique_ptr<VKBuffer> buffer;
- VKFenceWatch watch;
- u64 last_epoch = 0;
+ struct StagingBuffer {
+ vk::Buffer buffer;
+ MemoryCommit commit;
+ std::span<u8> mapped_span;
+ u64 tick = 0;
+
+ StagingBufferRef Ref() const noexcept {
+ return {
+ .buffer = *buffer,
+ .offset = 0,
+ .mapped_span = mapped_span,
+ };
+ }
};
- struct StagingBuffers final {
+ struct StagingBuffers {
std::vector<StagingBuffer> entries;
- std::size_t delete_index = 0;
+ size_t delete_index = 0;
+ size_t iterate_index = 0;
};
- static constexpr std::size_t NumLevels = sizeof(std::size_t) * CHAR_BIT;
- using StagingBuffersCache = std::array<StagingBuffers, NumLevels>;
+ static constexpr size_t NUM_LEVELS = sizeof(size_t) * CHAR_BIT;
+ using StagingBuffersCache = std::array<StagingBuffers, NUM_LEVELS>;
+
+ StagingBufferRef GetStreamBuffer(size_t size);
- VKBuffer* TryGetReservedBuffer(std::size_t size, bool host_visible);
+ bool AreRegionsActive(size_t region_begin, size_t region_end) const;
- VKBuffer& CreateStagingBuffer(std::size_t size, bool host_visible);
+ StagingBufferRef GetStagingBuffer(size_t size, MemoryUsage usage);
- StagingBuffersCache& GetCache(bool host_visible);
+ std::optional<StagingBufferRef> TryGetReservedBuffer(size_t size, MemoryUsage usage);
- void ReleaseCache(bool host_visible);
+ StagingBufferRef CreateStagingBuffer(size_t size, MemoryUsage usage);
- u64 ReleaseLevel(StagingBuffersCache& cache, std::size_t log2);
+ StagingBuffersCache& GetCache(MemoryUsage usage);
- const VKDevice& device;
- VKMemoryManager& memory_manager;
+ void ReleaseCache(MemoryUsage usage);
+
+ void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
+
+ const Device& device;
+ MemoryAllocator& memory_allocator;
VKScheduler& scheduler;
- StagingBuffersCache host_staging_buffers;
- StagingBuffersCache device_staging_buffers;
+ vk::Buffer stream_buffer;
+ vk::DeviceMemory stream_memory;
+ u8* stream_pointer = nullptr;
+
+ size_t iterator = 0;
+ size_t used_iterator = 0;
+ size_t free_iterator = 0;
+ std::array<u64, NUM_SYNCS> sync_ticks{};
- u64 epoch = 0;
+ StagingBuffersCache device_local_cache;
+ StagingBuffersCache upload_cache;
+ StagingBuffersCache download_cache;
- std::size_t current_delete_level = 0;
+ size_t current_delete_level = 0;
+ u64 buffer_index = 0;
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index 4bd1009f9..956f86845 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <cstddef>
#include <iterator>
@@ -14,12 +15,10 @@
#include "video_core/renderer_vulkan/vk_state_tracker.h"
#define OFF(field_name) MAXWELL3D_REG_INDEX(field_name)
-#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / sizeof(u32))
+#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32)))
namespace Vulkan {
-
namespace {
-
using namespace Dirty;
using namespace VideoCommon::Dirty;
using Tegra::Engines::Maxwell3D;
@@ -29,21 +28,18 @@ using Table = Maxwell3D::DirtyState::Table;
using Flags = Maxwell3D::DirtyState::Flags;
Flags MakeInvalidationFlags() {
+ static constexpr int INVALIDATION_FLAGS[]{
+ Viewports, Scissors, DepthBias, BlendConstants, DepthBounds,
+ StencilProperties, CullMode, DepthBoundsEnable, DepthTestEnable, DepthWriteEnable,
+ DepthCompareOp, FrontFace, StencilOp, StencilTestEnable, VertexBuffers,
+ };
Flags flags{};
- flags[Viewports] = true;
- flags[Scissors] = true;
- flags[DepthBias] = true;
- flags[BlendConstants] = true;
- flags[DepthBounds] = true;
- flags[StencilProperties] = true;
- flags[CullMode] = true;
- flags[DepthBoundsEnable] = true;
- flags[DepthTestEnable] = true;
- flags[DepthWriteEnable] = true;
- flags[DepthCompareOp] = true;
- flags[FrontFace] = true;
- flags[StencilOp] = true;
- flags[StencilTestEnable] = true;
+ for (const int flag : INVALIDATION_FLAGS) {
+ flags[flag] = true;
+ }
+ for (int index = VertexBuffer0; index <= VertexBuffer31; ++index) {
+ flags[index] = true;
+ }
return flags;
}
@@ -130,15 +126,40 @@ void SetupDirtyStencilTestEnable(Tables& tables) {
tables[0][OFF(stencil_enable)] = StencilTestEnable;
}
-} // Anonymous namespace
+void SetupDirtyBlending(Tables& tables) {
+ tables[0][OFF(color_mask_common)] = Blending;
+ tables[0][OFF(independent_blend_enable)] = Blending;
+ FillBlock(tables[0], OFF(color_mask), NUM(color_mask), Blending);
+ FillBlock(tables[0], OFF(blend), NUM(blend), Blending);
+ FillBlock(tables[0], OFF(independent_blend), NUM(independent_blend), Blending);
+}
+
+void SetupDirtyInstanceDivisors(Tables& tables) {
+ static constexpr size_t divisor_offset = 3;
+ for (size_t index = 0; index < Regs::NumVertexArrays; ++index) {
+ tables[0][OFF(instanced_arrays) + index] = InstanceDivisors;
+ tables[0][OFF(vertex_array) + index * NUM(vertex_array[0]) + divisor_offset] =
+ InstanceDivisors;
+ }
+}
-StateTracker::StateTracker(Core::System& system)
- : system{system}, invalidation_flags{MakeInvalidationFlags()} {}
+void SetupDirtyVertexAttributes(Tables& tables) {
+ FillBlock(tables[0], OFF(vertex_attrib_format), NUM(vertex_attrib_format), VertexAttributes);
+}
+
+void SetupDirtyViewportSwizzles(Tables& tables) {
+ static constexpr size_t swizzle_offset = 6;
+ for (size_t index = 0; index < Regs::NumViewports; ++index) {
+ tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
+ ViewportSwizzles;
+ }
+}
+} // Anonymous namespace
-void StateTracker::Initialize() {
- auto& dirty = system.GPU().Maxwell3D().dirty;
- auto& tables = dirty.tables;
- SetupDirtyRenderTargets(tables);
+StateTracker::StateTracker(Tegra::GPU& gpu)
+ : flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} {
+ auto& tables = gpu.Maxwell3D().dirty.tables;
+ SetupDirtyFlags(tables);
SetupDirtyViewports(tables);
SetupDirtyScissors(tables);
SetupDirtyDepthBias(tables);
@@ -153,11 +174,10 @@ void StateTracker::Initialize() {
SetupDirtyFrontFace(tables);
SetupDirtyStencilOp(tables);
SetupDirtyStencilTestEnable(tables);
-}
-
-void StateTracker::InvalidateCommandBufferState() {
- system.GPU().Maxwell3D().dirty.flags |= invalidation_flags;
- current_topology = INVALID_TOPOLOGY;
+ SetupDirtyBlending(tables);
+ SetupDirtyInstanceDivisors(tables);
+ SetupDirtyVertexAttributes(tables);
+ SetupDirtyViewportSwizzles(tables);
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h
index 13a6ce786..84e918a71 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.h
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.h
@@ -35,6 +35,11 @@ enum : u8 {
StencilOp,
StencilTestEnable,
+ Blending,
+ InstanceDivisors,
+ VertexAttributes,
+ ViewportSwizzles,
+
Last
};
static_assert(Last <= std::numeric_limits<u8>::max());
@@ -45,11 +50,20 @@ class StateTracker {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
public:
- explicit StateTracker(Core::System& system);
+ explicit StateTracker(Tegra::GPU& gpu);
- void Initialize();
+ void InvalidateCommandBufferState() {
+ flags |= invalidation_flags;
+ current_topology = INVALID_TOPOLOGY;
+ }
- void InvalidateCommandBufferState();
+ void InvalidateViewports() {
+ flags[Dirty::Viewports] = true;
+ }
+
+ void InvalidateScissors() {
+ flags[Dirty::Scissors] = true;
+ }
bool TouchViewports() {
return Exchange(Dirty::Viewports, false);
@@ -121,13 +135,12 @@ private:
static constexpr auto INVALID_TOPOLOGY = static_cast<Maxwell::PrimitiveTopology>(~0u);
bool Exchange(std::size_t id, bool new_value) const noexcept {
- auto& flags = system.GPU().Maxwell3D().dirty.flags;
const bool is_dirty = flags[id];
flags[id] = new_value;
return is_dirty;
}
- Core::System& system;
+ Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags;
Maxwell::PrimitiveTopology current_topology = INVALID_TOPOLOGY;
};
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
index a5526a3f5..a09fe084e 100644
--- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
+++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
@@ -10,16 +10,19 @@
#include "common/alignment.h"
#include "common/assert.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
namespace {
+constexpr VkBufferUsageFlags BUFFER_USAGE =
+ VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
+ VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+
constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000;
constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000;
@@ -57,17 +60,16 @@ u32 GetMemoryType(const VkPhysicalDeviceMemoryProperties& properties,
} // Anonymous namespace
-VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler,
- VkBufferUsageFlags usage)
- : device{device}, scheduler{scheduler} {
- CreateBuffers(usage);
+VKStreamBuffer::VKStreamBuffer(const Device& device_, VKScheduler& scheduler_)
+ : device{device_}, scheduler{scheduler_} {
+ CreateBuffers();
ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE);
ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE);
}
VKStreamBuffer::~VKStreamBuffer() = default;
-std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) {
+std::pair<u8*, u64> VKStreamBuffer::Map(u64 size, u64 alignment) {
ASSERT(size <= stream_buffer_size);
mapped_size = size;
@@ -77,7 +79,6 @@ std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) {
WaitPendingOperations(offset);
- bool invalidated = false;
if (offset + size > stream_buffer_size) {
// The buffer would overflow, save the amount of used watches and reset the state.
invalidation_mark = current_watch_cursor;
@@ -91,11 +92,9 @@ std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) {
// Ensure that we don't wait for uncommitted fences.
scheduler.Flush();
-
- invalidated = true;
}
- return {memory.Map(offset, size), offset, invalidated};
+ return std::make_pair(memory.Map(offset, size), offset);
}
void VKStreamBuffer::Unmap(u64 size) {
@@ -111,23 +110,24 @@ void VKStreamBuffer::Unmap(u64 size) {
}
auto& watch = current_watches[current_watch_cursor++];
watch.upper_bound = offset;
- watch.fence.Watch(scheduler.GetFence());
+ watch.tick = scheduler.CurrentTick();
}
-void VKStreamBuffer::CreateBuffers(VkBufferUsageFlags usage) {
+void VKStreamBuffer::CreateBuffers() {
const auto memory_properties = device.GetPhysical().GetMemoryProperties();
const u32 preferred_type = GetMemoryType(memory_properties);
const u32 preferred_heap = memory_properties.memoryTypes[preferred_type].heapIndex;
// Substract from the preferred heap size some bytes to avoid getting out of memory.
const VkDeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size;
- const VkDeviceSize allocable_size = heap_size - 9 * 1024 * 1024;
+ // As per DXVK's example, using `heap_size / 2`
+ const VkDeviceSize allocable_size = heap_size / 2;
buffer = device.GetLogical().CreateBuffer({
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = std::min(PREFERRED_STREAM_BUFFER_SIZE, allocable_size),
- .usage = usage,
+ .usage = BUFFER_USAGE,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
@@ -157,7 +157,7 @@ void VKStreamBuffer::WaitPendingOperations(u64 requested_upper_bound) {
while (requested_upper_bound < wait_bound && wait_cursor < *invalidation_mark) {
auto& watch = previous_watches[wait_cursor];
wait_bound = watch.upper_bound;
- watch.fence.Wait();
+ scheduler.Wait(watch.tick);
++wait_cursor;
}
}
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h
index 689f0d276..2e9c8cb46 100644
--- a/src/video_core/renderer_vulkan/vk_stream_buffer.h
+++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h
@@ -5,32 +5,29 @@
#pragma once
#include <optional>
-#include <tuple>
+#include <utility>
#include <vector>
#include "common/common_types.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-class VKDevice;
-class VKFence;
+class Device;
class VKFenceWatch;
class VKScheduler;
class VKStreamBuffer final {
public:
- explicit VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler,
- VkBufferUsageFlags usage);
+ explicit VKStreamBuffer(const Device& device, VKScheduler& scheduler);
~VKStreamBuffer();
/**
* Reserves a region of memory from the stream buffer.
* @param size Size to reserve.
- * @returns A tuple in the following order: Raw memory pointer (with offset added), buffer
- * offset and a boolean that's true when buffer has been invalidated.
+ * @returns A pair of a raw memory pointer (with offset added), and the buffer offset
*/
- std::tuple<u8*, u64, bool> Map(u64 size, u64 alignment);
+ std::pair<u8*, u64> Map(u64 size, u64 alignment);
/// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy.
void Unmap(u64 size);
@@ -44,20 +41,20 @@ public:
}
private:
- struct Watch final {
- VKFenceWatch fence;
+ struct Watch {
+ u64 tick{};
u64 upper_bound{};
};
/// Creates Vulkan buffer handles committing the required the required memory.
- void CreateBuffers(VkBufferUsageFlags usage);
+ void CreateBuffers();
/// Increases the amount of watches available.
void ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size);
void WaitPendingOperations(u64 requested_upper_bound);
- const VKDevice& device; ///< Vulkan device manager.
+ const Device& device; ///< Vulkan device manager.
VKScheduler& scheduler; ///< Command scheduler.
vk::Buffer buffer; ///< Mapped buffer.
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 6bfd2abae..0b63bd6c8 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -11,10 +11,10 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
@@ -56,8 +56,11 @@ VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 wi
} // Anonymous namespace
-VKSwapchain::VKSwapchain(VkSurfaceKHR surface, const VKDevice& device)
- : surface{surface}, device{device} {}
+VKSwapchain::VKSwapchain(VkSurfaceKHR surface_, const Device& device_, VKScheduler& scheduler_,
+ u32 width, u32 height, bool srgb)
+ : surface{surface_}, device{device_}, scheduler{scheduler_} {
+ Create(width, height, srgb);
+}
VKSwapchain::~VKSwapchain() = default;
@@ -75,21 +78,18 @@ void VKSwapchain::Create(u32 width, u32 height, bool srgb) {
CreateSemaphores();
CreateImageViews();
- fences.resize(image_count, nullptr);
+ resource_ticks.clear();
+ resource_ticks.resize(image_count);
}
void VKSwapchain::AcquireNextImage() {
device.GetLogical().AcquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(),
*present_semaphores[frame_index], {}, &image_index);
- if (auto& fence = fences[image_index]; fence) {
- fence->Wait();
- fence->Release();
- fence = nullptr;
- }
+ scheduler.Wait(resource_ticks[image_index]);
}
-bool VKSwapchain::Present(VkSemaphore render_semaphore, VKFence& fence) {
+bool VKSwapchain::Present(VkSemaphore render_semaphore) {
const VkSemaphore present_semaphore{*present_semaphores[frame_index]};
const std::array<VkSemaphore, 2> semaphores{present_semaphore, render_semaphore};
const auto present_queue{device.GetPresentQueue()};
@@ -123,8 +123,7 @@ bool VKSwapchain::Present(VkSemaphore render_semaphore, VKFence& fence) {
break;
}
- ASSERT(fences[image_index] == nullptr);
- fences[image_index] = &fence;
+ resource_ticks[image_index] = scheduler.CurrentTick();
frame_index = (frame_index + 1) % static_cast<u32>(image_count);
return recreated;
}
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
index a35d61345..a728511e0 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.h
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -7,7 +7,7 @@
#include <vector>
#include "common/common_types.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Layout {
struct FramebufferLayout;
@@ -15,12 +15,13 @@ struct FramebufferLayout;
namespace Vulkan {
-class VKDevice;
-class VKFence;
+class Device;
+class VKScheduler;
class VKSwapchain {
public:
- explicit VKSwapchain(VkSurfaceKHR surface, const VKDevice& device);
+ explicit VKSwapchain(VkSurfaceKHR surface, const Device& device, VKScheduler& scheduler,
+ u32 width, u32 height, bool srgb);
~VKSwapchain();
/// Creates (or recreates) the swapchain with a given size.
@@ -31,7 +32,7 @@ public:
/// Presents the rendered image to the swapchain. Returns true when the swapchains had to be
/// recreated. Takes responsability for the ownership of fence.
- bool Present(VkSemaphore render_semaphore, VKFence& fence);
+ bool Present(VkSemaphore render_semaphore);
/// Returns true when the framebuffer layout has changed.
bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const;
@@ -73,7 +74,8 @@ private:
void Destroy();
const VkSurfaceKHR surface;
- const VKDevice& device;
+ const Device& device;
+ VKScheduler& scheduler;
vk::SwapchainKHR swapchain;
@@ -81,7 +83,7 @@ private:
std::vector<VkImage> images;
std::vector<vk::ImageView> image_views;
std::vector<vk::Framebuffer> framebuffers;
- std::vector<VKFence*> fences;
+ std::vector<u64> resource_ticks;
std::vector<vk::Semaphore> present_semaphores;
u32 image_index{};
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 2c6f54101..22a1014a9 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -4,614 +4,1167 @@
#include <algorithm>
#include <array>
-#include <cstddef>
-#include <cstring>
-#include <memory>
-#include <variant>
+#include <span>
#include <vector>
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "core/core.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/morton.h"
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/renderer_vulkan/blit_image.h"
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-#include "video_core/surface.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-using VideoCore::MortonSwizzle;
-using VideoCore::MortonSwizzleMode;
-
+using Tegra::Engines::Fermi2D;
using Tegra::Texture::SwizzleSource;
-using VideoCore::Surface::PixelFormat;
-using VideoCore::Surface::SurfaceTarget;
+using Tegra::Texture::TextureMipmapFilter;
+using VideoCommon::BufferImageCopy;
+using VideoCommon::ImageInfo;
+using VideoCommon::ImageType;
+using VideoCommon::SubresourceRange;
+using VideoCore::Surface::IsPixelFormatASTC;
namespace {
-VkImageType SurfaceTargetToImage(SurfaceTarget target) {
- switch (target) {
- case SurfaceTarget::Texture1D:
- case SurfaceTarget::Texture1DArray:
+constexpr std::array ATTACHMENT_REFERENCES{
+ VkAttachmentReference{0, VK_IMAGE_LAYOUT_GENERAL},
+ VkAttachmentReference{1, VK_IMAGE_LAYOUT_GENERAL},
+ VkAttachmentReference{2, VK_IMAGE_LAYOUT_GENERAL},
+ VkAttachmentReference{3, VK_IMAGE_LAYOUT_GENERAL},
+ VkAttachmentReference{4, VK_IMAGE_LAYOUT_GENERAL},
+ VkAttachmentReference{5, VK_IMAGE_LAYOUT_GENERAL},
+ VkAttachmentReference{6, VK_IMAGE_LAYOUT_GENERAL},
+ VkAttachmentReference{7, VK_IMAGE_LAYOUT_GENERAL},
+ VkAttachmentReference{8, VK_IMAGE_LAYOUT_GENERAL},
+};
+
+constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
+ if (color == std::array<float, 4>{0, 0, 0, 0}) {
+ return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+ } else if (color == std::array<float, 4>{0, 0, 0, 1}) {
+ return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
+ } else if (color == std::array<float, 4>{1, 1, 1, 1}) {
+ return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+ }
+ if (color[0] + color[1] + color[2] > 1.35f) {
+ // If color elements are brighter than roughly 0.5 average, use white border
+ return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+ } else if (color[3] > 0.5f) {
+ return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
+ } else {
+ return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+ }
+}
+
+[[nodiscard]] VkImageType ConvertImageType(const ImageType type) {
+ switch (type) {
+ case ImageType::e1D:
return VK_IMAGE_TYPE_1D;
- case SurfaceTarget::Texture2D:
- case SurfaceTarget::Texture2DArray:
- case SurfaceTarget::TextureCubemap:
- case SurfaceTarget::TextureCubeArray:
+ case ImageType::e2D:
+ case ImageType::Linear:
return VK_IMAGE_TYPE_2D;
- case SurfaceTarget::Texture3D:
+ case ImageType::e3D:
return VK_IMAGE_TYPE_3D;
- case SurfaceTarget::TextureBuffer:
- UNREACHABLE();
- return {};
+ case ImageType::Buffer:
+ break;
}
- UNREACHABLE_MSG("Unknown texture target={}", static_cast<u32>(target));
+ UNREACHABLE_MSG("Invalid image type={}", type);
return {};
}
-VkImageAspectFlags PixelFormatToImageAspect(PixelFormat pixel_format) {
- if (pixel_format < PixelFormat::MaxColorFormat) {
- return VK_IMAGE_ASPECT_COLOR_BIT;
- } else if (pixel_format < PixelFormat::MaxDepthFormat) {
- return VK_IMAGE_ASPECT_DEPTH_BIT;
- } else if (pixel_format < PixelFormat::MaxDepthStencilFormat) {
- return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
- } else {
- UNREACHABLE_MSG("Invalid pixel format={}", static_cast<int>(pixel_format));
- return VK_IMAGE_ASPECT_COLOR_BIT;
+[[nodiscard]] VkSampleCountFlagBits ConvertSampleCount(u32 num_samples) {
+ switch (num_samples) {
+ case 1:
+ return VK_SAMPLE_COUNT_1_BIT;
+ case 2:
+ return VK_SAMPLE_COUNT_2_BIT;
+ case 4:
+ return VK_SAMPLE_COUNT_4_BIT;
+ case 8:
+ return VK_SAMPLE_COUNT_8_BIT;
+ case 16:
+ return VK_SAMPLE_COUNT_16_BIT;
+ default:
+ UNREACHABLE_MSG("Invalid number of samples={}", num_samples);
+ return VK_SAMPLE_COUNT_1_BIT;
}
}
-VkImageViewType GetImageViewType(SurfaceTarget target) {
- switch (target) {
- case SurfaceTarget::Texture1D:
- return VK_IMAGE_VIEW_TYPE_1D;
- case SurfaceTarget::Texture2D:
- return VK_IMAGE_VIEW_TYPE_2D;
- case SurfaceTarget::Texture3D:
- return VK_IMAGE_VIEW_TYPE_3D;
- case SurfaceTarget::Texture1DArray:
- return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
- case SurfaceTarget::Texture2DArray:
- return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
- case SurfaceTarget::TextureCubemap:
- return VK_IMAGE_VIEW_TYPE_CUBE;
- case SurfaceTarget::TextureCubeArray:
- return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
- case SurfaceTarget::TextureBuffer:
- break;
+[[nodiscard]] VkImageUsageFlags ImageUsageFlags(const MaxwellToVK::FormatInfo& info,
+ PixelFormat format) {
+ VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT;
+ if (info.attachable) {
+ switch (VideoCore::Surface::GetFormatType(format)) {
+ case VideoCore::Surface::SurfaceType::ColorTexture:
+ usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ break;
+ case VideoCore::Surface::SurfaceType::Depth:
+ case VideoCore::Surface::SurfaceType::DepthStencil:
+ usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ break;
+ default:
+ UNREACHABLE_MSG("Invalid surface type");
+ }
}
- UNREACHABLE();
- return {};
+ if (info.storage) {
+ usage |= VK_IMAGE_USAGE_STORAGE_BIT;
+ }
+ return usage;
}
-vk::Buffer CreateBuffer(const VKDevice& device, const SurfaceParams& params,
- std::size_t host_memory_size) {
- // TODO(Rodrigo): Move texture buffer creation to the buffer cache
- return device.GetLogical().CreateBuffer({
- .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+/// Returns the preferred format for a VkImage
+[[nodiscard]] PixelFormat StorageFormat(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::A8B8G8R8_SRGB:
+ return PixelFormat::A8B8G8R8_UNORM;
+ default:
+ return format;
+ }
+}
+
+[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
+ const PixelFormat format = StorageFormat(info.format);
+ const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format);
+ VkImageCreateFlags flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+ if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
+ info.size.width == info.size.height) {
+ flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+ }
+ if (info.type == ImageType::e3D) {
+ flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
+ }
+ const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples);
+ return VkImageCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
- .flags = 0,
- .size = static_cast<VkDeviceSize>(host_memory_size),
- .usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
- VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
- VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ .flags = flags,
+ .imageType = ConvertImageType(info.type),
+ .format = format_info.format,
+ .extent{
+ .width = info.size.width >> samples_x,
+ .height = info.size.height >> samples_y,
+ .depth = info.size.depth,
+ },
+ .mipLevels = static_cast<u32>(info.resources.levels),
+ .arrayLayers = static_cast<u32>(info.resources.layers),
+ .samples = ConvertSampleCount(info.num_samples),
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = ImageUsageFlags(format_info, format),
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
- });
-}
-
-VkBufferViewCreateInfo GenerateBufferViewCreateInfo(const VKDevice& device,
- const SurfaceParams& params, VkBuffer buffer,
- std::size_t host_memory_size) {
- ASSERT(params.IsBuffer());
-
- return {
- .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .buffer = buffer,
- .format =
- MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, params.pixel_format).format,
- .offset = 0,
- .range = static_cast<VkDeviceSize>(host_memory_size),
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
}
-VkImageCreateInfo GenerateImageCreateInfo(const VKDevice& device, const SurfaceParams& params) {
- ASSERT(!params.IsBuffer());
-
- const auto [format, attachable, storage] =
- MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, params.pixel_format);
+[[nodiscard]] vk::Image MakeImage(const Device& device, const ImageInfo& info) {
+ if (info.type == ImageType::Buffer) {
+ return vk::Image{};
+ }
+ return device.GetLogical().CreateImage(MakeImageCreateInfo(device, info));
+}
- VkImageCreateInfo ci{
- .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+[[nodiscard]] vk::Buffer MakeBuffer(const Device& device, const ImageInfo& info) {
+ if (info.type != ImageType::Buffer) {
+ return vk::Buffer{};
+ }
+ const size_t bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
+ return device.GetLogical().CreateBuffer(VkBufferCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .imageType = SurfaceTargetToImage(params.target),
- .format = format,
- .extent = {},
- .mipLevels = params.num_levels,
- .arrayLayers = static_cast<u32>(params.GetNumLayers()),
- .samples = VK_SAMPLE_COUNT_1_BIT,
- .tiling = VK_IMAGE_TILING_OPTIMAL,
- .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
- VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
+ .size = info.size.width * bytes_per_block,
+ .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
+ VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
+ VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
- .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
- };
- if (attachable) {
- ci.usage |= params.IsPixelFormatZeta() ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
- : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
- }
- if (storage) {
- ci.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
- }
-
- switch (params.target) {
- case SurfaceTarget::TextureCubemap:
- case SurfaceTarget::TextureCubeArray:
- ci.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
- [[fallthrough]];
- case SurfaceTarget::Texture1D:
- case SurfaceTarget::Texture1DArray:
- case SurfaceTarget::Texture2D:
- case SurfaceTarget::Texture2DArray:
- ci.extent = {params.width, params.height, 1};
- break;
- case SurfaceTarget::Texture3D:
- ci.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
- ci.extent = {params.width, params.height, params.depth};
- break;
- case SurfaceTarget::TextureBuffer:
- UNREACHABLE();
- }
-
- return ci;
+ });
}
-u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::SwizzleSource y_source,
- Tegra::Texture::SwizzleSource z_source, Tegra::Texture::SwizzleSource w_source) {
- return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) |
- (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source);
+[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
+ switch (VideoCore::Surface::GetFormatType(format)) {
+ case VideoCore::Surface::SurfaceType::ColorTexture:
+ return VK_IMAGE_ASPECT_COLOR_BIT;
+ case VideoCore::Surface::SurfaceType::Depth:
+ return VK_IMAGE_ASPECT_DEPTH_BIT;
+ case VideoCore::Surface::SurfaceType::DepthStencil:
+ return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ default:
+ UNREACHABLE_MSG("Invalid surface type");
+ return VkImageAspectFlags{};
+ }
}
-} // Anonymous namespace
-
-CachedSurface::CachedSurface(Core::System& system, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
- GPUVAddr gpu_addr, const SurfaceParams& params)
- : SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, system{system},
- device{device}, resource_manager{resource_manager},
- memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{staging_pool} {
- if (params.IsBuffer()) {
- buffer = CreateBuffer(device, params, host_memory_size);
- commit = memory_manager.Commit(buffer, false);
-
- const auto buffer_view_ci =
- GenerateBufferViewCreateInfo(device, params, *buffer, host_memory_size);
- format = buffer_view_ci.format;
-
- buffer_view = device.GetLogical().CreateBufferView(buffer_view_ci);
- } else {
- const auto image_ci = GenerateImageCreateInfo(device, params);
- format = image_ci.format;
-
- image.emplace(device, scheduler, image_ci, PixelFormatToImageAspect(params.pixel_format));
- commit = memory_manager.Commit(image->GetHandle(), false);
+[[nodiscard]] VkImageAspectFlags ImageViewAspectMask(const VideoCommon::ImageViewInfo& info) {
+ if (info.IsRenderTarget()) {
+ return ImageAspectMask(info.format);
}
-
- // TODO(Rodrigo): Move this to a virtual function.
- u32 num_layers = 1;
- if (params.is_layered || params.target == SurfaceTarget::Texture3D) {
- num_layers = params.depth;
+ const bool is_first = info.Swizzle()[0] == SwizzleSource::R;
+ switch (info.format) {
+ case PixelFormat::D24_UNORM_S8_UINT:
+ case PixelFormat::D32_FLOAT_S8_UINT:
+ return is_first ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_STENCIL_BIT;
+ case PixelFormat::S8_UINT_D24_UNORM:
+ return is_first ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
+ case PixelFormat::D16_UNORM:
+ case PixelFormat::D32_FLOAT:
+ return VK_IMAGE_ASPECT_DEPTH_BIT;
+ default:
+ return VK_IMAGE_ASPECT_COLOR_BIT;
}
- main_view = CreateView(ViewParams(params.target, 0, num_layers, 0, params.num_levels));
}
-CachedSurface::~CachedSurface() = default;
-
-void CachedSurface::UploadTexture(const std::vector<u8>& staging_buffer) {
- // To upload data we have to be outside of a renderpass
- scheduler.RequestOutsideRenderPassOperationContext();
+[[nodiscard]] VkAttachmentDescription AttachmentDescription(const Device& device,
+ const ImageView* image_view) {
+ using MaxwellToVK::SurfaceFormat;
+ const PixelFormat pixel_format = image_view->format;
+ return VkAttachmentDescription{
+ .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT,
+ .format = SurfaceFormat(device, FormatType::Optimal, true, pixel_format).format,
+ .samples = image_view->Samples(),
+ .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
+ .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
+ .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE,
+ .initialLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .finalLayout = VK_IMAGE_LAYOUT_GENERAL,
+ };
+}
- if (params.IsBuffer()) {
- UploadBuffer(staging_buffer);
- } else {
- UploadImage(staging_buffer);
+[[nodiscard]] VkComponentSwizzle ComponentSwizzle(SwizzleSource swizzle) {
+ switch (swizzle) {
+ case SwizzleSource::Zero:
+ return VK_COMPONENT_SWIZZLE_ZERO;
+ case SwizzleSource::R:
+ return VK_COMPONENT_SWIZZLE_R;
+ case SwizzleSource::G:
+ return VK_COMPONENT_SWIZZLE_G;
+ case SwizzleSource::B:
+ return VK_COMPONENT_SWIZZLE_B;
+ case SwizzleSource::A:
+ return VK_COMPONENT_SWIZZLE_A;
+ case SwizzleSource::OneFloat:
+ case SwizzleSource::OneInt:
+ return VK_COMPONENT_SWIZZLE_ONE;
}
+ UNREACHABLE_MSG("Invalid swizzle={}", swizzle);
+ return VK_COMPONENT_SWIZZLE_ZERO;
}
-void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) {
- UNIMPLEMENTED_IF(params.IsBuffer());
-
- if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5_UNORM) {
- LOG_WARNING(Render_Vulkan, "A1B5G5R5 flushing is stubbed");
+[[nodiscard]] VkImageViewType ImageViewType(VideoCommon::ImageViewType type) {
+ switch (type) {
+ case VideoCommon::ImageViewType::e1D:
+ return VK_IMAGE_VIEW_TYPE_1D;
+ case VideoCommon::ImageViewType::e2D:
+ return VK_IMAGE_VIEW_TYPE_2D;
+ case VideoCommon::ImageViewType::Cube:
+ return VK_IMAGE_VIEW_TYPE_CUBE;
+ case VideoCommon::ImageViewType::e3D:
+ return VK_IMAGE_VIEW_TYPE_3D;
+ case VideoCommon::ImageViewType::e1DArray:
+ return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
+ case VideoCommon::ImageViewType::e2DArray:
+ return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+ case VideoCommon::ImageViewType::CubeArray:
+ return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
+ case VideoCommon::ImageViewType::Rect:
+ LOG_WARNING(Render_Vulkan, "Unnormalized image view type not supported");
+ return VK_IMAGE_VIEW_TYPE_2D;
+ case VideoCommon::ImageViewType::Buffer:
+ UNREACHABLE_MSG("Texture buffers can't be image views");
+ return VK_IMAGE_VIEW_TYPE_1D;
}
+ UNREACHABLE_MSG("Invalid image view type={}", type);
+ return VK_IMAGE_VIEW_TYPE_2D;
+}
- // We can't copy images to buffers inside a renderpass
- scheduler.RequestOutsideRenderPassOperationContext();
+[[nodiscard]] VkImageSubresourceLayers MakeImageSubresourceLayers(
+ VideoCommon::SubresourceLayers subresource, VkImageAspectFlags aspect_mask) {
+ return VkImageSubresourceLayers{
+ .aspectMask = aspect_mask,
+ .mipLevel = static_cast<u32>(subresource.base_level),
+ .baseArrayLayer = static_cast<u32>(subresource.base_layer),
+ .layerCount = static_cast<u32>(subresource.num_layers),
+ };
+}
- FullTransition(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT,
- VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+[[nodiscard]] VkOffset3D MakeOffset3D(VideoCommon::Offset3D offset3d) {
+ return VkOffset3D{
+ .x = offset3d.x,
+ .y = offset3d.y,
+ .z = offset3d.z,
+ };
+}
- const auto& buffer = staging_pool.GetUnusedBuffer(host_memory_size, true);
- // TODO(Rodrigo): Do this in a single copy
- for (u32 level = 0; level < params.num_levels; ++level) {
- scheduler.Record([image = *image->GetHandle(), buffer = *buffer.handle,
- copy = GetBufferImageCopy(level)](vk::CommandBuffer cmdbuf) {
- cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, copy);
- });
- }
- scheduler.Finish();
+[[nodiscard]] VkExtent3D MakeExtent3D(VideoCommon::Extent3D extent3d) {
+ return VkExtent3D{
+ .width = static_cast<u32>(extent3d.width),
+ .height = static_cast<u32>(extent3d.height),
+ .depth = static_cast<u32>(extent3d.depth),
+ };
+}
- // TODO(Rodrigo): Use an intern buffer for staging buffers and avoid this unnecessary memcpy.
- std::memcpy(staging_buffer.data(), buffer.commit->Map(host_memory_size), host_memory_size);
+[[nodiscard]] VkImageCopy MakeImageCopy(const VideoCommon::ImageCopy& copy,
+ VkImageAspectFlags aspect_mask) noexcept {
+ return VkImageCopy{
+ .srcSubresource = MakeImageSubresourceLayers(copy.src_subresource, aspect_mask),
+ .srcOffset = MakeOffset3D(copy.src_offset),
+ .dstSubresource = MakeImageSubresourceLayers(copy.dst_subresource, aspect_mask),
+ .dstOffset = MakeOffset3D(copy.dst_offset),
+ .extent = MakeExtent3D(copy.extent),
+ };
}
-void CachedSurface::DecorateSurfaceName() {
- // TODO(Rodrigo): Add name decorations
+[[nodiscard]] std::vector<VkBufferCopy> TransformBufferCopies(
+ std::span<const VideoCommon::BufferCopy> copies, size_t buffer_offset) {
+ std::vector<VkBufferCopy> result(copies.size());
+ std::ranges::transform(
+ copies, result.begin(), [buffer_offset](const VideoCommon::BufferCopy& copy) {
+ return VkBufferCopy{
+ .srcOffset = static_cast<VkDeviceSize>(copy.src_offset + buffer_offset),
+ .dstOffset = static_cast<VkDeviceSize>(copy.dst_offset),
+ .size = static_cast<VkDeviceSize>(copy.size),
+ };
+ });
+ return result;
}
-View CachedSurface::CreateView(const ViewParams& params) {
- // TODO(Rodrigo): Add name decorations
- return views[params] = std::make_shared<CachedSurfaceView>(device, *this, params);
+[[nodiscard]] std::vector<VkBufferImageCopy> TransformBufferImageCopies(
+ std::span<const BufferImageCopy> copies, size_t buffer_offset, VkImageAspectFlags aspect_mask) {
+ struct Maker {
+ VkBufferImageCopy operator()(const BufferImageCopy& copy) const {
+ return VkBufferImageCopy{
+ .bufferOffset = copy.buffer_offset + buffer_offset,
+ .bufferRowLength = copy.buffer_row_length,
+ .bufferImageHeight = copy.buffer_image_height,
+ .imageSubresource =
+ {
+ .aspectMask = aspect_mask,
+ .mipLevel = static_cast<u32>(copy.image_subresource.base_level),
+ .baseArrayLayer = static_cast<u32>(copy.image_subresource.base_layer),
+ .layerCount = static_cast<u32>(copy.image_subresource.num_layers),
+ },
+ .imageOffset =
+ {
+ .x = copy.image_offset.x,
+ .y = copy.image_offset.y,
+ .z = copy.image_offset.z,
+ },
+ .imageExtent =
+ {
+ .width = copy.image_extent.width,
+ .height = copy.image_extent.height,
+ .depth = copy.image_extent.depth,
+ },
+ };
+ }
+ size_t buffer_offset;
+ VkImageAspectFlags aspect_mask;
+ };
+ if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ std::vector<VkBufferImageCopy> result(copies.size() * 2);
+ std::ranges::transform(copies, result.begin(),
+ Maker{buffer_offset, VK_IMAGE_ASPECT_DEPTH_BIT});
+ std::ranges::transform(copies, result.begin() + copies.size(),
+ Maker{buffer_offset, VK_IMAGE_ASPECT_STENCIL_BIT});
+ return result;
+ } else {
+ std::vector<VkBufferImageCopy> result(copies.size());
+ std::ranges::transform(copies, result.begin(), Maker{buffer_offset, aspect_mask});
+ return result;
+ }
}
-void CachedSurface::UploadBuffer(const std::vector<u8>& staging_buffer) {
- const auto& src_buffer = staging_pool.GetUnusedBuffer(host_memory_size, true);
- std::memcpy(src_buffer.commit->Map(host_memory_size), staging_buffer.data(), host_memory_size);
+[[nodiscard]] VkImageSubresourceRange MakeSubresourceRange(VkImageAspectFlags aspect_mask,
+ const SubresourceRange& range) {
+ return VkImageSubresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = static_cast<u32>(range.base.level),
+ .levelCount = static_cast<u32>(range.extent.levels),
+ .baseArrayLayer = static_cast<u32>(range.base.layer),
+ .layerCount = static_cast<u32>(range.extent.layers),
+ };
+}
- scheduler.Record([src_buffer = *src_buffer.handle, dst_buffer = *buffer,
- size = host_memory_size](vk::CommandBuffer cmdbuf) {
- VkBufferCopy copy;
- copy.srcOffset = 0;
- copy.dstOffset = 0;
- copy.size = size;
- cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
+[[nodiscard]] VkImageSubresourceRange MakeSubresourceRange(const ImageView* image_view) {
+ SubresourceRange range = image_view->range;
+ if (True(image_view->flags & VideoCommon::ImageViewFlagBits::Slice)) {
+ // Slice image views always affect a single layer, but their subresource range corresponds
+ // to the slice. Override the value to affect a single layer.
+ range.base.layer = 0;
+ range.extent.layers = 1;
+ }
+ return MakeSubresourceRange(ImageAspectMask(image_view->format), range);
+}
- VkBufferMemoryBarrier barrier;
- barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
- barrier.pNext = nullptr;
- barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
- barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
- barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // They'll be ignored anyway
- barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
- barrier.buffer = dst_buffer;
- barrier.offset = 0;
- barrier.size = size;
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
- 0, {}, barrier, {});
- });
+[[nodiscard]] VkImageSubresourceLayers MakeSubresourceLayers(const ImageView* image_view) {
+ return VkImageSubresourceLayers{
+ .aspectMask = ImageAspectMask(image_view->format),
+ .mipLevel = static_cast<u32>(image_view->range.base.level),
+ .baseArrayLayer = static_cast<u32>(image_view->range.base.layer),
+ .layerCount = static_cast<u32>(image_view->range.extent.layers),
+ };
}
-void CachedSurface::UploadImage(const std::vector<u8>& staging_buffer) {
- const auto& src_buffer = staging_pool.GetUnusedBuffer(host_memory_size, true);
- std::memcpy(src_buffer.commit->Map(host_memory_size), staging_buffer.data(), host_memory_size);
-
- FullTransition(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
-
- for (u32 level = 0; level < params.num_levels; ++level) {
- const VkBufferImageCopy copy = GetBufferImageCopy(level);
- if (image->GetAspectMask() == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
- scheduler.Record([buffer = *src_buffer.handle, image = *image->GetHandle(),
- copy](vk::CommandBuffer cmdbuf) {
- std::array<VkBufferImageCopy, 2> copies = {copy, copy};
- copies[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
- copies[1].imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
- cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
- copies);
- });
- } else {
- scheduler.Record([buffer = *src_buffer.handle, image = *image->GetHandle(),
- copy](vk::CommandBuffer cmdbuf) {
- cmdbuf.CopyBufferToImage(buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
- });
- }
+[[nodiscard]] constexpr SwizzleSource ConvertGreenRed(SwizzleSource value) {
+ switch (value) {
+ case SwizzleSource::G:
+ return SwizzleSource::R;
+ default:
+ return value;
}
}
-VkBufferImageCopy CachedSurface::GetBufferImageCopy(u32 level) const {
- return {
- .bufferOffset = params.GetHostMipmapLevelOffset(level, is_converted),
- .bufferRowLength = 0,
- .bufferImageHeight = 0,
- .imageSubresource =
+void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage image,
+ VkImageAspectFlags aspect_mask, bool is_initialized,
+ std::span<const VkBufferImageCopy> copies) {
+ static constexpr VkAccessFlags WRITE_ACCESS_FLAGS =
+ VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+ static constexpr VkAccessFlags READ_ACCESS_FLAGS = VK_ACCESS_SHADER_READ_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
+ const VkImageMemoryBarrier read_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = WRITE_ACCESS_FLAGS,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = is_initialized ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image,
+ .subresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ };
+ const VkImageMemoryBarrier write_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = WRITE_ACCESS_FLAGS | READ_ACCESS_FLAGS,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image,
+ .subresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
+ read_barrier);
+ cmdbuf.CopyBufferToImage(src_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copies);
+ // TODO: Move this to another API
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0,
+ write_barrier);
+}
+
+[[nodiscard]] VkImageBlit MakeImageBlit(const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ const VkImageSubresourceLayers& dst_layers,
+ const VkImageSubresourceLayers& src_layers) {
+ return VkImageBlit{
+ .srcSubresource = src_layers,
+ .srcOffsets =
{
- .aspectMask = image->GetAspectMask(),
- .mipLevel = level,
- .baseArrayLayer = 0,
- .layerCount = static_cast<u32>(params.GetNumLayers()),
+ {
+ .x = src_region[0].x,
+ .y = src_region[0].y,
+ .z = 0,
+ },
+ {
+ .x = src_region[1].x,
+ .y = src_region[1].y,
+ .z = 1,
+ },
},
- .imageOffset = {.x = 0, .y = 0, .z = 0},
- .imageExtent =
+ .dstSubresource = dst_layers,
+ .dstOffsets =
{
- .width = params.GetMipWidth(level),
- .height = params.GetMipHeight(level),
- .depth = params.target == SurfaceTarget::Texture3D ? params.GetMipDepth(level) : 1U,
+ {
+ .x = dst_region[0].x,
+ .y = dst_region[0].y,
+ .z = 0,
+ },
+ {
+ .x = dst_region[1].x,
+ .y = dst_region[1].y,
+ .z = 1,
+ },
},
};
}
-VkImageSubresourceRange CachedSurface::GetImageSubresourceRange() const {
- return {image->GetAspectMask(), 0, params.num_levels, 0,
- static_cast<u32>(params.GetNumLayers())};
+[[nodiscard]] VkImageResolve MakeImageResolve(const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ const VkImageSubresourceLayers& dst_layers,
+ const VkImageSubresourceLayers& src_layers) {
+ return VkImageResolve{
+ .srcSubresource = src_layers,
+ .srcOffset =
+ {
+ .x = src_region[0].x,
+ .y = src_region[0].y,
+ .z = 0,
+ },
+ .dstSubresource = dst_layers,
+ .dstOffset =
+ {
+ .x = dst_region[0].x,
+ .y = dst_region[0].y,
+ .z = 0,
+ },
+ .extent =
+ {
+ .width = static_cast<u32>(dst_region[1].x - dst_region[0].x),
+ .height = static_cast<u32>(dst_region[1].y - dst_region[0].y),
+ .depth = 1,
+ },
+ };
}
-CachedSurfaceView::CachedSurfaceView(const VKDevice& device, CachedSurface& surface,
- const ViewParams& params)
- : VideoCommon::ViewBase{params}, params{surface.GetSurfaceParams()},
- image{surface.GetImageHandle()}, buffer_view{surface.GetBufferViewHandle()},
- aspect_mask{surface.GetAspectMask()}, device{device}, surface{surface},
- base_level{params.base_level}, num_levels{params.num_levels},
- image_view_type{image ? GetImageViewType(params.target) : VK_IMAGE_VIEW_TYPE_1D} {
- if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) {
- base_layer = 0;
- num_layers = 1;
- base_slice = params.base_layer;
- num_slices = params.num_layers;
- } else {
- base_layer = params.base_layer;
- num_layers = params.num_layers;
+struct RangedBarrierRange {
+ u32 min_mip = std::numeric_limits<u32>::max();
+ u32 max_mip = std::numeric_limits<u32>::min();
+ u32 min_layer = std::numeric_limits<u32>::max();
+ u32 max_layer = std::numeric_limits<u32>::min();
+
+ void AddLayers(const VkImageSubresourceLayers& layers) {
+ min_mip = std::min(min_mip, layers.mipLevel);
+ max_mip = std::max(max_mip, layers.mipLevel + 1);
+ min_layer = std::min(min_layer, layers.baseArrayLayer);
+ max_layer = std::max(max_layer, layers.baseArrayLayer + layers.layerCount);
+ }
+
+ VkImageSubresourceRange SubresourceRange(VkImageAspectFlags aspect_mask) const noexcept {
+ return VkImageSubresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = min_mip,
+ .levelCount = max_mip - min_mip,
+ .baseArrayLayer = min_layer,
+ .layerCount = max_layer - min_layer,
+ };
}
+};
+
+} // Anonymous namespace
+
+void TextureCacheRuntime::Finish() {
+ scheduler.Finish();
}
-CachedSurfaceView::~CachedSurfaceView() = default;
+StagingBufferRef TextureCacheRuntime::UploadStagingBuffer(size_t size) {
+ return staging_buffer_pool.Request(size, MemoryUsage::Upload);
+}
-VkImageView CachedSurfaceView::GetImageView(SwizzleSource x_source, SwizzleSource y_source,
- SwizzleSource z_source, SwizzleSource w_source) {
- const u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source);
- if (last_image_view && last_swizzle == new_swizzle) {
- return last_image_view;
- }
- last_swizzle = new_swizzle;
+StagingBufferRef TextureCacheRuntime::DownloadStagingBuffer(size_t size) {
+ return staging_buffer_pool.Request(size, MemoryUsage::Download);
+}
- const auto [entry, is_cache_miss] = view_cache.try_emplace(new_swizzle);
- auto& image_view = entry->second;
- if (!is_cache_miss) {
- return last_image_view = *image_view;
+void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src,
+ const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ Tegra::Engines::Fermi2D::Filter filter,
+ Tegra::Engines::Fermi2D::Operation operation) {
+ const VkImageAspectFlags aspect_mask = ImageAspectMask(src.format);
+ const bool is_dst_msaa = dst.Samples() != VK_SAMPLE_COUNT_1_BIT;
+ const bool is_src_msaa = src.Samples() != VK_SAMPLE_COUNT_1_BIT;
+ ASSERT(aspect_mask == ImageAspectMask(dst.format));
+ if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT && !is_src_msaa && !is_dst_msaa) {
+ blit_image_helper.BlitColor(dst_framebuffer, src, dst_region, src_region, filter,
+ operation);
+ return;
}
-
- std::array swizzle{MaxwellToVK::SwizzleSource(x_source), MaxwellToVK::SwizzleSource(y_source),
- MaxwellToVK::SwizzleSource(z_source), MaxwellToVK::SwizzleSource(w_source)};
- if (params.pixel_format == VideoCore::Surface::PixelFormat::A1B5G5R5_UNORM) {
- // A1B5G5R5 is implemented as A1R5G5B5, we have to change the swizzle here.
- std::swap(swizzle[0], swizzle[2]);
+ if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
+ if (!device.IsBlitDepthStencilSupported()) {
+ UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
+ blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(),
+ dst_region, src_region, filter, operation);
+ return;
+ }
}
-
- // Games can sample depth or stencil values on textures. This is decided by the swizzle value on
- // hardware. To emulate this on Vulkan we specify it in the aspect.
- VkImageAspectFlags aspect = aspect_mask;
- if (aspect == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
- UNIMPLEMENTED_IF(x_source != SwizzleSource::R && x_source != SwizzleSource::G);
- const bool is_first = x_source == SwizzleSource::R;
- switch (params.pixel_format) {
- case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
- case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
- aspect = is_first ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_STENCIL_BIT;
- break;
- case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
- aspect = is_first ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
- break;
- default:
- aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
- UNIMPLEMENTED();
+ ASSERT(src.ImageFormat() == dst.ImageFormat());
+ ASSERT(!(is_dst_msaa && !is_src_msaa));
+ ASSERT(operation == Fermi2D::Operation::SrcCopy);
+
+ const VkImage dst_image = dst.ImageHandle();
+ const VkImage src_image = src.ImageHandle();
+ const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers(&dst);
+ const VkImageSubresourceLayers src_layers = MakeSubresourceLayers(&src);
+ const bool is_resolve = is_src_msaa && !is_dst_msaa;
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([filter, dst_region, src_region, dst_image, src_image, dst_layers, src_layers,
+ aspect_mask, is_resolve](vk::CommandBuffer cmdbuf) {
+ const std::array read_barriers{
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = src_image,
+ .subresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ },
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = dst_image,
+ .subresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ },
+ };
+ VkImageMemoryBarrier write_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = dst_image,
+ .subresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, nullptr, nullptr, read_barriers);
+ if (is_resolve) {
+ cmdbuf.ResolveImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ MakeImageResolve(dst_region, src_region, dst_layers, src_layers));
+ } else {
+ const bool is_linear = filter == Fermi2D::Filter::Bilinear;
+ const VkFilter vk_filter = is_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
+ cmdbuf.BlitImage(
+ src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ MakeImageBlit(dst_region, src_region, dst_layers, src_layers), vk_filter);
}
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ 0, write_barrier);
+ });
+}
- // Make sure we sample the first component
- std::transform(
- swizzle.begin(), swizzle.end(), swizzle.begin(), [](VkComponentSwizzle component) {
- return component == VK_COMPONENT_SWIZZLE_G ? VK_COMPONENT_SWIZZLE_R : component;
- });
+void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view) {
+ switch (dst_view.format) {
+ case PixelFormat::R16_UNORM:
+ if (src_view.format == PixelFormat::D16_UNORM) {
+ return blit_image_helper.ConvertD16ToR16(dst, src_view);
+ }
+ break;
+ case PixelFormat::R32_FLOAT:
+ if (src_view.format == PixelFormat::D32_FLOAT) {
+ return blit_image_helper.ConvertD32ToR32(dst, src_view);
+ }
+ break;
+ case PixelFormat::D16_UNORM:
+ if (src_view.format == PixelFormat::R16_UNORM) {
+ return blit_image_helper.ConvertR16ToD16(dst, src_view);
+ }
+ break;
+ case PixelFormat::D32_FLOAT:
+ if (src_view.format == PixelFormat::R32_FLOAT) {
+ return blit_image_helper.ConvertR32ToD32(dst, src_view);
+ }
+ break;
+ default:
+ break;
}
+ UNIMPLEMENTED_MSG("Unimplemented format copy from {} to {}", src_view.format, dst_view.format);
+}
- if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) {
- ASSERT(base_slice == 0);
- ASSERT(num_slices == params.depth);
- }
+void TextureCacheRuntime::CopyImage(Image& dst, Image& src,
+ std::span<const VideoCommon::ImageCopy> copies) {
+ std::vector<VkImageCopy> vk_copies(copies.size());
+ const VkImageAspectFlags aspect_mask = dst.AspectMask();
+ ASSERT(aspect_mask == src.AspectMask());
- image_view = device.GetLogical().CreateImageView({
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .image = surface.GetImageHandle(),
- .viewType = image_view_type,
- .format = surface.GetImage().GetFormat(),
- .components =
- {
- .r = swizzle[0],
- .g = swizzle[1],
- .b = swizzle[2],
- .a = swizzle[3],
+ std::ranges::transform(copies, vk_copies.begin(), [aspect_mask](const auto& copy) {
+ return MakeImageCopy(copy, aspect_mask);
+ });
+ const VkImage dst_image = dst.Handle();
+ const VkImage src_image = src.Handle();
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([dst_image, src_image, aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) {
+ RangedBarrierRange dst_range;
+ RangedBarrierRange src_range;
+ for (const VkImageCopy& copy : vk_copies) {
+ dst_range.AddLayers(copy.dstSubresource);
+ src_range.AddLayers(copy.srcSubresource);
+ }
+ const std::array read_barriers{
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = src_image,
+ .subresourceRange = src_range.SubresourceRange(aspect_mask),
},
- .subresourceRange =
- {
- .aspectMask = aspect,
- .baseMipLevel = base_level,
- .levelCount = num_levels,
- .baseArrayLayer = base_layer,
- .layerCount = num_layers,
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = dst_image,
+ .subresourceRange = dst_range.SubresourceRange(aspect_mask),
},
+ };
+ const VkImageMemoryBarrier write_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = dst_image,
+ .subresourceRange = dst_range.SubresourceRange(aspect_mask),
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, {}, {}, read_barriers);
+ cmdbuf.CopyImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk_copies);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ 0, write_barrier);
});
-
- return last_image_view = *image_view;
}
-VkImageView CachedSurfaceView::GetAttachment() {
- if (render_target) {
- return *render_target;
+Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_addr_,
+ VAddr cpu_addr_)
+ : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime.scheduler},
+ image(MakeImage(runtime.device, info)), buffer(MakeBuffer(runtime.device, info)),
+ aspect_mask(ImageAspectMask(info.format)) {
+ if (image) {
+ commit = runtime.memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
+ } else {
+ commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
+ }
+ if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) {
+ flags |= VideoCommon::ImageFlagBits::Converted;
+ }
+ if (runtime.device.HasDebuggingToolAttached()) {
+ if (image) {
+ image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
+ } else {
+ buffer.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
+ }
}
+}
- VkImageViewCreateInfo ci{
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .image = surface.GetImageHandle(),
- .viewType = VK_IMAGE_VIEW_TYPE_1D,
- .format = surface.GetImage().GetFormat(),
- .components =
- {
- .r = VK_COMPONENT_SWIZZLE_IDENTITY,
- .g = VK_COMPONENT_SWIZZLE_IDENTITY,
- .b = VK_COMPONENT_SWIZZLE_IDENTITY,
- .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
+ // TODO: Move this to another API
+ scheduler->RequestOutsideRenderPassOperationContext();
+ std::vector vk_copies = TransformBufferImageCopies(copies, map.offset, aspect_mask);
+ const VkBuffer src_buffer = map.buffer;
+ const VkImage vk_image = *image;
+ const VkImageAspectFlags vk_aspect_mask = aspect_mask;
+ const bool is_initialized = std::exchange(initialized, true);
+ scheduler->Record([src_buffer, vk_image, vk_aspect_mask, is_initialized,
+ vk_copies](vk::CommandBuffer cmdbuf) {
+ CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, is_initialized, vk_copies);
+ });
+}
+
+void Image::UploadMemory(const StagingBufferRef& map,
+ std::span<const VideoCommon::BufferCopy> copies) {
+ // TODO: Move this to another API
+ scheduler->RequestOutsideRenderPassOperationContext();
+ std::vector vk_copies = TransformBufferCopies(copies, map.offset);
+ const VkBuffer src_buffer = map.buffer;
+ const VkBuffer dst_buffer = *buffer;
+ scheduler->Record([src_buffer, dst_buffer, vk_copies](vk::CommandBuffer cmdbuf) {
+ // TODO: Barriers
+ cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies);
+ });
+}
+
+void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
+ std::vector vk_copies = TransformBufferImageCopies(copies, map.offset, aspect_mask);
+ scheduler->Record([buffer = map.buffer, image = *image, aspect_mask = aspect_mask,
+ vk_copies](vk::CommandBuffer cmdbuf) {
+ const VkImageMemoryBarrier read_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image,
+ .subresourceRange{
+ .aspectMask = aspect_mask,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
},
- .subresourceRange =
- {
+ };
+ const VkImageMemoryBarrier image_write_barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image,
+ .subresourceRange{
.aspectMask = aspect_mask,
- .baseMipLevel = base_level,
- .levelCount = num_levels,
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
- .layerCount = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
},
+ };
+ const VkMemoryBarrier memory_write_barrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+ };
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, read_barrier);
+ cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, vk_copies);
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ 0, memory_write_barrier, nullptr, image_write_barrier);
+ });
+}
+
+ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info,
+ ImageId image_id_, Image& image)
+ : VideoCommon::ImageViewBase{info, image.info, image_id_}, device{&runtime.device},
+ image_handle{image.Handle()}, image_format{image.info.format}, samples{ConvertSampleCount(
+ image.info.num_samples)} {
+ const VkImageAspectFlags aspect_mask = ImageViewAspectMask(info);
+ std::array<SwizzleSource, 4> swizzle{
+ SwizzleSource::R,
+ SwizzleSource::G,
+ SwizzleSource::B,
+ SwizzleSource::A,
};
- if (image_view_type == VK_IMAGE_VIEW_TYPE_3D) {
- ci.viewType = num_slices > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
- ci.subresourceRange.baseArrayLayer = base_slice;
- ci.subresourceRange.layerCount = num_slices;
- } else {
- ci.viewType = image_view_type;
- ci.subresourceRange.baseArrayLayer = base_layer;
- ci.subresourceRange.layerCount = num_layers;
+ if (!info.IsRenderTarget()) {
+ swizzle = info.Swizzle();
+ if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
+ std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
+ }
+ }
+ const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
+ const VkFormat vk_format = format_info.format;
+ const VkImageViewUsageCreateInfo image_view_usage{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .usage = ImageUsageFlags(format_info, format),
+ };
+ const VkImageViewCreateInfo create_info{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = &image_view_usage,
+ .flags = 0,
+ .image = image.Handle(),
+ .viewType = VkImageViewType{},
+ .format = vk_format,
+ .components{
+ .r = ComponentSwizzle(swizzle[0]),
+ .g = ComponentSwizzle(swizzle[1]),
+ .b = ComponentSwizzle(swizzle[2]),
+ .a = ComponentSwizzle(swizzle[3]),
+ },
+ .subresourceRange = MakeSubresourceRange(aspect_mask, info.range),
+ };
+ const auto create = [&](VideoCommon::ImageViewType view_type, std::optional<u32> num_layers) {
+ VkImageViewCreateInfo ci{create_info};
+ ci.viewType = ImageViewType(view_type);
+ if (num_layers) {
+ ci.subresourceRange.layerCount = *num_layers;
+ }
+ vk::ImageView handle = device->GetLogical().CreateImageView(ci);
+ if (device->HasDebuggingToolAttached()) {
+ handle.SetObjectNameEXT(VideoCommon::Name(*this, view_type).c_str());
+ }
+ image_views[static_cast<size_t>(view_type)] = std::move(handle);
+ };
+ switch (info.type) {
+ case VideoCommon::ImageViewType::e1D:
+ case VideoCommon::ImageViewType::e1DArray:
+ create(VideoCommon::ImageViewType::e1D, 1);
+ create(VideoCommon::ImageViewType::e1DArray, std::nullopt);
+ render_target = Handle(VideoCommon::ImageViewType::e1DArray);
+ break;
+ case VideoCommon::ImageViewType::e2D:
+ case VideoCommon::ImageViewType::e2DArray:
+ create(VideoCommon::ImageViewType::e2D, 1);
+ create(VideoCommon::ImageViewType::e2DArray, std::nullopt);
+ render_target = Handle(VideoCommon::ImageViewType::e2DArray);
+ break;
+ case VideoCommon::ImageViewType::e3D:
+ create(VideoCommon::ImageViewType::e3D, std::nullopt);
+ render_target = Handle(VideoCommon::ImageViewType::e3D);
+ break;
+ case VideoCommon::ImageViewType::Cube:
+ case VideoCommon::ImageViewType::CubeArray:
+ create(VideoCommon::ImageViewType::Cube, 6);
+ create(VideoCommon::ImageViewType::CubeArray, std::nullopt);
+ break;
+ case VideoCommon::ImageViewType::Rect:
+ UNIMPLEMENTED();
+ break;
+ case VideoCommon::ImageViewType::Buffer:
+ buffer_view = device->GetLogical().CreateBufferView(VkBufferViewCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .buffer = image.Buffer(),
+ .format = vk_format,
+ .offset = 0, // TODO: Redesign buffer cache to support this
+ .range = image.guest_size_bytes,
+ });
+ break;
}
- render_target = device.GetLogical().CreateImageView(ci);
- return *render_target;
}
-VKTextureCache::VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKResourceManager& resource_manager,
- VKMemoryManager& memory_manager, VKScheduler& scheduler,
- VKStagingBufferPool& staging_pool)
- : TextureCache(system, rasterizer, device.IsOptimalAstcSupported()), device{device},
- resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler},
- staging_pool{staging_pool} {}
-
-VKTextureCache::~VKTextureCache() = default;
+ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams& params)
+ : VideoCommon::ImageViewBase{params} {}
-Surface VKTextureCache::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) {
- return std::make_shared<CachedSurface>(system, device, resource_manager, memory_manager,
- scheduler, staging_pool, gpu_addr, params);
+VkImageView ImageView::DepthView() {
+ if (depth_view) {
+ return *depth_view;
+ }
+ depth_view = MakeDepthStencilView(VK_IMAGE_ASPECT_DEPTH_BIT);
+ return *depth_view;
}
-void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface,
- const VideoCommon::CopyParams& copy_params) {
- const bool src_3d = src_surface->GetSurfaceParams().target == SurfaceTarget::Texture3D;
- const bool dst_3d = dst_surface->GetSurfaceParams().target == SurfaceTarget::Texture3D;
- UNIMPLEMENTED_IF(src_3d);
-
- // The texture cache handles depth in OpenGL terms, we have to handle it as subresource and
- // dimension respectively.
- const u32 dst_base_layer = dst_3d ? 0 : copy_params.dest_z;
- const u32 dst_offset_z = dst_3d ? copy_params.dest_z : 0;
-
- const u32 extent_z = dst_3d ? copy_params.depth : 1;
- const u32 num_layers = dst_3d ? 1 : copy_params.depth;
-
- // We can't copy inside a renderpass
- scheduler.RequestOutsideRenderPassOperationContext();
-
- src_surface->Transition(copy_params.source_z, copy_params.depth, copy_params.source_level, 1,
- VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT,
- VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
- dst_surface->Transition(dst_base_layer, num_layers, copy_params.dest_level, 1,
- VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
-
- const VkImageCopy copy{
- .srcSubresource =
- {
- .aspectMask = src_surface->GetAspectMask(),
- .mipLevel = copy_params.source_level,
- .baseArrayLayer = copy_params.source_z,
- .layerCount = num_layers,
- },
- .srcOffset =
- {
- .x = static_cast<s32>(copy_params.source_x),
- .y = static_cast<s32>(copy_params.source_y),
- .z = 0,
- },
- .dstSubresource =
- {
- .aspectMask = dst_surface->GetAspectMask(),
- .mipLevel = copy_params.dest_level,
- .baseArrayLayer = dst_base_layer,
- .layerCount = num_layers,
- },
- .dstOffset =
- {
- .x = static_cast<s32>(copy_params.dest_x),
- .y = static_cast<s32>(copy_params.dest_y),
- .z = static_cast<s32>(dst_offset_z),
- },
- .extent =
- {
- .width = copy_params.width,
- .height = copy_params.height,
- .depth = extent_z,
- },
- };
+VkImageView ImageView::StencilView() {
+ if (stencil_view) {
+ return *stencil_view;
+ }
+ stencil_view = MakeDepthStencilView(VK_IMAGE_ASPECT_STENCIL_BIT);
+ return *stencil_view;
+}
- const VkImage src_image = src_surface->GetImageHandle();
- const VkImage dst_image = dst_surface->GetImageHandle();
- scheduler.Record([src_image, dst_image, copy](vk::CommandBuffer cmdbuf) {
- cmdbuf.CopyImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image,
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
+vk::ImageView ImageView::MakeDepthStencilView(VkImageAspectFlags aspect_mask) {
+ return device->GetLogical().CreateImageView({
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = image_handle,
+ .viewType = ImageViewType(type),
+ .format = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format).format,
+ .components{
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange = MakeSubresourceRange(aspect_mask, range),
});
}
-void VKTextureCache::ImageBlit(View& src_view, View& dst_view,
- const Tegra::Engines::Fermi2D::Config& copy_config) {
- // We can't blit inside a renderpass
- scheduler.RequestOutsideRenderPassOperationContext();
-
- src_view->Transition(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT,
- VK_ACCESS_TRANSFER_READ_BIT);
- dst_view->Transition(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT,
- VK_ACCESS_TRANSFER_WRITE_BIT);
-
- VkImageBlit blit;
- blit.srcSubresource = src_view->GetImageSubresourceLayers();
- blit.srcOffsets[0].x = copy_config.src_rect.left;
- blit.srcOffsets[0].y = copy_config.src_rect.top;
- blit.srcOffsets[0].z = 0;
- blit.srcOffsets[1].x = copy_config.src_rect.right;
- blit.srcOffsets[1].y = copy_config.src_rect.bottom;
- blit.srcOffsets[1].z = 1;
- blit.dstSubresource = dst_view->GetImageSubresourceLayers();
- blit.dstOffsets[0].x = copy_config.dst_rect.left;
- blit.dstOffsets[0].y = copy_config.dst_rect.top;
- blit.dstOffsets[0].z = 0;
- blit.dstOffsets[1].x = copy_config.dst_rect.right;
- blit.dstOffsets[1].y = copy_config.dst_rect.bottom;
- blit.dstOffsets[1].z = 1;
-
- const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear;
-
- scheduler.Record([src_image = src_view->GetImage(), dst_image = dst_view->GetImage(), blit,
- is_linear](vk::CommandBuffer cmdbuf) {
- cmdbuf.BlitImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image,
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, blit,
- is_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST);
+Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& tsc) {
+ const auto& device = runtime.device;
+ const bool arbitrary_borders = runtime.device.IsExtCustomBorderColorSupported();
+ const std::array<float, 4> color = tsc.BorderColor();
+ // C++20 bit_cast
+ VkClearColorValue border_color;
+ std::memcpy(&border_color, &color, sizeof(color));
+ const VkSamplerCustomBorderColorCreateInfoEXT border_ci{
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .customBorderColor = border_color,
+ .format = VK_FORMAT_UNDEFINED,
+ };
+ const void* pnext = nullptr;
+ if (arbitrary_borders) {
+ pnext = &border_ci;
+ }
+ const VkSamplerReductionModeCreateInfoEXT reduction_ci{
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT,
+ .pNext = pnext,
+ .reductionMode = MaxwellToVK::SamplerReduction(tsc.reduction_filter),
+ };
+ if (runtime.device.IsExtSamplerFilterMinmaxSupported()) {
+ pnext = &reduction_ci;
+ } else if (reduction_ci.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) {
+ LOG_WARNING(Render_Vulkan, "VK_EXT_sampler_filter_minmax is required");
+ }
+ // Some games have samplers with garbage. Sanitize them here.
+ const float max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f);
+ sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+ .pNext = pnext,
+ .flags = 0,
+ .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter),
+ .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter),
+ .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
+ .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter),
+ .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter),
+ .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter),
+ .mipLodBias = tsc.LodBias(),
+ .anisotropyEnable = static_cast<VkBool32>(max_anisotropy > 1.0f ? VK_TRUE : VK_FALSE),
+ .maxAnisotropy = max_anisotropy,
+ .compareEnable = tsc.depth_compare_enabled,
+ .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
+ .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(),
+ .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(),
+ .borderColor =
+ arbitrary_borders ? VK_BORDER_COLOR_INT_CUSTOM_EXT : ConvertBorderColor(color),
+ .unnormalizedCoordinates = VK_FALSE,
});
}
-void VKTextureCache::BufferCopy(Surface& src_surface, Surface& dst_surface) {
- // Currently unimplemented. PBO copies should be dropped and we should use a render pass to
- // convert from color to depth and viceversa.
- LOG_WARNING(Render_Vulkan, "Unimplemented");
+Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers,
+ ImageView* depth_buffer, const VideoCommon::RenderTargets& key) {
+ std::vector<VkAttachmentDescription> descriptions;
+ std::vector<VkImageView> attachments;
+ RenderPassKey renderpass_key{};
+ s32 num_layers = 1;
+
+ for (size_t index = 0; index < NUM_RT; ++index) {
+ const ImageView* const color_buffer = color_buffers[index];
+ if (!color_buffer) {
+ renderpass_key.color_formats[index] = PixelFormat::Invalid;
+ continue;
+ }
+ descriptions.push_back(AttachmentDescription(runtime.device, color_buffer));
+ attachments.push_back(color_buffer->RenderTarget());
+ renderpass_key.color_formats[index] = color_buffer->format;
+ num_layers = std::max(num_layers, color_buffer->range.extent.layers);
+ images[num_images] = color_buffer->ImageHandle();
+ image_ranges[num_images] = MakeSubresourceRange(color_buffer);
+ samples = color_buffer->Samples();
+ ++num_images;
+ }
+ const size_t num_colors = attachments.size();
+ const VkAttachmentReference* depth_attachment =
+ depth_buffer ? &ATTACHMENT_REFERENCES[num_colors] : nullptr;
+ if (depth_buffer) {
+ descriptions.push_back(AttachmentDescription(runtime.device, depth_buffer));
+ attachments.push_back(depth_buffer->RenderTarget());
+ renderpass_key.depth_format = depth_buffer->format;
+ num_layers = std::max(num_layers, depth_buffer->range.extent.layers);
+ images[num_images] = depth_buffer->ImageHandle();
+ image_ranges[num_images] = MakeSubresourceRange(depth_buffer);
+ samples = depth_buffer->Samples();
+ ++num_images;
+ } else {
+ renderpass_key.depth_format = PixelFormat::Invalid;
+ }
+ renderpass_key.samples = samples;
+
+ const auto& device = runtime.device.GetLogical();
+ const auto [cache_pair, is_new] = runtime.renderpass_cache.try_emplace(renderpass_key);
+ if (is_new) {
+ const VkSubpassDescription subpass{
+ .flags = 0,
+ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+ .inputAttachmentCount = 0,
+ .pInputAttachments = nullptr,
+ .colorAttachmentCount = static_cast<u32>(num_colors),
+ .pColorAttachments = num_colors != 0 ? ATTACHMENT_REFERENCES.data() : nullptr,
+ .pResolveAttachments = nullptr,
+ .pDepthStencilAttachment = depth_attachment,
+ .preserveAttachmentCount = 0,
+ .pPreserveAttachments = nullptr,
+ };
+ cache_pair->second = device.CreateRenderPass(VkRenderPassCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .attachmentCount = static_cast<u32>(descriptions.size()),
+ .pAttachments = descriptions.data(),
+ .subpassCount = 1,
+ .pSubpasses = &subpass,
+ .dependencyCount = 0,
+ .pDependencies = nullptr,
+ });
+ }
+ renderpass = *cache_pair->second;
+ render_area = VkExtent2D{
+ .width = key.size.width,
+ .height = key.size.height,
+ };
+ num_color_buffers = static_cast<u32>(num_colors);
+ framebuffer = device.CreateFramebuffer(VkFramebufferCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .renderPass = renderpass,
+ .attachmentCount = static_cast<u32>(attachments.size()),
+ .pAttachments = attachments.data(),
+ .width = key.size.width,
+ .height = key.size.height,
+ .layers = static_cast<u32>(std::max(num_layers, 1)),
+ });
+ if (runtime.device.HasDebuggingToolAttached()) {
+ framebuffer.SetObjectNameEXT(VideoCommon::Name(key).c_str());
+ }
}
} // 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 807e26c8a..b08c23459 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -4,225 +4,253 @@
#pragma once
-#include <memory>
-#include <unordered_map>
-
-#include "common/common_types.h"
-#include "video_core/renderer_vulkan/vk_image.h"
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_scheduler.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-#include "video_core/texture_cache/surface_base.h"
-#include "video_core/texture_cache/texture_cache.h"
-
-namespace Core {
-class System;
-}
+#include <compare>
+#include <span>
-namespace VideoCore {
-class RasterizerInterface;
-}
+#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
+#include "video_core/texture_cache/texture_cache.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-class RasterizerVulkan;
-class VKDevice;
-class VKResourceManager;
+using VideoCommon::ImageId;
+using VideoCommon::NUM_RT;
+using VideoCommon::Offset2D;
+using VideoCommon::RenderTargets;
+using VideoCore::Surface::PixelFormat;
+
+class BlitImageHelper;
+class Device;
+class Image;
+class ImageView;
+class Framebuffer;
+class StagingBufferPool;
class VKScheduler;
-class VKStagingBufferPool;
-class CachedSurfaceView;
-class CachedSurface;
+struct RenderPassKey {
+ constexpr auto operator<=>(const RenderPassKey&) const noexcept = default;
-using Surface = std::shared_ptr<CachedSurface>;
-using View = std::shared_ptr<CachedSurfaceView>;
-using TextureCacheBase = VideoCommon::TextureCache<Surface, View>;
+ std::array<PixelFormat, NUM_RT> color_formats;
+ PixelFormat depth_format;
+ VkSampleCountFlagBits samples;
+};
-using VideoCommon::SurfaceParams;
-using VideoCommon::ViewParams;
+} // namespace Vulkan
-class CachedSurface final : public VideoCommon::SurfaceBase<View> {
- friend CachedSurfaceView;
+namespace std {
+template <>
+struct hash<Vulkan::RenderPassKey> {
+ [[nodiscard]] constexpr size_t operator()(const Vulkan::RenderPassKey& key) const noexcept {
+ size_t value = static_cast<size_t>(key.depth_format) << 48;
+ value ^= static_cast<size_t>(key.samples) << 52;
+ for (size_t i = 0; i < key.color_formats.size(); ++i) {
+ value ^= static_cast<size_t>(key.color_formats[i]) << (i * 6);
+ }
+ return value;
+ }
+};
+} // namespace std
-public:
- explicit CachedSurface(Core::System& system, const VKDevice& device,
- VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
- VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
- GPUVAddr gpu_addr, const SurfaceParams& params);
- ~CachedSurface();
+namespace Vulkan {
- void UploadTexture(const std::vector<u8>& staging_buffer) override;
- void DownloadTexture(std::vector<u8>& staging_buffer) override;
+struct TextureCacheRuntime {
+ const Device& device;
+ VKScheduler& scheduler;
+ MemoryAllocator& memory_allocator;
+ StagingBufferPool& staging_buffer_pool;
+ BlitImageHelper& blit_image_helper;
+ std::unordered_map<RenderPassKey, vk::RenderPass> renderpass_cache{};
- void FullTransition(VkPipelineStageFlags new_stage_mask, VkAccessFlags new_access,
- VkImageLayout new_layout) {
- image->Transition(0, static_cast<u32>(params.GetNumLayers()), 0, params.num_levels,
- new_stage_mask, new_access, new_layout);
- }
+ void Finish();
- void Transition(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels,
- VkPipelineStageFlags new_stage_mask, VkAccessFlags new_access,
- VkImageLayout new_layout) {
- image->Transition(base_layer, num_layers, base_level, num_levels, new_stage_mask,
- new_access, new_layout);
- }
+ [[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
- VKImage& GetImage() {
- return *image;
- }
+ [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size);
- const VKImage& GetImage() const {
- return *image;
- }
+ void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src,
+ const std::array<Offset2D, 2>& dst_region,
+ const std::array<Offset2D, 2>& src_region,
+ Tegra::Engines::Fermi2D::Filter filter,
+ Tegra::Engines::Fermi2D::Operation operation);
+
+ void CopyImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
- VkImage GetImageHandle() const {
- return *image->GetHandle();
+ void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view);
+
+ [[nodiscard]] bool CanAccelerateImageUpload(Image&) const noexcept {
+ return false;
}
- VkImageAspectFlags GetAspectMask() const {
- return image->GetAspectMask();
+ void AccelerateImageUpload(Image&, const StagingBufferRef&,
+ std::span<const VideoCommon::SwizzleParameters>) {
+ UNREACHABLE();
}
- VkBufferView GetBufferViewHandle() const {
- return *buffer_view;
+ void InsertUploadMemoryBarrier() {}
+
+ bool HasBrokenTextureViewFormats() const noexcept {
+ // No known Vulkan driver has broken image views
+ return false;
}
+};
-protected:
- void DecorateSurfaceName();
+class Image : public VideoCommon::ImageBase {
+public:
+ explicit Image(TextureCacheRuntime&, const VideoCommon::ImageInfo& info, GPUVAddr gpu_addr,
+ VAddr cpu_addr);
- View CreateView(const ViewParams& params) override;
+ void UploadMemory(const StagingBufferRef& map,
+ std::span<const VideoCommon::BufferImageCopy> copies);
-private:
- void UploadBuffer(const std::vector<u8>& staging_buffer);
+ void UploadMemory(const StagingBufferRef& map, std::span<const VideoCommon::BufferCopy> copies);
- void UploadImage(const std::vector<u8>& staging_buffer);
+ void DownloadMemory(const StagingBufferRef& map,
+ std::span<const VideoCommon::BufferImageCopy> copies);
- VkBufferImageCopy GetBufferImageCopy(u32 level) const;
+ [[nodiscard]] VkImage Handle() const noexcept {
+ return *image;
+ }
- VkImageSubresourceRange GetImageSubresourceRange() const;
+ [[nodiscard]] VkBuffer Buffer() const noexcept {
+ return *buffer;
+ }
- Core::System& system;
- const VKDevice& device;
- VKResourceManager& resource_manager;
- VKMemoryManager& memory_manager;
- VKScheduler& scheduler;
- VKStagingBufferPool& staging_pool;
+ [[nodiscard]] VkImageCreateFlags AspectMask() const noexcept {
+ return aspect_mask;
+ }
- std::optional<VKImage> image;
+private:
+ VKScheduler* scheduler;
+ vk::Image image;
vk::Buffer buffer;
- vk::BufferView buffer_view;
- VKMemoryCommit commit;
-
- VkFormat format = VK_FORMAT_UNDEFINED;
+ MemoryCommit commit;
+ VkImageAspectFlags aspect_mask = 0;
+ bool initialized = false;
};
-class CachedSurfaceView final : public VideoCommon::ViewBase {
+class ImageView : public VideoCommon::ImageViewBase {
public:
- explicit CachedSurfaceView(const VKDevice& device, CachedSurface& surface,
- const ViewParams& params);
- ~CachedSurfaceView();
+ explicit ImageView(TextureCacheRuntime&, const VideoCommon::ImageViewInfo&, ImageId, Image&);
+ explicit ImageView(TextureCacheRuntime&, const VideoCommon::NullImageParams&);
- VkImageView GetImageView(Tegra::Texture::SwizzleSource x_source,
- Tegra::Texture::SwizzleSource y_source,
- Tegra::Texture::SwizzleSource z_source,
- Tegra::Texture::SwizzleSource w_source);
+ [[nodiscard]] VkImageView DepthView();
- VkImageView GetAttachment();
+ [[nodiscard]] VkImageView StencilView();
- bool IsSameSurface(const CachedSurfaceView& rhs) const {
- return &surface == &rhs.surface;
+ [[nodiscard]] VkImageView Handle(VideoCommon::ImageViewType query_type) const noexcept {
+ return *image_views[static_cast<size_t>(query_type)];
}
- u32 GetWidth() const {
- return params.GetMipWidth(base_level);
+ [[nodiscard]] VkBufferView BufferView() const noexcept {
+ return *buffer_view;
}
- u32 GetHeight() const {
- return params.GetMipHeight(base_level);
+ [[nodiscard]] VkImage ImageHandle() const noexcept {
+ return image_handle;
}
- u32 GetNumLayers() const {
- return num_layers;
+ [[nodiscard]] VkImageView RenderTarget() const noexcept {
+ return render_target;
}
- bool IsBufferView() const {
- return buffer_view;
+ [[nodiscard]] PixelFormat ImageFormat() const noexcept {
+ return image_format;
}
- VkImage GetImage() const {
- return image;
+ [[nodiscard]] VkSampleCountFlagBits Samples() const noexcept {
+ return samples;
}
- VkBufferView GetBufferView() const {
- return buffer_view;
- }
+private:
+ [[nodiscard]] vk::ImageView MakeDepthStencilView(VkImageAspectFlags aspect_mask);
- VkImageSubresourceRange GetImageSubresourceRange() const {
- return {aspect_mask, base_level, num_levels, base_layer, num_layers};
- }
+ const Device* device = nullptr;
+ std::array<vk::ImageView, VideoCommon::NUM_IMAGE_VIEW_TYPES> image_views;
+ vk::ImageView depth_view;
+ vk::ImageView stencil_view;
+ vk::BufferView buffer_view;
+ VkImage image_handle = VK_NULL_HANDLE;
+ VkImageView render_target = VK_NULL_HANDLE;
+ PixelFormat image_format = PixelFormat::Invalid;
+ VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
+};
- VkImageSubresourceLayers GetImageSubresourceLayers() const {
- return {surface.GetAspectMask(), base_level, base_layer, num_layers};
- }
+class ImageAlloc : public VideoCommon::ImageAllocBase {};
- void Transition(VkImageLayout new_layout, VkPipelineStageFlags new_stage_mask,
- VkAccessFlags new_access) const {
- surface.Transition(base_layer, num_layers, base_level, num_levels, new_stage_mask,
- new_access, new_layout);
- }
+class Sampler {
+public:
+ explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
- void MarkAsModified(u64 tick) {
- surface.MarkAsModified(true, tick);
+ [[nodiscard]] VkSampler Handle() const noexcept {
+ return *sampler;
}
private:
- // Store a copy of these values to avoid double dereference when reading them
- const SurfaceParams params;
- const VkImage image;
- const VkBufferView buffer_view;
- const VkImageAspectFlags aspect_mask;
-
- const VKDevice& device;
- CachedSurface& surface;
- const u32 base_level;
- const u32 num_levels;
- const VkImageViewType image_view_type;
- u32 base_layer = 0;
- u32 num_layers = 0;
- u32 base_slice = 0;
- u32 num_slices = 0;
-
- VkImageView last_image_view = nullptr;
- u32 last_swizzle = 0;
-
- vk::ImageView render_target;
- std::unordered_map<u32, vk::ImageView> view_cache;
+ vk::Sampler sampler;
};
-class VKTextureCache final : public TextureCacheBase {
+class Framebuffer {
public:
- explicit VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- const VKDevice& device, VKResourceManager& resource_manager,
- VKMemoryManager& memory_manager, VKScheduler& scheduler,
- VKStagingBufferPool& staging_pool);
- ~VKTextureCache();
+ explicit Framebuffer(TextureCacheRuntime&, std::span<ImageView*, NUM_RT> color_buffers,
+ ImageView* depth_buffer, const VideoCommon::RenderTargets& key);
-private:
- Surface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) override;
+ [[nodiscard]] VkFramebuffer Handle() const noexcept {
+ return *framebuffer;
+ }
- void ImageCopy(Surface& src_surface, Surface& dst_surface,
- const VideoCommon::CopyParams& copy_params) override;
+ [[nodiscard]] VkRenderPass RenderPass() const noexcept {
+ return renderpass;
+ }
- void ImageBlit(View& src_view, View& dst_view,
- const Tegra::Engines::Fermi2D::Config& copy_config) override;
+ [[nodiscard]] VkExtent2D RenderArea() const noexcept {
+ return render_area;
+ }
- void BufferCopy(Surface& src_surface, Surface& dst_surface) override;
+ [[nodiscard]] VkSampleCountFlagBits Samples() const noexcept {
+ return samples;
+ }
- const VKDevice& device;
- VKResourceManager& resource_manager;
- VKMemoryManager& memory_manager;
- VKScheduler& scheduler;
- VKStagingBufferPool& staging_pool;
+ [[nodiscard]] u32 NumColorBuffers() const noexcept {
+ return num_color_buffers;
+ }
+
+ [[nodiscard]] u32 NumImages() const noexcept {
+ return num_images;
+ }
+
+ [[nodiscard]] const std::array<VkImage, 9>& Images() const noexcept {
+ return images;
+ }
+
+ [[nodiscard]] const std::array<VkImageSubresourceRange, 9>& ImageRanges() const noexcept {
+ return image_ranges;
+ }
+
+private:
+ vk::Framebuffer framebuffer;
+ VkRenderPass renderpass{};
+ VkExtent2D render_area{};
+ VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
+ u32 num_color_buffers = 0;
+ u32 num_images = 0;
+ std::array<VkImage, 9> images{};
+ std::array<VkImageSubresourceRange, 9> image_ranges{};
};
+struct TextureCacheParams {
+ static constexpr bool ENABLE_VALIDATION = true;
+ static constexpr bool FRAMEBUFFER_BLITS = false;
+ static constexpr bool HAS_EMULATED_COPIES = false;
+
+ using Runtime = Vulkan::TextureCacheRuntime;
+ using Image = Vulkan::Image;
+ using ImageAlloc = Vulkan::ImageAlloc;
+ using ImageView = Vulkan::ImageView;
+ using Sampler = Vulkan::Sampler;
+ using Framebuffer = Vulkan::Framebuffer;
+};
+
+using TextureCache = VideoCommon::TextureCache<TextureCacheParams>;
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
index 351c048d2..f99273c6a 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
@@ -7,15 +7,15 @@
#include "common/assert.h"
#include "common/logging/log.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-VKUpdateDescriptorQueue::VKUpdateDescriptorQueue(const VKDevice& device, VKScheduler& scheduler)
- : device{device}, scheduler{scheduler} {}
+VKUpdateDescriptorQueue::VKUpdateDescriptorQueue(const Device& device_, VKScheduler& scheduler_)
+ : device{device_}, scheduler{scheduler_} {}
VKUpdateDescriptorQueue::~VKUpdateDescriptorQueue() = default;
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h
index 945320c72..e214f7195 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.h
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h
@@ -8,11 +8,11 @@
#include <boost/container/static_vector.hpp>
#include "common/common_types.h"
-#include "video_core/renderer_vulkan/wrapper.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
-class VKDevice;
+class Device;
class VKScheduler;
struct DescriptorUpdateEntry {
@@ -31,7 +31,7 @@ struct DescriptorUpdateEntry {
class VKUpdateDescriptorQueue final {
public:
- explicit VKUpdateDescriptorQueue(const VKDevice& device, VKScheduler& scheduler);
+ explicit VKUpdateDescriptorQueue(const Device& device_, VKScheduler& scheduler_);
~VKUpdateDescriptorQueue();
void TickFrame();
@@ -40,32 +40,36 @@ public:
void Send(VkDescriptorUpdateTemplateKHR update_template, VkDescriptorSet set);
- void AddSampledImage(VkSampler sampler, VkImageView image_view) {
- payload.emplace_back(VkDescriptorImageInfo{sampler, image_view, {}});
+ void AddSampledImage(VkImageView image_view, VkSampler sampler) {
+ payload.emplace_back(VkDescriptorImageInfo{
+ .sampler = sampler,
+ .imageView = image_view,
+ .imageLayout = VK_IMAGE_LAYOUT_GENERAL,
+ });
}
void AddImage(VkImageView image_view) {
- payload.emplace_back(VkDescriptorImageInfo{{}, image_view, {}});
+ payload.emplace_back(VkDescriptorImageInfo{
+ .sampler = VK_NULL_HANDLE,
+ .imageView = image_view,
+ .imageLayout = VK_IMAGE_LAYOUT_GENERAL,
+ });
}
- void AddBuffer(VkBuffer buffer, u64 offset, std::size_t size) {
- payload.emplace_back(VkDescriptorBufferInfo{buffer, offset, size});
+ void AddBuffer(VkBuffer buffer, u64 offset, size_t size) {
+ payload.emplace_back(VkDescriptorBufferInfo{
+ .buffer = buffer,
+ .offset = offset,
+ .range = size,
+ });
}
void AddTexelBuffer(VkBufferView texel_buffer) {
payload.emplace_back(texel_buffer);
}
- VkImageLayout* LastImageLayout() {
- return &payload.back().image.imageLayout;
- }
-
- const VkImageLayout* LastImageLayout() const {
- return &payload.back().image.imageLayout;
- }
-
private:
- const VKDevice& device;
+ const Device& device;
VKScheduler& scheduler;
const DescriptorUpdateEntry* upload_start = nullptr;
diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp
deleted file mode 100644
index 013865aa4..000000000
--- a/src/video_core/renderer_vulkan/wrapper.cpp
+++ /dev/null
@@ -1,805 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <exception>
-#include <memory>
-#include <optional>
-#include <utility>
-#include <vector>
-
-#include "common/common_types.h"
-
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan::vk {
-
-namespace {
-
-void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld) {
- std::stable_sort(devices.begin(), devices.end(), [&](auto lhs, auto rhs) {
- // This will call Vulkan more than needed, but these calls are cheap.
- const auto lhs_properties = vk::PhysicalDevice(lhs, dld).GetProperties();
- const auto rhs_properties = vk::PhysicalDevice(rhs, dld).GetProperties();
-
- // Prefer discrete GPUs, Nvidia over AMD, AMD over Intel, Intel over the rest.
- const bool preferred =
- (lhs_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
- rhs_properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) ||
- (lhs_properties.vendorID == 0x10DE && rhs_properties.vendorID != 0x10DE) ||
- (lhs_properties.vendorID == 0x1002 && rhs_properties.vendorID != 0x1002) ||
- (lhs_properties.vendorID == 0x8086 && rhs_properties.vendorID != 0x8086);
- return !preferred;
- });
-}
-
-template <typename T>
-bool Proc(T& result, const InstanceDispatch& dld, const char* proc_name,
- VkInstance instance = nullptr) noexcept {
- result = reinterpret_cast<T>(dld.vkGetInstanceProcAddr(instance, proc_name));
- return result != nullptr;
-}
-
-template <typename T>
-void Proc(T& result, const DeviceDispatch& dld, const char* proc_name, VkDevice device) noexcept {
- result = reinterpret_cast<T>(dld.vkGetDeviceProcAddr(device, proc_name));
-}
-
-void Load(VkDevice device, DeviceDispatch& dld) noexcept {
-#define X(name) Proc(dld.name, dld, #name, device)
- X(vkAcquireNextImageKHR);
- X(vkAllocateCommandBuffers);
- X(vkAllocateDescriptorSets);
- X(vkAllocateMemory);
- X(vkBeginCommandBuffer);
- X(vkBindBufferMemory);
- X(vkBindImageMemory);
- X(vkCmdBeginQuery);
- X(vkCmdBeginRenderPass);
- X(vkCmdBeginTransformFeedbackEXT);
- X(vkCmdBindDescriptorSets);
- X(vkCmdBindIndexBuffer);
- X(vkCmdBindPipeline);
- X(vkCmdBindTransformFeedbackBuffersEXT);
- X(vkCmdBindVertexBuffers);
- X(vkCmdBlitImage);
- X(vkCmdClearAttachments);
- X(vkCmdCopyBuffer);
- X(vkCmdCopyBufferToImage);
- X(vkCmdCopyImage);
- X(vkCmdCopyImageToBuffer);
- X(vkCmdDispatch);
- X(vkCmdDraw);
- X(vkCmdDrawIndexed);
- X(vkCmdEndQuery);
- X(vkCmdEndRenderPass);
- X(vkCmdEndTransformFeedbackEXT);
- X(vkCmdFillBuffer);
- X(vkCmdPipelineBarrier);
- X(vkCmdPushConstants);
- X(vkCmdSetBlendConstants);
- X(vkCmdSetDepthBias);
- X(vkCmdSetDepthBounds);
- X(vkCmdSetEvent);
- X(vkCmdSetScissor);
- X(vkCmdSetStencilCompareMask);
- X(vkCmdSetStencilReference);
- X(vkCmdSetStencilWriteMask);
- X(vkCmdSetViewport);
- X(vkCmdWaitEvents);
- X(vkCmdBindVertexBuffers2EXT);
- X(vkCmdSetCullModeEXT);
- X(vkCmdSetDepthBoundsTestEnableEXT);
- X(vkCmdSetDepthCompareOpEXT);
- X(vkCmdSetDepthTestEnableEXT);
- X(vkCmdSetDepthWriteEnableEXT);
- X(vkCmdSetFrontFaceEXT);
- X(vkCmdSetPrimitiveTopologyEXT);
- X(vkCmdSetStencilOpEXT);
- X(vkCmdSetStencilTestEnableEXT);
- X(vkCreateBuffer);
- X(vkCreateBufferView);
- X(vkCreateCommandPool);
- X(vkCreateComputePipelines);
- X(vkCreateDescriptorPool);
- X(vkCreateDescriptorSetLayout);
- X(vkCreateDescriptorUpdateTemplateKHR);
- X(vkCreateEvent);
- X(vkCreateFence);
- X(vkCreateFramebuffer);
- X(vkCreateGraphicsPipelines);
- X(vkCreateImage);
- X(vkCreateImageView);
- X(vkCreatePipelineLayout);
- X(vkCreateQueryPool);
- X(vkCreateRenderPass);
- X(vkCreateSampler);
- X(vkCreateSemaphore);
- X(vkCreateShaderModule);
- X(vkCreateSwapchainKHR);
- X(vkDestroyBuffer);
- X(vkDestroyBufferView);
- X(vkDestroyCommandPool);
- X(vkDestroyDescriptorPool);
- X(vkDestroyDescriptorSetLayout);
- X(vkDestroyDescriptorUpdateTemplateKHR);
- X(vkDestroyEvent);
- X(vkDestroyFence);
- X(vkDestroyFramebuffer);
- X(vkDestroyImage);
- X(vkDestroyImageView);
- X(vkDestroyPipeline);
- X(vkDestroyPipelineLayout);
- X(vkDestroyQueryPool);
- X(vkDestroyRenderPass);
- X(vkDestroySampler);
- X(vkDestroySemaphore);
- X(vkDestroyShaderModule);
- X(vkDestroySwapchainKHR);
- X(vkDeviceWaitIdle);
- X(vkEndCommandBuffer);
- X(vkFreeCommandBuffers);
- X(vkFreeDescriptorSets);
- X(vkFreeMemory);
- X(vkGetBufferMemoryRequirements);
- X(vkGetDeviceQueue);
- X(vkGetEventStatus);
- X(vkGetFenceStatus);
- X(vkGetImageMemoryRequirements);
- X(vkGetQueryPoolResults);
- X(vkMapMemory);
- X(vkQueueSubmit);
- X(vkResetFences);
- X(vkResetQueryPoolEXT);
- X(vkUnmapMemory);
- X(vkUpdateDescriptorSetWithTemplateKHR);
- X(vkUpdateDescriptorSets);
- X(vkWaitForFences);
-#undef X
-}
-
-} // Anonymous namespace
-
-bool Load(InstanceDispatch& dld) noexcept {
-#define X(name) Proc(dld.name, dld, #name)
- return X(vkCreateInstance) && X(vkEnumerateInstanceExtensionProperties) &&
- X(vkEnumerateInstanceLayerProperties);
-#undef X
-}
-
-bool Load(VkInstance instance, InstanceDispatch& dld) noexcept {
-#define X(name) Proc(dld.name, dld, #name, instance)
- // These functions may fail to load depending on the enabled extensions.
- // Don't return a failure on these.
- X(vkCreateDebugUtilsMessengerEXT);
- X(vkDestroyDebugUtilsMessengerEXT);
- X(vkDestroySurfaceKHR);
- X(vkGetPhysicalDeviceFeatures2KHR);
- X(vkGetPhysicalDeviceProperties2KHR);
- X(vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
- X(vkGetPhysicalDeviceSurfaceFormatsKHR);
- X(vkGetPhysicalDeviceSurfacePresentModesKHR);
- X(vkGetPhysicalDeviceSurfaceSupportKHR);
- X(vkGetSwapchainImagesKHR);
- X(vkQueuePresentKHR);
-
- return X(vkCreateDevice) && X(vkDestroyDevice) && X(vkDestroyDevice) &&
- X(vkEnumerateDeviceExtensionProperties) && X(vkEnumeratePhysicalDevices) &&
- X(vkGetDeviceProcAddr) && X(vkGetPhysicalDeviceFormatProperties) &&
- X(vkGetPhysicalDeviceMemoryProperties) && X(vkGetPhysicalDeviceProperties) &&
- X(vkGetPhysicalDeviceQueueFamilyProperties);
-#undef X
-}
-
-const char* Exception::what() const noexcept {
- return ToString(result);
-}
-
-const char* ToString(VkResult result) noexcept {
- switch (result) {
- case VkResult::VK_SUCCESS:
- return "VK_SUCCESS";
- case VkResult::VK_NOT_READY:
- return "VK_NOT_READY";
- case VkResult::VK_TIMEOUT:
- return "VK_TIMEOUT";
- case VkResult::VK_EVENT_SET:
- return "VK_EVENT_SET";
- case VkResult::VK_EVENT_RESET:
- return "VK_EVENT_RESET";
- case VkResult::VK_INCOMPLETE:
- return "VK_INCOMPLETE";
- case VkResult::VK_ERROR_OUT_OF_HOST_MEMORY:
- return "VK_ERROR_OUT_OF_HOST_MEMORY";
- case VkResult::VK_ERROR_OUT_OF_DEVICE_MEMORY:
- return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
- case VkResult::VK_ERROR_INITIALIZATION_FAILED:
- return "VK_ERROR_INITIALIZATION_FAILED";
- case VkResult::VK_ERROR_DEVICE_LOST:
- return "VK_ERROR_DEVICE_LOST";
- case VkResult::VK_ERROR_MEMORY_MAP_FAILED:
- return "VK_ERROR_MEMORY_MAP_FAILED";
- case VkResult::VK_ERROR_LAYER_NOT_PRESENT:
- return "VK_ERROR_LAYER_NOT_PRESENT";
- case VkResult::VK_ERROR_EXTENSION_NOT_PRESENT:
- return "VK_ERROR_EXTENSION_NOT_PRESENT";
- case VkResult::VK_ERROR_FEATURE_NOT_PRESENT:
- return "VK_ERROR_FEATURE_NOT_PRESENT";
- case VkResult::VK_ERROR_INCOMPATIBLE_DRIVER:
- return "VK_ERROR_INCOMPATIBLE_DRIVER";
- case VkResult::VK_ERROR_TOO_MANY_OBJECTS:
- return "VK_ERROR_TOO_MANY_OBJECTS";
- case VkResult::VK_ERROR_FORMAT_NOT_SUPPORTED:
- return "VK_ERROR_FORMAT_NOT_SUPPORTED";
- case VkResult::VK_ERROR_FRAGMENTED_POOL:
- return "VK_ERROR_FRAGMENTED_POOL";
- case VkResult::VK_ERROR_OUT_OF_POOL_MEMORY:
- return "VK_ERROR_OUT_OF_POOL_MEMORY";
- case VkResult::VK_ERROR_INVALID_EXTERNAL_HANDLE:
- return "VK_ERROR_INVALID_EXTERNAL_HANDLE";
- case VkResult::VK_ERROR_SURFACE_LOST_KHR:
- return "VK_ERROR_SURFACE_LOST_KHR";
- case VkResult::VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
- return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
- case VkResult::VK_SUBOPTIMAL_KHR:
- return "VK_SUBOPTIMAL_KHR";
- case VkResult::VK_ERROR_OUT_OF_DATE_KHR:
- return "VK_ERROR_OUT_OF_DATE_KHR";
- case VkResult::VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
- return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
- case VkResult::VK_ERROR_VALIDATION_FAILED_EXT:
- return "VK_ERROR_VALIDATION_FAILED_EXT";
- case VkResult::VK_ERROR_INVALID_SHADER_NV:
- return "VK_ERROR_INVALID_SHADER_NV";
- case VkResult::VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT:
- return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
- case VkResult::VK_ERROR_FRAGMENTATION_EXT:
- return "VK_ERROR_FRAGMENTATION_EXT";
- case VkResult::VK_ERROR_NOT_PERMITTED_EXT:
- return "VK_ERROR_NOT_PERMITTED_EXT";
- case VkResult::VK_ERROR_INVALID_DEVICE_ADDRESS_EXT:
- return "VK_ERROR_INVALID_DEVICE_ADDRESS_EXT";
- case VkResult::VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
- return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
- }
- return "Unknown";
-}
-
-void Destroy(VkInstance instance, const InstanceDispatch& dld) noexcept {
- dld.vkDestroyInstance(instance, nullptr);
-}
-
-void Destroy(VkDevice device, const InstanceDispatch& dld) noexcept {
- dld.vkDestroyDevice(device, nullptr);
-}
-
-void Destroy(VkDevice device, VkBuffer handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyBuffer(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkBufferView handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyBufferView(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkCommandPool handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyCommandPool(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkDescriptorPool handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyDescriptorPool(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkDescriptorSetLayout handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyDescriptorSetLayout(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkDescriptorUpdateTemplateKHR handle,
- const DeviceDispatch& dld) noexcept {
- dld.vkDestroyDescriptorUpdateTemplateKHR(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkDeviceMemory handle, const DeviceDispatch& dld) noexcept {
- dld.vkFreeMemory(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkEvent handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyEvent(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkFence handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyFence(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkFramebuffer handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyFramebuffer(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkImage handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyImage(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkImageView handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyImageView(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkPipeline handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyPipeline(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkPipelineLayout handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyPipelineLayout(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkQueryPool handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyQueryPool(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkRenderPass handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyRenderPass(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkSampler handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroySampler(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkSwapchainKHR handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroySwapchainKHR(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkSemaphore handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroySemaphore(device, handle, nullptr);
-}
-
-void Destroy(VkDevice device, VkShaderModule handle, const DeviceDispatch& dld) noexcept {
- dld.vkDestroyShaderModule(device, handle, nullptr);
-}
-
-void Destroy(VkInstance instance, VkDebugUtilsMessengerEXT handle,
- const InstanceDispatch& dld) noexcept {
- dld.vkDestroyDebugUtilsMessengerEXT(instance, handle, nullptr);
-}
-
-void Destroy(VkInstance instance, VkSurfaceKHR handle, const InstanceDispatch& dld) noexcept {
- dld.vkDestroySurfaceKHR(instance, handle, nullptr);
-}
-
-VkResult Free(VkDevice device, VkDescriptorPool handle, Span<VkDescriptorSet> sets,
- const DeviceDispatch& dld) noexcept {
- return dld.vkFreeDescriptorSets(device, handle, sets.size(), sets.data());
-}
-
-VkResult Free(VkDevice device, VkCommandPool handle, Span<VkCommandBuffer> buffers,
- const DeviceDispatch& dld) noexcept {
- dld.vkFreeCommandBuffers(device, handle, buffers.size(), buffers.data());
- return VK_SUCCESS;
-}
-
-Instance Instance::Create(Span<const char*> layers, Span<const char*> extensions,
- InstanceDispatch& dld) noexcept {
- static constexpr VkApplicationInfo application_info{
- .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
- .pNext = nullptr,
- .pApplicationName = "yuzu Emulator",
- .applicationVersion = VK_MAKE_VERSION(0, 1, 0),
- .pEngineName = "yuzu Emulator",
- .engineVersion = VK_MAKE_VERSION(0, 1, 0),
- .apiVersion = VK_API_VERSION_1_1,
- };
-
- const VkInstanceCreateInfo ci{
- .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .pApplicationInfo = &application_info,
- .enabledLayerCount = layers.size(),
- .ppEnabledLayerNames = layers.data(),
- .enabledExtensionCount = extensions.size(),
- .ppEnabledExtensionNames = extensions.data(),
- };
-
- VkInstance instance;
- if (dld.vkCreateInstance(&ci, nullptr, &instance) != VK_SUCCESS) {
- // Failed to create the instance.
- return {};
- }
- if (!Proc(dld.vkDestroyInstance, dld, "vkDestroyInstance", instance)) {
- // We successfully created an instance but the destroy function couldn't be loaded.
- // This is a good moment to panic.
- return {};
- }
-
- return Instance(instance, dld);
-}
-
-std::optional<std::vector<VkPhysicalDevice>> Instance::EnumeratePhysicalDevices() {
- u32 num;
- if (dld->vkEnumeratePhysicalDevices(handle, &num, nullptr) != VK_SUCCESS) {
- return std::nullopt;
- }
- std::vector<VkPhysicalDevice> physical_devices(num);
- if (dld->vkEnumeratePhysicalDevices(handle, &num, physical_devices.data()) != VK_SUCCESS) {
- return std::nullopt;
- }
- SortPhysicalDevices(physical_devices, *dld);
- return std::make_optional(std::move(physical_devices));
-}
-
-DebugCallback Instance::TryCreateDebugCallback(
- PFN_vkDebugUtilsMessengerCallbackEXT callback) noexcept {
- const VkDebugUtilsMessengerCreateInfoEXT ci{
- .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
- .pNext = nullptr,
- .flags = 0,
- .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT,
- .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
- VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
- .pfnUserCallback = callback,
- .pUserData = nullptr,
- };
-
- VkDebugUtilsMessengerEXT messenger;
- if (dld->vkCreateDebugUtilsMessengerEXT(handle, &ci, nullptr, &messenger) != VK_SUCCESS) {
- return {};
- }
- return DebugCallback(messenger, handle, *dld);
-}
-
-void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
- Check(dld->vkBindBufferMemory(owner, handle, memory, offset));
-}
-
-void Image::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
- Check(dld->vkBindImageMemory(owner, handle, memory, offset));
-}
-
-DescriptorSets DescriptorPool::Allocate(const VkDescriptorSetAllocateInfo& ai) const {
- const std::size_t num = ai.descriptorSetCount;
- std::unique_ptr sets = std::make_unique<VkDescriptorSet[]>(num);
- switch (const VkResult result = dld->vkAllocateDescriptorSets(owner, &ai, sets.get())) {
- case VK_SUCCESS:
- return DescriptorSets(std::move(sets), num, owner, handle, *dld);
- case VK_ERROR_OUT_OF_POOL_MEMORY:
- return {};
- default:
- throw Exception(result);
- }
-}
-
-CommandBuffers CommandPool::Allocate(std::size_t num_buffers, VkCommandBufferLevel level) const {
- const VkCommandBufferAllocateInfo ai{
- .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
- .pNext = nullptr,
- .commandPool = handle,
- .level = level,
- .commandBufferCount = static_cast<u32>(num_buffers),
- };
-
- std::unique_ptr buffers = std::make_unique<VkCommandBuffer[]>(num_buffers);
- switch (const VkResult result = dld->vkAllocateCommandBuffers(owner, &ai, buffers.get())) {
- case VK_SUCCESS:
- return CommandBuffers(std::move(buffers), num_buffers, owner, handle, *dld);
- case VK_ERROR_OUT_OF_POOL_MEMORY:
- return {};
- default:
- throw Exception(result);
- }
-}
-
-std::vector<VkImage> SwapchainKHR::GetImages() const {
- u32 num;
- Check(dld->vkGetSwapchainImagesKHR(owner, handle, &num, nullptr));
- std::vector<VkImage> images(num);
- Check(dld->vkGetSwapchainImagesKHR(owner, handle, &num, images.data()));
- return images;
-}
-
-Device Device::Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
- Span<const char*> enabled_extensions, const void* next,
- DeviceDispatch& dld) noexcept {
- const VkDeviceCreateInfo ci{
- .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
- .pNext = next,
- .flags = 0,
- .queueCreateInfoCount = queues_ci.size(),
- .pQueueCreateInfos = queues_ci.data(),
- .enabledLayerCount = 0,
- .ppEnabledLayerNames = nullptr,
- .enabledExtensionCount = enabled_extensions.size(),
- .ppEnabledExtensionNames = enabled_extensions.data(),
- .pEnabledFeatures = nullptr,
- };
-
- VkDevice device;
- if (dld.vkCreateDevice(physical_device, &ci, nullptr, &device) != VK_SUCCESS) {
- return {};
- }
- Load(device, dld);
- return Device(device, dld);
-}
-
-Queue Device::GetQueue(u32 family_index) const noexcept {
- VkQueue queue;
- dld->vkGetDeviceQueue(handle, family_index, 0, &queue);
- return Queue(queue, *dld);
-}
-
-Buffer Device::CreateBuffer(const VkBufferCreateInfo& ci) const {
- VkBuffer object;
- Check(dld->vkCreateBuffer(handle, &ci, nullptr, &object));
- return Buffer(object, handle, *dld);
-}
-
-BufferView Device::CreateBufferView(const VkBufferViewCreateInfo& ci) const {
- VkBufferView object;
- Check(dld->vkCreateBufferView(handle, &ci, nullptr, &object));
- return BufferView(object, handle, *dld);
-}
-
-Image Device::CreateImage(const VkImageCreateInfo& ci) const {
- VkImage object;
- Check(dld->vkCreateImage(handle, &ci, nullptr, &object));
- return Image(object, handle, *dld);
-}
-
-ImageView Device::CreateImageView(const VkImageViewCreateInfo& ci) const {
- VkImageView object;
- Check(dld->vkCreateImageView(handle, &ci, nullptr, &object));
- return ImageView(object, handle, *dld);
-}
-
-Semaphore Device::CreateSemaphore() const {
- static constexpr VkSemaphoreCreateInfo ci{
- .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- };
-
- VkSemaphore object;
- Check(dld->vkCreateSemaphore(handle, &ci, nullptr, &object));
- return Semaphore(object, handle, *dld);
-}
-
-Fence Device::CreateFence(const VkFenceCreateInfo& ci) const {
- VkFence object;
- Check(dld->vkCreateFence(handle, &ci, nullptr, &object));
- return Fence(object, handle, *dld);
-}
-
-DescriptorPool Device::CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const {
- VkDescriptorPool object;
- Check(dld->vkCreateDescriptorPool(handle, &ci, nullptr, &object));
- return DescriptorPool(object, handle, *dld);
-}
-
-RenderPass Device::CreateRenderPass(const VkRenderPassCreateInfo& ci) const {
- VkRenderPass object;
- Check(dld->vkCreateRenderPass(handle, &ci, nullptr, &object));
- return RenderPass(object, handle, *dld);
-}
-
-DescriptorSetLayout Device::CreateDescriptorSetLayout(
- const VkDescriptorSetLayoutCreateInfo& ci) const {
- VkDescriptorSetLayout object;
- Check(dld->vkCreateDescriptorSetLayout(handle, &ci, nullptr, &object));
- return DescriptorSetLayout(object, handle, *dld);
-}
-
-PipelineLayout Device::CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const {
- VkPipelineLayout object;
- Check(dld->vkCreatePipelineLayout(handle, &ci, nullptr, &object));
- return PipelineLayout(object, handle, *dld);
-}
-
-Pipeline Device::CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci) const {
- VkPipeline object;
- Check(dld->vkCreateGraphicsPipelines(handle, nullptr, 1, &ci, nullptr, &object));
- return Pipeline(object, handle, *dld);
-}
-
-Pipeline Device::CreateComputePipeline(const VkComputePipelineCreateInfo& ci) const {
- VkPipeline object;
- Check(dld->vkCreateComputePipelines(handle, nullptr, 1, &ci, nullptr, &object));
- return Pipeline(object, handle, *dld);
-}
-
-Sampler Device::CreateSampler(const VkSamplerCreateInfo& ci) const {
- VkSampler object;
- Check(dld->vkCreateSampler(handle, &ci, nullptr, &object));
- return Sampler(object, handle, *dld);
-}
-
-Framebuffer Device::CreateFramebuffer(const VkFramebufferCreateInfo& ci) const {
- VkFramebuffer object;
- Check(dld->vkCreateFramebuffer(handle, &ci, nullptr, &object));
- return Framebuffer(object, handle, *dld);
-}
-
-CommandPool Device::CreateCommandPool(const VkCommandPoolCreateInfo& ci) const {
- VkCommandPool object;
- Check(dld->vkCreateCommandPool(handle, &ci, nullptr, &object));
- return CommandPool(object, handle, *dld);
-}
-
-DescriptorUpdateTemplateKHR Device::CreateDescriptorUpdateTemplateKHR(
- const VkDescriptorUpdateTemplateCreateInfoKHR& ci) const {
- VkDescriptorUpdateTemplateKHR object;
- Check(dld->vkCreateDescriptorUpdateTemplateKHR(handle, &ci, nullptr, &object));
- return DescriptorUpdateTemplateKHR(object, handle, *dld);
-}
-
-QueryPool Device::CreateQueryPool(const VkQueryPoolCreateInfo& ci) const {
- VkQueryPool object;
- Check(dld->vkCreateQueryPool(handle, &ci, nullptr, &object));
- return QueryPool(object, handle, *dld);
-}
-
-ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) const {
- VkShaderModule object;
- Check(dld->vkCreateShaderModule(handle, &ci, nullptr, &object));
- return ShaderModule(object, handle, *dld);
-}
-
-Event Device::CreateNewEvent() const {
- static constexpr VkEventCreateInfo ci{
- .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- };
-
- VkEvent object;
- Check(dld->vkCreateEvent(handle, &ci, nullptr, &object));
- return Event(object, handle, *dld);
-}
-
-SwapchainKHR Device::CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const {
- VkSwapchainKHR object;
- Check(dld->vkCreateSwapchainKHR(handle, &ci, nullptr, &object));
- return SwapchainKHR(object, handle, *dld);
-}
-
-DeviceMemory Device::TryAllocateMemory(const VkMemoryAllocateInfo& ai) const noexcept {
- VkDeviceMemory memory;
- if (dld->vkAllocateMemory(handle, &ai, nullptr, &memory) != VK_SUCCESS) {
- return {};
- }
- return DeviceMemory(memory, handle, *dld);
-}
-
-DeviceMemory Device::AllocateMemory(const VkMemoryAllocateInfo& ai) const {
- VkDeviceMemory memory;
- Check(dld->vkAllocateMemory(handle, &ai, nullptr, &memory));
- return DeviceMemory(memory, handle, *dld);
-}
-
-VkMemoryRequirements Device::GetBufferMemoryRequirements(VkBuffer buffer) const noexcept {
- VkMemoryRequirements requirements;
- dld->vkGetBufferMemoryRequirements(handle, buffer, &requirements);
- return requirements;
-}
-
-VkMemoryRequirements Device::GetImageMemoryRequirements(VkImage image) const noexcept {
- VkMemoryRequirements requirements;
- dld->vkGetImageMemoryRequirements(handle, image, &requirements);
- return requirements;
-}
-
-void Device::UpdateDescriptorSets(Span<VkWriteDescriptorSet> writes,
- Span<VkCopyDescriptorSet> copies) const noexcept {
- dld->vkUpdateDescriptorSets(handle, writes.size(), writes.data(), copies.size(), copies.data());
-}
-
-VkPhysicalDeviceProperties PhysicalDevice::GetProperties() const noexcept {
- VkPhysicalDeviceProperties properties;
- dld->vkGetPhysicalDeviceProperties(physical_device, &properties);
- return properties;
-}
-
-void PhysicalDevice::GetProperties2KHR(VkPhysicalDeviceProperties2KHR& properties) const noexcept {
- dld->vkGetPhysicalDeviceProperties2KHR(physical_device, &properties);
-}
-
-VkPhysicalDeviceFeatures PhysicalDevice::GetFeatures() const noexcept {
- VkPhysicalDeviceFeatures2KHR features2;
- features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
- features2.pNext = nullptr;
- dld->vkGetPhysicalDeviceFeatures2KHR(physical_device, &features2);
- return features2.features;
-}
-
-void PhysicalDevice::GetFeatures2KHR(VkPhysicalDeviceFeatures2KHR& features) const noexcept {
- dld->vkGetPhysicalDeviceFeatures2KHR(physical_device, &features);
-}
-
-VkFormatProperties PhysicalDevice::GetFormatProperties(VkFormat format) const noexcept {
- VkFormatProperties properties;
- dld->vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties);
- return properties;
-}
-
-std::vector<VkExtensionProperties> PhysicalDevice::EnumerateDeviceExtensionProperties() const {
- u32 num;
- dld->vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &num, nullptr);
- std::vector<VkExtensionProperties> properties(num);
- dld->vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &num, properties.data());
- return properties;
-}
-
-std::vector<VkQueueFamilyProperties> PhysicalDevice::GetQueueFamilyProperties() const {
- u32 num;
- dld->vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &num, nullptr);
- std::vector<VkQueueFamilyProperties> properties(num);
- dld->vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &num, properties.data());
- return properties;
-}
-
-bool PhysicalDevice::GetSurfaceSupportKHR(u32 queue_family_index, VkSurfaceKHR surface) const {
- VkBool32 supported;
- Check(dld->vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, queue_family_index, surface,
- &supported));
- return supported == VK_TRUE;
-}
-
-VkSurfaceCapabilitiesKHR PhysicalDevice::GetSurfaceCapabilitiesKHR(VkSurfaceKHR surface) const {
- VkSurfaceCapabilitiesKHR capabilities;
- Check(dld->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities));
- return capabilities;
-}
-
-std::vector<VkSurfaceFormatKHR> PhysicalDevice::GetSurfaceFormatsKHR(VkSurfaceKHR surface) const {
- u32 num;
- Check(dld->vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &num, nullptr));
- std::vector<VkSurfaceFormatKHR> formats(num);
- Check(
- dld->vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &num, formats.data()));
- return formats;
-}
-
-std::vector<VkPresentModeKHR> PhysicalDevice::GetSurfacePresentModesKHR(
- VkSurfaceKHR surface) const {
- u32 num;
- Check(dld->vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &num, nullptr));
- std::vector<VkPresentModeKHR> modes(num);
- Check(dld->vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &num,
- modes.data()));
- return modes;
-}
-
-VkPhysicalDeviceMemoryProperties PhysicalDevice::GetMemoryProperties() const noexcept {
- VkPhysicalDeviceMemoryProperties properties;
- dld->vkGetPhysicalDeviceMemoryProperties(physical_device, &properties);
- return properties;
-}
-
-std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProperties(
- const InstanceDispatch& dld) {
- u32 num;
- if (dld.vkEnumerateInstanceExtensionProperties(nullptr, &num, nullptr) != VK_SUCCESS) {
- return std::nullopt;
- }
- std::vector<VkExtensionProperties> properties(num);
- if (dld.vkEnumerateInstanceExtensionProperties(nullptr, &num, properties.data()) !=
- VK_SUCCESS) {
- return std::nullopt;
- }
- return std::move(properties);
-}
-
-std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
- const InstanceDispatch& dld) {
- u32 num;
- if (dld.vkEnumerateInstanceLayerProperties(&num, nullptr) != VK_SUCCESS) {
- return std::nullopt;
- }
- std::vector<VkLayerProperties> properties(num);
- if (dld.vkEnumerateInstanceLayerProperties(&num, properties.data()) != VK_SUCCESS) {
- return std::nullopt;
- }
- return properties;
-}
-
-} // namespace Vulkan::vk
diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h
deleted file mode 100644
index b9d3fedc1..000000000
--- a/src/video_core/renderer_vulkan/wrapper.h
+++ /dev/null
@@ -1,1057 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <exception>
-#include <iterator>
-#include <limits>
-#include <memory>
-#include <optional>
-#include <type_traits>
-#include <utility>
-#include <vector>
-
-#define VK_NO_PROTOTYPES
-#include <vulkan/vulkan.h>
-
-#include "common/common_types.h"
-
-namespace Vulkan::vk {
-
-/**
- * Span for Vulkan arrays.
- * Based on std::span but optimized for array access instead of iterators.
- * Size returns uint32_t instead of size_t to ease interaction with Vulkan functions.
- */
-template <typename T>
-class Span {
-public:
- using value_type = T;
- using size_type = u32;
- using difference_type = std::ptrdiff_t;
- using reference = const T&;
- using const_reference = const T&;
- using pointer = const T*;
- using const_pointer = const T*;
- using iterator = const T*;
- using const_iterator = const T*;
-
- /// Construct an empty span.
- constexpr Span() noexcept = default;
-
- /// Construct a span from a single element.
- constexpr Span(const T& value) noexcept : ptr{&value}, num{1} {}
-
- /// Construct a span from a range.
- template <typename Range>
- // requires std::data(const Range&)
- // requires std::size(const Range&)
- constexpr Span(const Range& range) : ptr{std::data(range)}, num{std::size(range)} {}
-
- /// Construct a span from a pointer and a size.
- /// This is inteded for subranges.
- constexpr Span(const T* ptr, std::size_t num) noexcept : ptr{ptr}, num{num} {}
-
- /// Returns the data pointer by the span.
- constexpr const T* data() const noexcept {
- return ptr;
- }
-
- /// Returns the number of elements in the span.
- /// @note Returns a 32 bits integer because most Vulkan functions expect this type.
- constexpr u32 size() const noexcept {
- return static_cast<u32>(num);
- }
-
- /// Returns true when the span is empty.
- constexpr bool empty() const noexcept {
- return num == 0;
- }
-
- /// Returns a reference to the element in the passed index.
- /// @pre: index < size()
- constexpr const T& operator[](std::size_t index) const noexcept {
- return ptr[index];
- }
-
- /// Returns an iterator to the beginning of the span.
- constexpr const T* begin() const noexcept {
- return ptr;
- }
-
- /// Returns an iterator to the end of the span.
- constexpr const T* end() const noexcept {
- return ptr + num;
- }
-
- /// Returns an iterator to the beginning of the span.
- constexpr const T* cbegin() const noexcept {
- return ptr;
- }
-
- /// Returns an iterator to the end of the span.
- constexpr const T* cend() const noexcept {
- return ptr + num;
- }
-
-private:
- const T* ptr = nullptr;
- std::size_t num = 0;
-};
-
-/// Vulkan exception generated from a VkResult.
-class Exception final : public std::exception {
-public:
- /// Construct the exception with a result.
- /// @pre result != VK_SUCCESS
- explicit Exception(VkResult result_) : result{result_} {}
- virtual ~Exception() = default;
-
- const char* what() const noexcept override;
-
-private:
- VkResult result;
-};
-
-/// Converts a VkResult enum into a rodata string
-const char* ToString(VkResult) noexcept;
-
-/// Throws a Vulkan exception if result is not success.
-inline void Check(VkResult result) {
- if (result != VK_SUCCESS) {
- throw Exception(result);
- }
-}
-
-/// Throws a Vulkan exception if result is an error.
-/// @return result
-inline VkResult Filter(VkResult result) {
- if (result < 0) {
- throw Exception(result);
- }
- return result;
-}
-
-/// Table holding Vulkan instance function pointers.
-struct InstanceDispatch {
- PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
-
- PFN_vkCreateInstance vkCreateInstance;
- PFN_vkDestroyInstance vkDestroyInstance;
- PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
- PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
-
- PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT;
- PFN_vkCreateDevice vkCreateDevice;
- PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT;
- PFN_vkDestroyDevice vkDestroyDevice;
- PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
- PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
- PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
- PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
- PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR;
- PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties;
- PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
- PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
- PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR;
- PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
- PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
- PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
- PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
- PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR;
- PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
- PFN_vkQueuePresentKHR vkQueuePresentKHR;
-};
-
-/// Table holding Vulkan device function pointers.
-struct DeviceDispatch : public InstanceDispatch {
- PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
- PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
- PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
- PFN_vkAllocateMemory vkAllocateMemory;
- PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
- PFN_vkBindBufferMemory vkBindBufferMemory;
- PFN_vkBindImageMemory vkBindImageMemory;
- PFN_vkCmdBeginQuery vkCmdBeginQuery;
- PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
- PFN_vkCmdBeginTransformFeedbackEXT vkCmdBeginTransformFeedbackEXT;
- PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
- PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
- PFN_vkCmdBindPipeline vkCmdBindPipeline;
- PFN_vkCmdBindTransformFeedbackBuffersEXT vkCmdBindTransformFeedbackBuffersEXT;
- PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
- PFN_vkCmdBlitImage vkCmdBlitImage;
- PFN_vkCmdClearAttachments vkCmdClearAttachments;
- PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
- PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
- PFN_vkCmdCopyImage vkCmdCopyImage;
- PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
- PFN_vkCmdDispatch vkCmdDispatch;
- PFN_vkCmdDraw vkCmdDraw;
- PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
- PFN_vkCmdEndQuery vkCmdEndQuery;
- PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
- PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT;
- PFN_vkCmdFillBuffer vkCmdFillBuffer;
- PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
- PFN_vkCmdPushConstants vkCmdPushConstants;
- PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
- PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
- PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
- PFN_vkCmdSetEvent vkCmdSetEvent;
- PFN_vkCmdSetScissor vkCmdSetScissor;
- PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask;
- PFN_vkCmdSetStencilReference vkCmdSetStencilReference;
- PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
- PFN_vkCmdSetViewport vkCmdSetViewport;
- PFN_vkCmdWaitEvents vkCmdWaitEvents;
- PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT;
- PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT;
- PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT;
- PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT;
- PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT;
- PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT;
- PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT;
- PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT;
- PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT;
- PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT;
- PFN_vkCreateBuffer vkCreateBuffer;
- PFN_vkCreateBufferView vkCreateBufferView;
- PFN_vkCreateCommandPool vkCreateCommandPool;
- PFN_vkCreateComputePipelines vkCreateComputePipelines;
- PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
- PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
- PFN_vkCreateDescriptorUpdateTemplateKHR vkCreateDescriptorUpdateTemplateKHR;
- PFN_vkCreateEvent vkCreateEvent;
- PFN_vkCreateFence vkCreateFence;
- PFN_vkCreateFramebuffer vkCreateFramebuffer;
- PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
- PFN_vkCreateImage vkCreateImage;
- PFN_vkCreateImageView vkCreateImageView;
- PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
- PFN_vkCreateQueryPool vkCreateQueryPool;
- PFN_vkCreateRenderPass vkCreateRenderPass;
- PFN_vkCreateSampler vkCreateSampler;
- PFN_vkCreateSemaphore vkCreateSemaphore;
- PFN_vkCreateShaderModule vkCreateShaderModule;
- PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
- PFN_vkDestroyBuffer vkDestroyBuffer;
- PFN_vkDestroyBufferView vkDestroyBufferView;
- PFN_vkDestroyCommandPool vkDestroyCommandPool;
- PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
- PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
- PFN_vkDestroyDescriptorUpdateTemplateKHR vkDestroyDescriptorUpdateTemplateKHR;
- PFN_vkDestroyEvent vkDestroyEvent;
- PFN_vkDestroyFence vkDestroyFence;
- PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
- PFN_vkDestroyImage vkDestroyImage;
- PFN_vkDestroyImageView vkDestroyImageView;
- PFN_vkDestroyPipeline vkDestroyPipeline;
- PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
- PFN_vkDestroyQueryPool vkDestroyQueryPool;
- PFN_vkDestroyRenderPass vkDestroyRenderPass;
- PFN_vkDestroySampler vkDestroySampler;
- PFN_vkDestroySemaphore vkDestroySemaphore;
- PFN_vkDestroyShaderModule vkDestroyShaderModule;
- PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
- PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
- PFN_vkEndCommandBuffer vkEndCommandBuffer;
- PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
- PFN_vkFreeDescriptorSets vkFreeDescriptorSets;
- PFN_vkFreeMemory vkFreeMemory;
- PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
- PFN_vkGetDeviceQueue vkGetDeviceQueue;
- PFN_vkGetEventStatus vkGetEventStatus;
- PFN_vkGetFenceStatus vkGetFenceStatus;
- PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
- PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
- PFN_vkMapMemory vkMapMemory;
- PFN_vkQueueSubmit vkQueueSubmit;
- PFN_vkResetFences vkResetFences;
- PFN_vkResetQueryPoolEXT vkResetQueryPoolEXT;
- PFN_vkUnmapMemory vkUnmapMemory;
- PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR;
- PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
- PFN_vkWaitForFences vkWaitForFences;
-};
-
-/// Loads instance agnostic function pointers.
-/// @return True on success, false on error.
-bool Load(InstanceDispatch&) noexcept;
-
-/// Loads instance function pointers.
-/// @return True on success, false on error.
-bool Load(VkInstance, InstanceDispatch&) noexcept;
-
-void Destroy(VkInstance, const InstanceDispatch&) noexcept;
-void Destroy(VkDevice, const InstanceDispatch&) noexcept;
-
-void Destroy(VkDevice, VkBuffer, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkBufferView, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkCommandPool, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkDescriptorPool, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkDescriptorSetLayout, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkDescriptorUpdateTemplateKHR, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkDeviceMemory, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkEvent, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkFence, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkFramebuffer, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkImage, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkImageView, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkPipeline, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkPipelineLayout, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkQueryPool, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkRenderPass, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkSampler, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkSwapchainKHR, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkSemaphore, const DeviceDispatch&) noexcept;
-void Destroy(VkDevice, VkShaderModule, const DeviceDispatch&) noexcept;
-void Destroy(VkInstance, VkDebugUtilsMessengerEXT, const InstanceDispatch&) noexcept;
-void Destroy(VkInstance, VkSurfaceKHR, const InstanceDispatch&) noexcept;
-
-VkResult Free(VkDevice, VkDescriptorPool, Span<VkDescriptorSet>, const DeviceDispatch&) noexcept;
-VkResult Free(VkDevice, VkCommandPool, Span<VkCommandBuffer>, const DeviceDispatch&) noexcept;
-
-template <typename Type, typename OwnerType, typename Dispatch>
-class Handle;
-
-/// Handle with an owning type.
-/// Analogue to std::unique_ptr.
-template <typename Type, typename OwnerType, typename Dispatch>
-class Handle {
-public:
- /// Construct a handle and hold it's ownership.
- explicit Handle(Type handle_, OwnerType owner_, const Dispatch& dld_) noexcept
- : handle{handle_}, owner{owner_}, dld{&dld_} {}
-
- /// Construct an empty handle.
- Handle() = default;
-
- /// Copying Vulkan objects is not supported and will never be.
- Handle(const Handle&) = delete;
- Handle& operator=(const Handle&) = delete;
-
- /// Construct a handle transfering the ownership from another handle.
- Handle(Handle&& rhs) noexcept
- : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {}
-
- /// Assign the current handle transfering the ownership from another handle.
- /// Destroys any previously held object.
- Handle& operator=(Handle&& rhs) noexcept {
- Release();
- handle = std::exchange(rhs.handle, nullptr);
- owner = rhs.owner;
- dld = rhs.dld;
- return *this;
- }
-
- /// Destroys the current handle if it existed.
- ~Handle() noexcept {
- Release();
- }
-
- /// Destroys any held object.
- void reset() noexcept {
- Release();
- handle = nullptr;
- }
-
- /// Returns the address of the held object.
- /// Intended for Vulkan structures that expect a pointer to an array.
- const Type* address() const noexcept {
- return std::addressof(handle);
- }
-
- /// Returns the held Vulkan handle.
- Type operator*() const noexcept {
- return handle;
- }
-
- /// Returns true when there's a held object.
- explicit operator bool() const noexcept {
- return handle != nullptr;
- }
-
-protected:
- Type handle = nullptr;
- OwnerType owner = nullptr;
- const Dispatch* dld = nullptr;
-
-private:
- /// Destroys the held object if it exists.
- void Release() noexcept {
- if (handle) {
- Destroy(owner, handle, *dld);
- }
- }
-};
-
-/// Dummy type used to specify a handle has no owner.
-struct NoOwner {};
-
-/// Handle without an owning type.
-/// Analogue to std::unique_ptr
-template <typename Type, typename Dispatch>
-class Handle<Type, NoOwner, Dispatch> {
-public:
- /// Construct a handle and hold it's ownership.
- explicit Handle(Type handle_, const Dispatch& dld_) noexcept : handle{handle_}, dld{&dld_} {}
-
- /// Construct an empty handle.
- Handle() noexcept = default;
-
- /// Copying Vulkan objects is not supported and will never be.
- Handle(const Handle&) = delete;
- Handle& operator=(const Handle&) = delete;
-
- /// Construct a handle transfering ownership from another handle.
- Handle(Handle&& rhs) noexcept : handle{std::exchange(rhs.handle, nullptr)}, dld{rhs.dld} {}
-
- /// Assign the current handle transfering the ownership from another handle.
- /// Destroys any previously held object.
- Handle& operator=(Handle&& rhs) noexcept {
- Release();
- handle = std::exchange(rhs.handle, nullptr);
- dld = rhs.dld;
- return *this;
- }
-
- /// Destroys the current handle if it existed.
- ~Handle() noexcept {
- Release();
- }
-
- /// Destroys any held object.
- void reset() noexcept {
- Release();
- handle = nullptr;
- }
-
- /// Returns the address of the held object.
- /// Intended for Vulkan structures that expect a pointer to an array.
- const Type* address() const noexcept {
- return std::addressof(handle);
- }
-
- /// Returns the held Vulkan handle.
- Type operator*() const noexcept {
- return handle;
- }
-
- /// Returns true when there's a held object.
- operator bool() const noexcept {
- return handle != nullptr;
- }
-
-protected:
- Type handle = nullptr;
- const Dispatch* dld = nullptr;
-
-private:
- /// Destroys the held object if it exists.
- void Release() noexcept {
- if (handle) {
- Destroy(handle, *dld);
- }
- }
-};
-
-/// Array of a pool allocation.
-/// Analogue to std::vector
-template <typename AllocationType, typename PoolType>
-class PoolAllocations {
-public:
- /// Construct an empty allocation.
- PoolAllocations() = default;
-
- /// Construct an allocation. Errors are reported through IsOutOfPoolMemory().
- explicit PoolAllocations(std::unique_ptr<AllocationType[]> allocations, std::size_t num,
- VkDevice device, PoolType pool, const DeviceDispatch& dld) noexcept
- : allocations{std::move(allocations)}, num{num}, device{device}, pool{pool}, dld{&dld} {}
-
- /// Copying Vulkan allocations is not supported and will never be.
- PoolAllocations(const PoolAllocations&) = delete;
- PoolAllocations& operator=(const PoolAllocations&) = delete;
-
- /// Construct an allocation transfering ownership from another allocation.
- PoolAllocations(PoolAllocations&& rhs) noexcept
- : allocations{std::move(rhs.allocations)}, num{rhs.num}, device{rhs.device}, pool{rhs.pool},
- dld{rhs.dld} {}
-
- /// Assign an allocation transfering ownership from another allocation.
- /// Releases any previously held allocation.
- PoolAllocations& operator=(PoolAllocations&& rhs) noexcept {
- Release();
- allocations = std::move(rhs.allocations);
- num = rhs.num;
- device = rhs.device;
- pool = rhs.pool;
- dld = rhs.dld;
- return *this;
- }
-
- /// Destroys any held allocation.
- ~PoolAllocations() {
- Release();
- }
-
- /// Returns the number of allocations.
- std::size_t size() const noexcept {
- return num;
- }
-
- /// Returns a pointer to the array of allocations.
- AllocationType const* data() const noexcept {
- return allocations.get();
- }
-
- /// Returns the allocation in the specified index.
- /// @pre index < size()
- AllocationType operator[](std::size_t index) const noexcept {
- return allocations[index];
- }
-
- /// True when a pool fails to construct.
- bool IsOutOfPoolMemory() const noexcept {
- return !device;
- }
-
-private:
- /// Destroys the held allocations if they exist.
- void Release() noexcept {
- if (!allocations) {
- return;
- }
- const Span<AllocationType> span(allocations.get(), num);
- const VkResult result = Free(device, pool, span, *dld);
- // There's no way to report errors from a destructor.
- if (result != VK_SUCCESS) {
- std::terminate();
- }
- }
-
- std::unique_ptr<AllocationType[]> allocations;
- std::size_t num = 0;
- VkDevice device = nullptr;
- PoolType pool = nullptr;
- const DeviceDispatch* dld = nullptr;
-};
-
-using BufferView = Handle<VkBufferView, VkDevice, DeviceDispatch>;
-using DebugCallback = Handle<VkDebugUtilsMessengerEXT, VkInstance, InstanceDispatch>;
-using DescriptorSetLayout = Handle<VkDescriptorSetLayout, VkDevice, DeviceDispatch>;
-using DescriptorUpdateTemplateKHR = Handle<VkDescriptorUpdateTemplateKHR, VkDevice, DeviceDispatch>;
-using Framebuffer = Handle<VkFramebuffer, VkDevice, DeviceDispatch>;
-using ImageView = Handle<VkImageView, VkDevice, DeviceDispatch>;
-using Pipeline = Handle<VkPipeline, VkDevice, DeviceDispatch>;
-using PipelineLayout = Handle<VkPipelineLayout, VkDevice, DeviceDispatch>;
-using QueryPool = Handle<VkQueryPool, VkDevice, DeviceDispatch>;
-using RenderPass = Handle<VkRenderPass, VkDevice, DeviceDispatch>;
-using Sampler = Handle<VkSampler, VkDevice, DeviceDispatch>;
-using Semaphore = Handle<VkSemaphore, VkDevice, DeviceDispatch>;
-using ShaderModule = Handle<VkShaderModule, VkDevice, DeviceDispatch>;
-using SurfaceKHR = Handle<VkSurfaceKHR, VkInstance, InstanceDispatch>;
-
-using DescriptorSets = PoolAllocations<VkDescriptorSet, VkDescriptorPool>;
-using CommandBuffers = PoolAllocations<VkCommandBuffer, VkCommandPool>;
-
-/// Vulkan instance owning handle.
-class Instance : public Handle<VkInstance, NoOwner, InstanceDispatch> {
- using Handle<VkInstance, NoOwner, InstanceDispatch>::Handle;
-
-public:
- /// Creates a Vulkan instance. Use "operator bool" for error handling.
- static Instance Create(Span<const char*> layers, Span<const char*> extensions,
- InstanceDispatch& dld) noexcept;
-
- /// Enumerates physical devices.
- /// @return Physical devices and an empty handle on failure.
- std::optional<std::vector<VkPhysicalDevice>> EnumeratePhysicalDevices();
-
- /// Tries to create a debug callback messenger. Returns an empty handle on failure.
- DebugCallback TryCreateDebugCallback(PFN_vkDebugUtilsMessengerCallbackEXT callback) noexcept;
-};
-
-class Queue {
-public:
- /// Construct an empty queue handle.
- constexpr Queue() noexcept = default;
-
- /// Construct a queue handle.
- constexpr Queue(VkQueue queue, const DeviceDispatch& dld) noexcept : queue{queue}, dld{&dld} {}
-
- VkResult Submit(Span<VkSubmitInfo> submit_infos, VkFence fence) const noexcept {
- return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence);
- }
-
- VkResult Present(const VkPresentInfoKHR& present_info) const noexcept {
- return dld->vkQueuePresentKHR(queue, &present_info);
- }
-
-private:
- VkQueue queue = nullptr;
- const DeviceDispatch* dld = nullptr;
-};
-
-class Buffer : public Handle<VkBuffer, VkDevice, DeviceDispatch> {
- using Handle<VkBuffer, VkDevice, DeviceDispatch>::Handle;
-
-public:
- /// Attaches a memory allocation.
- void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const;
-};
-
-class Image : public Handle<VkImage, VkDevice, DeviceDispatch> {
- using Handle<VkImage, VkDevice, DeviceDispatch>::Handle;
-
-public:
- /// Attaches a memory allocation.
- void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const;
-};
-
-class DeviceMemory : public Handle<VkDeviceMemory, VkDevice, DeviceDispatch> {
- using Handle<VkDeviceMemory, VkDevice, DeviceDispatch>::Handle;
-
-public:
- u8* Map(VkDeviceSize offset, VkDeviceSize size) const {
- void* data;
- Check(dld->vkMapMemory(owner, handle, offset, size, 0, &data));
- return static_cast<u8*>(data);
- }
-
- void Unmap() const noexcept {
- dld->vkUnmapMemory(owner, handle);
- }
-};
-
-class Fence : public Handle<VkFence, VkDevice, DeviceDispatch> {
- using Handle<VkFence, VkDevice, DeviceDispatch>::Handle;
-
-public:
- VkResult Wait(u64 timeout = std::numeric_limits<u64>::max()) const noexcept {
- return dld->vkWaitForFences(owner, 1, &handle, true, timeout);
- }
-
- VkResult GetStatus() const noexcept {
- return dld->vkGetFenceStatus(owner, handle);
- }
-
- void Reset() const {
- Check(dld->vkResetFences(owner, 1, &handle));
- }
-};
-
-class DescriptorPool : public Handle<VkDescriptorPool, VkDevice, DeviceDispatch> {
- using Handle<VkDescriptorPool, VkDevice, DeviceDispatch>::Handle;
-
-public:
- DescriptorSets Allocate(const VkDescriptorSetAllocateInfo& ai) const;
-};
-
-class CommandPool : public Handle<VkCommandPool, VkDevice, DeviceDispatch> {
- using Handle<VkCommandPool, VkDevice, DeviceDispatch>::Handle;
-
-public:
- CommandBuffers Allocate(std::size_t num_buffers,
- VkCommandBufferLevel level = VK_COMMAND_BUFFER_LEVEL_PRIMARY) const;
-};
-
-class SwapchainKHR : public Handle<VkSwapchainKHR, VkDevice, DeviceDispatch> {
- using Handle<VkSwapchainKHR, VkDevice, DeviceDispatch>::Handle;
-
-public:
- std::vector<VkImage> GetImages() const;
-};
-
-class Event : public Handle<VkEvent, VkDevice, DeviceDispatch> {
- using Handle<VkEvent, VkDevice, DeviceDispatch>::Handle;
-
-public:
- VkResult GetStatus() const noexcept {
- return dld->vkGetEventStatus(owner, handle);
- }
-};
-
-class Device : public Handle<VkDevice, NoOwner, DeviceDispatch> {
- using Handle<VkDevice, NoOwner, DeviceDispatch>::Handle;
-
-public:
- static Device Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
- Span<const char*> enabled_extensions, const void* next,
- DeviceDispatch& dld) noexcept;
-
- Queue GetQueue(u32 family_index) const noexcept;
-
- Buffer CreateBuffer(const VkBufferCreateInfo& ci) const;
-
- BufferView CreateBufferView(const VkBufferViewCreateInfo& ci) const;
-
- Image CreateImage(const VkImageCreateInfo& ci) const;
-
- ImageView CreateImageView(const VkImageViewCreateInfo& ci) const;
-
- Semaphore CreateSemaphore() const;
-
- Fence CreateFence(const VkFenceCreateInfo& ci) const;
-
- DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const;
-
- RenderPass CreateRenderPass(const VkRenderPassCreateInfo& ci) const;
-
- DescriptorSetLayout CreateDescriptorSetLayout(const VkDescriptorSetLayoutCreateInfo& ci) const;
-
- PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const;
-
- Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci) const;
-
- Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci) const;
-
- Sampler CreateSampler(const VkSamplerCreateInfo& ci) const;
-
- Framebuffer CreateFramebuffer(const VkFramebufferCreateInfo& ci) const;
-
- CommandPool CreateCommandPool(const VkCommandPoolCreateInfo& ci) const;
-
- DescriptorUpdateTemplateKHR CreateDescriptorUpdateTemplateKHR(
- const VkDescriptorUpdateTemplateCreateInfoKHR& ci) const;
-
- QueryPool CreateQueryPool(const VkQueryPoolCreateInfo& ci) const;
-
- ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const;
-
- Event CreateNewEvent() const;
-
- SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const;
-
- DeviceMemory TryAllocateMemory(const VkMemoryAllocateInfo& ai) const noexcept;
-
- DeviceMemory AllocateMemory(const VkMemoryAllocateInfo& ai) const;
-
- VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer) const noexcept;
-
- VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept;
-
- void UpdateDescriptorSets(Span<VkWriteDescriptorSet> writes,
- Span<VkCopyDescriptorSet> copies) const noexcept;
-
- void UpdateDescriptorSet(VkDescriptorSet set, VkDescriptorUpdateTemplateKHR update_template,
- const void* data) const noexcept {
- dld->vkUpdateDescriptorSetWithTemplateKHR(handle, set, update_template, data);
- }
-
- VkResult AcquireNextImageKHR(VkSwapchainKHR swapchain, u64 timeout, VkSemaphore semaphore,
- VkFence fence, u32* image_index) const noexcept {
- return dld->vkAcquireNextImageKHR(handle, swapchain, timeout, semaphore, fence,
- image_index);
- }
-
- VkResult WaitIdle() const noexcept {
- return dld->vkDeviceWaitIdle(handle);
- }
-
- void ResetQueryPoolEXT(VkQueryPool query_pool, u32 first, u32 count) const noexcept {
- dld->vkResetQueryPoolEXT(handle, query_pool, first, count);
- }
-
- VkResult GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
- void* data, VkDeviceSize stride,
- VkQueryResultFlags flags) const noexcept {
- return dld->vkGetQueryPoolResults(handle, query_pool, first, count, data_size, data, stride,
- flags);
- }
-};
-
-class PhysicalDevice {
-public:
- constexpr PhysicalDevice() noexcept = default;
-
- constexpr PhysicalDevice(VkPhysicalDevice physical_device, const InstanceDispatch& dld) noexcept
- : physical_device{physical_device}, dld{&dld} {}
-
- constexpr operator VkPhysicalDevice() const noexcept {
- return physical_device;
- }
-
- VkPhysicalDeviceProperties GetProperties() const noexcept;
-
- void GetProperties2KHR(VkPhysicalDeviceProperties2KHR&) const noexcept;
-
- VkPhysicalDeviceFeatures GetFeatures() const noexcept;
-
- void GetFeatures2KHR(VkPhysicalDeviceFeatures2KHR&) const noexcept;
-
- VkFormatProperties GetFormatProperties(VkFormat) const noexcept;
-
- std::vector<VkExtensionProperties> EnumerateDeviceExtensionProperties() const;
-
- std::vector<VkQueueFamilyProperties> GetQueueFamilyProperties() const;
-
- bool GetSurfaceSupportKHR(u32 queue_family_index, VkSurfaceKHR) const;
-
- VkSurfaceCapabilitiesKHR GetSurfaceCapabilitiesKHR(VkSurfaceKHR) const;
-
- std::vector<VkSurfaceFormatKHR> GetSurfaceFormatsKHR(VkSurfaceKHR) const;
-
- std::vector<VkPresentModeKHR> GetSurfacePresentModesKHR(VkSurfaceKHR) const;
-
- VkPhysicalDeviceMemoryProperties GetMemoryProperties() const noexcept;
-
-private:
- VkPhysicalDevice physical_device = nullptr;
- const InstanceDispatch* dld = nullptr;
-};
-
-class CommandBuffer {
-public:
- CommandBuffer() noexcept = default;
-
- explicit CommandBuffer(VkCommandBuffer handle, const DeviceDispatch& dld) noexcept
- : handle{handle}, dld{&dld} {}
-
- const VkCommandBuffer* address() const noexcept {
- return &handle;
- }
-
- void Begin(const VkCommandBufferBeginInfo& begin_info) const {
- Check(dld->vkBeginCommandBuffer(handle, &begin_info));
- }
-
- void End() const {
- Check(dld->vkEndCommandBuffer(handle));
- }
-
- void BeginRenderPass(const VkRenderPassBeginInfo& renderpass_bi,
- VkSubpassContents contents) const noexcept {
- dld->vkCmdBeginRenderPass(handle, &renderpass_bi, contents);
- }
-
- void EndRenderPass() const noexcept {
- dld->vkCmdEndRenderPass(handle);
- }
-
- void BeginQuery(VkQueryPool query_pool, u32 query, VkQueryControlFlags flags) const noexcept {
- dld->vkCmdBeginQuery(handle, query_pool, query, flags);
- }
-
- void EndQuery(VkQueryPool query_pool, u32 query) const noexcept {
- dld->vkCmdEndQuery(handle, query_pool, query);
- }
-
- void BindDescriptorSets(VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 first,
- Span<VkDescriptorSet> sets, Span<u32> dynamic_offsets) const noexcept {
- dld->vkCmdBindDescriptorSets(handle, bind_point, layout, first, sets.size(), sets.data(),
- dynamic_offsets.size(), dynamic_offsets.data());
- }
-
- void BindPipeline(VkPipelineBindPoint bind_point, VkPipeline pipeline) const noexcept {
- dld->vkCmdBindPipeline(handle, bind_point, pipeline);
- }
-
- void BindIndexBuffer(VkBuffer buffer, VkDeviceSize offset,
- VkIndexType index_type) const noexcept {
- dld->vkCmdBindIndexBuffer(handle, buffer, offset, index_type);
- }
-
- void BindVertexBuffers(u32 first, u32 count, const VkBuffer* buffers,
- const VkDeviceSize* offsets) const noexcept {
- dld->vkCmdBindVertexBuffers(handle, first, count, buffers, offsets);
- }
-
- void BindVertexBuffer(u32 binding, VkBuffer buffer, VkDeviceSize offset) const noexcept {
- BindVertexBuffers(binding, 1, &buffer, &offset);
- }
-
- void Draw(u32 vertex_count, u32 instance_count, u32 first_vertex,
- u32 first_instance) const noexcept {
- dld->vkCmdDraw(handle, vertex_count, instance_count, first_vertex, first_instance);
- }
-
- void DrawIndexed(u32 index_count, u32 instance_count, u32 first_index, u32 vertex_offset,
- u32 first_instance) const noexcept {
- dld->vkCmdDrawIndexed(handle, index_count, instance_count, first_index, vertex_offset,
- first_instance);
- }
-
- void ClearAttachments(Span<VkClearAttachment> attachments,
- Span<VkClearRect> rects) const noexcept {
- dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
- rects.data());
- }
-
- void BlitImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image,
- VkImageLayout dst_layout, Span<VkImageBlit> regions,
- VkFilter filter) const noexcept {
- dld->vkCmdBlitImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(),
- regions.data(), filter);
- }
-
- void Dispatch(u32 x, u32 y, u32 z) const noexcept {
- dld->vkCmdDispatch(handle, x, y, z);
- }
-
- void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
- VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers,
- Span<VkBufferMemoryBarrier> buffer_barriers,
- Span<VkImageMemoryBarrier> image_barriers) const noexcept {
- dld->vkCmdPipelineBarrier(handle, src_stage_mask, dst_stage_mask, dependency_flags,
- memory_barriers.size(), memory_barriers.data(),
- buffer_barriers.size(), buffer_barriers.data(),
- image_barriers.size(), image_barriers.data());
- }
-
- void CopyBufferToImage(VkBuffer src_buffer, VkImage dst_image, VkImageLayout dst_image_layout,
- Span<VkBufferImageCopy> regions) const noexcept {
- dld->vkCmdCopyBufferToImage(handle, src_buffer, dst_image, dst_image_layout, regions.size(),
- regions.data());
- }
-
- void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
- Span<VkBufferCopy> regions) const noexcept {
- dld->vkCmdCopyBuffer(handle, src_buffer, dst_buffer, regions.size(), regions.data());
- }
-
- void CopyImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image,
- VkImageLayout dst_layout, Span<VkImageCopy> regions) const noexcept {
- dld->vkCmdCopyImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(),
- regions.data());
- }
-
- void CopyImageToBuffer(VkImage src_image, VkImageLayout src_layout, VkBuffer dst_buffer,
- Span<VkBufferImageCopy> regions) const noexcept {
- dld->vkCmdCopyImageToBuffer(handle, src_image, src_layout, dst_buffer, regions.size(),
- regions.data());
- }
-
- void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size,
- u32 data) const noexcept {
- dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data);
- }
-
- void PushConstants(VkPipelineLayout layout, VkShaderStageFlags flags, u32 offset, u32 size,
- const void* values) const noexcept {
- dld->vkCmdPushConstants(handle, layout, flags, offset, size, values);
- }
-
- void SetViewport(u32 first, Span<VkViewport> viewports) const noexcept {
- dld->vkCmdSetViewport(handle, first, viewports.size(), viewports.data());
- }
-
- void SetScissor(u32 first, Span<VkRect2D> scissors) const noexcept {
- dld->vkCmdSetScissor(handle, first, scissors.size(), scissors.data());
- }
-
- void SetBlendConstants(const float blend_constants[4]) const noexcept {
- dld->vkCmdSetBlendConstants(handle, blend_constants);
- }
-
- void SetStencilCompareMask(VkStencilFaceFlags face_mask, u32 compare_mask) const noexcept {
- dld->vkCmdSetStencilCompareMask(handle, face_mask, compare_mask);
- }
-
- void SetStencilReference(VkStencilFaceFlags face_mask, u32 reference) const noexcept {
- dld->vkCmdSetStencilReference(handle, face_mask, reference);
- }
-
- void SetStencilWriteMask(VkStencilFaceFlags face_mask, u32 write_mask) const noexcept {
- dld->vkCmdSetStencilWriteMask(handle, face_mask, write_mask);
- }
-
- void SetDepthBias(float constant_factor, float clamp, float slope_factor) const noexcept {
- dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor);
- }
-
- void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept {
- dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds);
- }
-
- void SetEvent(VkEvent event, VkPipelineStageFlags stage_flags) const noexcept {
- dld->vkCmdSetEvent(handle, event, stage_flags);
- }
-
- void WaitEvents(Span<VkEvent> events, VkPipelineStageFlags src_stage_mask,
- VkPipelineStageFlags dst_stage_mask, Span<VkMemoryBarrier> memory_barriers,
- Span<VkBufferMemoryBarrier> buffer_barriers,
- Span<VkImageMemoryBarrier> image_barriers) const noexcept {
- dld->vkCmdWaitEvents(handle, events.size(), events.data(), src_stage_mask, dst_stage_mask,
- memory_barriers.size(), memory_barriers.data(), buffer_barriers.size(),
- buffer_barriers.data(), image_barriers.size(), image_barriers.data());
- }
-
- void BindVertexBuffers2EXT(u32 first_binding, u32 binding_count, const VkBuffer* buffers,
- const VkDeviceSize* offsets, const VkDeviceSize* sizes,
- const VkDeviceSize* strides) const noexcept {
- dld->vkCmdBindVertexBuffers2EXT(handle, first_binding, binding_count, buffers, offsets,
- sizes, strides);
- }
-
- void SetCullModeEXT(VkCullModeFlags cull_mode) const noexcept {
- dld->vkCmdSetCullModeEXT(handle, cull_mode);
- }
-
- void SetDepthBoundsTestEnableEXT(bool enable) const noexcept {
- dld->vkCmdSetDepthBoundsTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
- }
-
- void SetDepthCompareOpEXT(VkCompareOp compare_op) const noexcept {
- dld->vkCmdSetDepthCompareOpEXT(handle, compare_op);
- }
-
- void SetDepthTestEnableEXT(bool enable) const noexcept {
- dld->vkCmdSetDepthTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
- }
-
- void SetDepthWriteEnableEXT(bool enable) const noexcept {
- dld->vkCmdSetDepthWriteEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
- }
-
- void SetFrontFaceEXT(VkFrontFace front_face) const noexcept {
- dld->vkCmdSetFrontFaceEXT(handle, front_face);
- }
-
- void SetPrimitiveTopologyEXT(VkPrimitiveTopology primitive_topology) const noexcept {
- dld->vkCmdSetPrimitiveTopologyEXT(handle, primitive_topology);
- }
-
- void SetStencilOpEXT(VkStencilFaceFlags face_mask, VkStencilOp fail_op, VkStencilOp pass_op,
- VkStencilOp depth_fail_op, VkCompareOp compare_op) const noexcept {
- dld->vkCmdSetStencilOpEXT(handle, face_mask, fail_op, pass_op, depth_fail_op, compare_op);
- }
-
- void SetStencilTestEnableEXT(bool enable) const noexcept {
- dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
- }
-
- void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers,
- const VkDeviceSize* offsets,
- const VkDeviceSize* sizes) const noexcept {
- dld->vkCmdBindTransformFeedbackBuffersEXT(handle, first, count, buffers, offsets, sizes);
- }
-
- void BeginTransformFeedbackEXT(u32 first_counter_buffer, u32 counter_buffers_count,
- const VkBuffer* counter_buffers,
- const VkDeviceSize* counter_buffer_offsets) const noexcept {
- dld->vkCmdBeginTransformFeedbackEXT(handle, first_counter_buffer, counter_buffers_count,
- counter_buffers, counter_buffer_offsets);
- }
-
- void EndTransformFeedbackEXT(u32 first_counter_buffer, u32 counter_buffers_count,
- const VkBuffer* counter_buffers,
- const VkDeviceSize* counter_buffer_offsets) const noexcept {
- dld->vkCmdEndTransformFeedbackEXT(handle, first_counter_buffer, counter_buffers_count,
- counter_buffers, counter_buffer_offsets);
- }
-
-private:
- VkCommandBuffer handle;
- const DeviceDispatch* dld;
-};
-
-std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProperties(
- const InstanceDispatch& dld);
-
-std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
- const InstanceDispatch& dld);
-
-} // namespace Vulkan::vk
diff --git a/src/video_core/sampler_cache.cpp b/src/video_core/sampler_cache.cpp
deleted file mode 100644
index 53c7ef12d..000000000
--- a/src/video_core/sampler_cache.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/cityhash.h"
-#include "common/common_types.h"
-#include "video_core/sampler_cache.h"
-
-namespace VideoCommon {
-
-std::size_t SamplerCacheKey::Hash() const {
- static_assert(sizeof(raw) % sizeof(u64) == 0);
- return static_cast<std::size_t>(
- Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64)));
-}
-
-bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const {
- return raw == rhs.raw;
-}
-
-} // namespace VideoCommon
diff --git a/src/video_core/sampler_cache.h b/src/video_core/sampler_cache.h
deleted file mode 100644
index cbe3ad071..000000000
--- a/src/video_core/sampler_cache.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstddef>
-#include <unordered_map>
-
-#include "video_core/textures/texture.h"
-
-namespace VideoCommon {
-
-struct SamplerCacheKey final : public Tegra::Texture::TSCEntry {
- std::size_t Hash() const;
-
- bool operator==(const SamplerCacheKey& rhs) const;
-
- bool operator!=(const SamplerCacheKey& rhs) const {
- return !operator==(rhs);
- }
-};
-
-} // namespace VideoCommon
-
-namespace std {
-
-template <>
-struct hash<VideoCommon::SamplerCacheKey> {
- std::size_t operator()(const VideoCommon::SamplerCacheKey& k) const noexcept {
- return k.Hash();
- }
-};
-
-} // namespace std
-
-namespace VideoCommon {
-
-template <typename SamplerType, typename SamplerStorageType>
-class SamplerCache {
-public:
- SamplerType GetSampler(const Tegra::Texture::TSCEntry& tsc) {
- const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc});
- auto& sampler = entry->second;
- if (is_cache_miss) {
- sampler = CreateSampler(tsc);
- }
- return ToSamplerType(sampler);
- }
-
-protected:
- virtual SamplerStorageType CreateSampler(const Tegra::Texture::TSCEntry& tsc) const = 0;
-
- virtual SamplerType ToSamplerType(const SamplerStorageType& sampler) const = 0;
-
-private:
- std::unordered_map<SamplerCacheKey, SamplerStorageType> cache;
-};
-
-} // namespace VideoCommon \ No newline at end of file
diff --git a/src/video_core/shader/ast.cpp b/src/video_core/shader/ast.cpp
index 3f96d9076..db11144c7 100644
--- a/src/video_core/shader/ast.cpp
+++ b/src/video_core/shader/ast.cpp
@@ -212,16 +212,15 @@ public:
}
void operator()(const ExprPredicate& expr) {
- inner += "P" + std::to_string(expr.predicate);
+ inner += fmt::format("P{}", expr.predicate);
}
void operator()(const ExprCondCode& expr) {
- u32 cc = static_cast<u32>(expr.cc);
- inner += "CC" + std::to_string(cc);
+ inner += fmt::format("CC{}", expr.cc);
}
void operator()(const ExprVar& expr) {
- inner += "V" + std::to_string(expr.var_index);
+ inner += fmt::format("V{}", expr.var_index);
}
void operator()(const ExprBoolean& expr) {
@@ -229,7 +228,7 @@ public:
}
void operator()(const ExprGprEqual& expr) {
- inner += "( gpr_" + std::to_string(expr.gpr) + " == " + std::to_string(expr.value) + ')';
+ inner += fmt::format("(gpr_{} == {})", expr.gpr, expr.value);
}
const std::string& GetResult() const {
@@ -374,8 +373,8 @@ std::string ASTManager::Print() const {
return printer.GetResult();
}
-ASTManager::ASTManager(bool full_decompile, bool disable_else_derivation)
- : full_decompile{full_decompile}, disable_else_derivation{disable_else_derivation} {};
+ASTManager::ASTManager(bool do_full_decompile, bool disable_else_derivation_)
+ : full_decompile{do_full_decompile}, disable_else_derivation{disable_else_derivation_} {}
ASTManager::~ASTManager() {
Clear();
diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h
index cca13bcde..dc49b369e 100644
--- a/src/video_core/shader/ast.h
+++ b/src/video_core/shader/ast.h
@@ -76,7 +76,7 @@ public:
class ASTIfThen {
public:
- explicit ASTIfThen(Expr condition) : condition{std::move(condition)} {}
+ explicit ASTIfThen(Expr condition_) : condition{std::move(condition_)} {}
Expr condition;
ASTZipper nodes{};
};
@@ -88,63 +88,68 @@ public:
class ASTBlockEncoded {
public:
- explicit ASTBlockEncoded(u32 start, u32 end) : start{start}, end{end} {}
+ explicit ASTBlockEncoded(u32 start_, u32 _) : start{start_}, end{_} {}
u32 start;
u32 end;
};
class ASTBlockDecoded {
public:
- explicit ASTBlockDecoded(NodeBlock&& new_nodes) : nodes(std::move(new_nodes)) {}
+ explicit ASTBlockDecoded(NodeBlock&& new_nodes_) : nodes(std::move(new_nodes_)) {}
NodeBlock nodes;
};
class ASTVarSet {
public:
- explicit ASTVarSet(u32 index, Expr condition) : index{index}, condition{std::move(condition)} {}
+ explicit ASTVarSet(u32 index_, Expr condition_)
+ : index{index_}, condition{std::move(condition_)} {}
+
u32 index;
Expr condition;
};
class ASTLabel {
public:
- explicit ASTLabel(u32 index) : index{index} {}
+ explicit ASTLabel(u32 index_) : index{index_} {}
u32 index;
bool unused{};
};
class ASTGoto {
public:
- explicit ASTGoto(Expr condition, u32 label) : condition{std::move(condition)}, label{label} {}
+ explicit ASTGoto(Expr condition_, u32 label_)
+ : condition{std::move(condition_)}, label{label_} {}
+
Expr condition;
u32 label;
};
class ASTDoWhile {
public:
- explicit ASTDoWhile(Expr condition) : condition{std::move(condition)} {}
+ explicit ASTDoWhile(Expr condition_) : condition{std::move(condition_)} {}
Expr condition;
ASTZipper nodes{};
};
class ASTReturn {
public:
- explicit ASTReturn(Expr condition, bool kills)
- : condition{std::move(condition)}, kills{kills} {}
+ explicit ASTReturn(Expr condition_, bool kills_)
+ : condition{std::move(condition_)}, kills{kills_} {}
+
Expr condition;
bool kills;
};
class ASTBreak {
public:
- explicit ASTBreak(Expr condition) : condition{std::move(condition)} {}
+ explicit ASTBreak(Expr condition_) : condition{std::move(condition_)} {}
Expr condition;
};
class ASTBase {
public:
- explicit ASTBase(ASTNode parent, ASTData data)
- : data{std::move(data)}, parent{std::move(parent)} {}
+ explicit ASTBase(ASTNode parent_, ASTData data_)
+ : data{std::move(data_)}, parent{std::move(parent_)} {}
template <class U, class... Args>
static ASTNode Make(ASTNode parent, Args&&... args) {
@@ -199,55 +204,48 @@ public:
}
std::optional<u32> GetGotoLabel() const {
- auto inner = std::get_if<ASTGoto>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTGoto>(&data)) {
return {inner->label};
}
- return {};
+ return std::nullopt;
}
Expr GetGotoCondition() const {
- auto inner = std::get_if<ASTGoto>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTGoto>(&data)) {
return inner->condition;
}
return nullptr;
}
void MarkLabelUnused() {
- auto inner = std::get_if<ASTLabel>(&data);
- if (inner) {
+ if (auto* inner = std::get_if<ASTLabel>(&data)) {
inner->unused = true;
}
}
bool IsLabelUnused() const {
- auto inner = std::get_if<ASTLabel>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTLabel>(&data)) {
return inner->unused;
}
return true;
}
std::optional<u32> GetLabelIndex() const {
- auto inner = std::get_if<ASTLabel>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTLabel>(&data)) {
return {inner->index};
}
- return {};
+ return std::nullopt;
}
Expr GetIfCondition() const {
- auto inner = std::get_if<ASTIfThen>(&data);
- if (inner) {
+ if (const auto* inner = std::get_if<ASTIfThen>(&data)) {
return inner->condition;
}
return nullptr;
}
void SetGotoCondition(Expr new_condition) {
- auto inner = std::get_if<ASTGoto>(&data);
- if (inner) {
+ if (auto* inner = std::get_if<ASTGoto>(&data)) {
inner->condition = std::move(new_condition);
}
}
@@ -307,7 +305,7 @@ private:
class ASTManager final {
public:
- ASTManager(bool full_decompile, bool disable_else_derivation);
+ explicit ASTManager(bool do_full_decompile, bool disable_else_derivation_);
~ASTManager();
ASTManager(const ASTManager& o) = delete;
diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp
index f815584f7..02adcf9c7 100644
--- a/src/video_core/shader/async_shaders.cpp
+++ b/src/video_core/shader/async_shaders.cpp
@@ -13,21 +13,22 @@
namespace VideoCommon::Shader {
-AsyncShaders::AsyncShaders(Core::Frontend::EmuWindow& emu_window) : emu_window(emu_window) {}
+AsyncShaders::AsyncShaders(Core::Frontend::EmuWindow& emu_window_) : emu_window(emu_window_) {}
AsyncShaders::~AsyncShaders() {
KillWorkers();
}
void AsyncShaders::AllocateWorkers() {
- // Max worker threads we should allow
- constexpr u32 MAX_THREADS = 4;
- // Deduce how many threads we can use
- const u32 threads_used = std::thread::hardware_concurrency() / 4;
- // Always allow at least 1 thread regardless of our settings
- const auto max_worker_count = std::max(1U, threads_used);
- // Don't use more than MAX_THREADS
- const auto num_workers = std::min(max_worker_count, MAX_THREADS);
+ // Use at least one thread
+ u32 num_workers = 1;
+
+ // Deduce how many more threads we can use
+ const u32 thread_count = std::thread::hardware_concurrency();
+ if (thread_count >= 8) {
+ // Increase async workers by 1 for every 2 threads >= 8
+ num_workers += 1 + (thread_count - 8) / 2;
+ }
// If we already have workers queued, ignore
if (num_workers == worker_threads.size()) {
@@ -42,8 +43,8 @@ void AsyncShaders::AllocateWorkers() {
// Create workers
for (std::size_t i = 0; i < num_workers; i++) {
context_list.push_back(emu_window.CreateSharedContext());
- worker_threads.push_back(
- std::thread(&AsyncShaders::ShaderCompilerThread, this, context_list[i].get()));
+ worker_threads.emplace_back(&AsyncShaders::ShaderCompilerThread, this,
+ context_list[i].get());
}
}
@@ -63,6 +64,7 @@ void AsyncShaders::FreeWorkers() {
void AsyncShaders::KillWorkers() {
is_thread_exiting.store(true);
+ cv.notify_all();
for (auto& thread : worker_threads) {
thread.detach();
}
@@ -73,11 +75,11 @@ void AsyncShaders::KillWorkers() {
worker_threads.clear();
}
-bool AsyncShaders::HasWorkQueued() {
+bool AsyncShaders::HasWorkQueued() const {
return !pending_queue.empty();
}
-bool AsyncShaders::HasCompletedWork() {
+bool AsyncShaders::HasCompletedWork() const {
std::shared_lock lock{completed_mutex};
return !finished_work.empty();
}
@@ -102,11 +104,10 @@ bool AsyncShaders::IsShaderAsync(const Tegra::GPU& gpu) const {
}
std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() {
- std::vector<AsyncShaders::Result> results;
+ std::vector<Result> results;
{
std::unique_lock lock{completed_mutex};
- results.assign(std::make_move_iterator(finished_work.begin()),
- std::make_move_iterator(finished_work.end()));
+ results = std::move(finished_work);
finished_work.clear();
}
return results;
@@ -115,11 +116,10 @@ std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() {
void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device,
Tegra::Engines::ShaderType shader_type, u64 uid,
std::vector<u64> code, std::vector<u64> code_b,
- u32 main_offset,
- VideoCommon::Shader::CompilerSettings compiler_settings,
- const VideoCommon::Shader::Registry& registry,
- VAddr cpu_addr) {
- WorkerParams params{
+ u32 main_offset, CompilerSettings compiler_settings,
+ const Registry& registry, VAddr cpu_addr) {
+ std::unique_lock lock(queue_mutex);
+ pending_queue.push({
.backend = device.UseAssemblyShaders() ? Backend::GLASM : Backend::OpenGL,
.device = &device,
.shader_type = shader_type,
@@ -130,35 +130,48 @@ void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device,
.compiler_settings = compiler_settings,
.registry = registry,
.cpu_address = cpu_addr,
- };
- std::unique_lock lock(queue_mutex);
- pending_queue.push(std::move(params));
+ .pp_cache = nullptr,
+ .vk_device = nullptr,
+ .scheduler = nullptr,
+ .descriptor_pool = nullptr,
+ .update_descriptor_queue = nullptr,
+ .bindings{},
+ .program{},
+ .key{},
+ .num_color_buffers = 0,
+ });
cv.notify_one();
}
void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache,
- const Vulkan::VKDevice& device, Vulkan::VKScheduler& scheduler,
+ const Vulkan::Device& device, Vulkan::VKScheduler& scheduler,
Vulkan::VKDescriptorPool& descriptor_pool,
Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
- Vulkan::VKRenderPassCache& renderpass_cache,
std::vector<VkDescriptorSetLayoutBinding> bindings,
Vulkan::SPIRVProgram program,
- Vulkan::GraphicsPipelineCacheKey key) {
- WorkerParams params{
+ Vulkan::GraphicsPipelineCacheKey key, u32 num_color_buffers) {
+ std::unique_lock lock(queue_mutex);
+ pending_queue.push({
.backend = Backend::Vulkan,
+ .device = nullptr,
+ .shader_type{},
+ .uid = 0,
+ .code{},
+ .code_b{},
+ .main_offset = 0,
+ .compiler_settings{},
+ .registry{},
+ .cpu_address = 0,
.pp_cache = pp_cache,
.vk_device = &device,
.scheduler = &scheduler,
.descriptor_pool = &descriptor_pool,
.update_descriptor_queue = &update_descriptor_queue,
- .renderpass_cache = &renderpass_cache,
- .bindings = bindings,
- .program = program,
+ .bindings = std::move(bindings),
+ .program = std::move(program),
.key = key,
- };
-
- std::unique_lock lock(queue_mutex);
- pending_queue.push(std::move(params));
+ .num_color_buffers = num_color_buffers,
+ });
cv.notify_one();
}
@@ -210,8 +223,8 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context
} else if (work.backend == Backend::Vulkan) {
auto pipeline = std::make_unique<Vulkan::VKGraphicsPipeline>(
*work.vk_device, *work.scheduler, *work.descriptor_pool,
- *work.update_descriptor_queue, *work.renderpass_cache, work.key, work.bindings,
- work.program);
+ *work.update_descriptor_queue, work.key, work.bindings, work.program,
+ work.num_color_buffers);
work.pp_cache->EmplacePipeline(std::move(pipeline));
}
diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h
index d5ae814d5..7fdff6e56 100644
--- a/src/video_core/shader/async_shaders.h
+++ b/src/video_core/shader/async_shaders.h
@@ -5,19 +5,19 @@
#pragma once
#include <condition_variable>
-#include <deque>
#include <memory>
#include <shared_mutex>
#include <thread>
-#include "common/bit_field.h"
+
+#include <glad/glad.h>
+
#include "common/common_types.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
-#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
-#include "video_core/renderer_vulkan/vk_update_descriptor.h"
+#include "video_core/vulkan_common/vulkan_device.h"
namespace Core::Frontend {
class EmuWindow;
@@ -57,7 +57,7 @@ public:
Tegra::Engines::ShaderType shader_type;
};
- explicit AsyncShaders(Core::Frontend::EmuWindow& emu_window);
+ explicit AsyncShaders(Core::Frontend::EmuWindow& emu_window_);
~AsyncShaders();
/// Start up shader worker threads
@@ -70,34 +70,34 @@ public:
void KillWorkers();
/// Check to see if any shaders have actually been compiled
- bool HasCompletedWork();
+ [[nodiscard]] bool HasCompletedWork() const;
/// Deduce if a shader can be build on another thread of MUST be built in sync. We cannot build
/// every shader async as some shaders are only built and executed once. We try to "guess" which
/// shader would be used only once
- bool IsShaderAsync(const Tegra::GPU& gpu) const;
+ [[nodiscard]] bool IsShaderAsync(const Tegra::GPU& gpu) const;
/// Pulls completed compiled shaders
- std::vector<Result> GetCompletedWork();
+ [[nodiscard]] std::vector<Result> GetCompletedWork();
void QueueOpenGLShader(const OpenGL::Device& device, Tegra::Engines::ShaderType shader_type,
u64 uid, std::vector<u64> code, std::vector<u64> code_b, u32 main_offset,
- VideoCommon::Shader::CompilerSettings compiler_settings,
- const VideoCommon::Shader::Registry& registry, VAddr cpu_addr);
+ CompilerSettings compiler_settings, const Registry& registry,
+ VAddr cpu_addr);
- void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& device,
+ void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::Device& device,
Vulkan::VKScheduler& scheduler,
Vulkan::VKDescriptorPool& descriptor_pool,
Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
- Vulkan::VKRenderPassCache& renderpass_cache,
std::vector<VkDescriptorSetLayoutBinding> bindings,
- Vulkan::SPIRVProgram program, Vulkan::GraphicsPipelineCacheKey key);
+ Vulkan::SPIRVProgram program, Vulkan::GraphicsPipelineCacheKey key,
+ u32 num_color_buffers);
private:
void ShaderCompilerThread(Core::Frontend::GraphicsContext* context);
/// Check our worker queue to see if we have any work queued already
- bool HasWorkQueued();
+ [[nodiscard]] bool HasWorkQueued() const;
struct WorkerParams {
Backend backend;
@@ -108,30 +108,30 @@ private:
std::vector<u64> code;
std::vector<u64> code_b;
u32 main_offset;
- VideoCommon::Shader::CompilerSettings compiler_settings;
- std::optional<VideoCommon::Shader::Registry> registry;
+ CompilerSettings compiler_settings;
+ std::optional<Registry> registry;
VAddr cpu_address;
// For Vulkan
Vulkan::VKPipelineCache* pp_cache;
- const Vulkan::VKDevice* vk_device;
+ const Vulkan::Device* vk_device;
Vulkan::VKScheduler* scheduler;
Vulkan::VKDescriptorPool* descriptor_pool;
Vulkan::VKUpdateDescriptorQueue* update_descriptor_queue;
- Vulkan::VKRenderPassCache* renderpass_cache;
std::vector<VkDescriptorSetLayoutBinding> bindings;
Vulkan::SPIRVProgram program;
Vulkan::GraphicsPipelineCacheKey key;
+ u32 num_color_buffers;
};
std::condition_variable cv;
- std::mutex queue_mutex;
- std::shared_mutex completed_mutex;
+ mutable std::mutex queue_mutex;
+ mutable std::shared_mutex completed_mutex;
std::atomic<bool> is_thread_exiting{};
std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list;
std::vector<std::thread> worker_threads;
std::queue<WorkerParams> pending_queue;
- std::vector<AsyncShaders::Result> finished_work;
+ std::vector<Result> finished_work;
Core::Frontend::EmuWindow& emu_window;
};
diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp
index 336397cdb..43d965f2f 100644
--- a/src/video_core/shader/control_flow.cpp
+++ b/src/video_core/shader/control_flow.cpp
@@ -66,8 +66,8 @@ struct BlockInfo {
};
struct CFGRebuildState {
- explicit CFGRebuildState(const ProgramCode& program_code, u32 start, Registry& registry)
- : program_code{program_code}, registry{registry}, start{start} {}
+ explicit CFGRebuildState(const ProgramCode& program_code_, u32 start_, Registry& registry_)
+ : program_code{program_code_}, registry{registry_}, start{start_} {}
const ProgramCode& program_code;
Registry& registry;
@@ -241,10 +241,10 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address)
ParseInfo parse_info{};
SingleBranch single_branch{};
- const auto insert_label = [](CFGRebuildState& state, u32 address) {
- const auto pair = state.labels.emplace(address);
+ const auto insert_label = [](CFGRebuildState& rebuild_state, u32 label_address) {
+ const auto pair = rebuild_state.labels.emplace(label_address);
if (pair.second) {
- state.inspect_queries.push_back(address);
+ rebuild_state.inspect_queries.push_back(label_address);
}
};
@@ -257,7 +257,7 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address)
single_branch.ignore = false;
break;
}
- if (state.registered.count(offset) != 0) {
+ if (state.registered.contains(offset)) {
single_branch.address = offset;
single_branch.ignore = true;
break;
@@ -547,13 +547,13 @@ bool TryQuery(CFGRebuildState& state) {
gather_labels(q2.ssy_stack, state.ssy_labels, block);
gather_labels(q2.pbk_stack, state.pbk_labels, block);
if (std::holds_alternative<SingleBranch>(*block.branch)) {
- const auto branch = std::get_if<SingleBranch>(block.branch.get());
+ auto* branch = std::get_if<SingleBranch>(block.branch.get());
if (!branch->condition.IsUnconditional()) {
q2.address = block.end + 1;
state.queries.push_back(q2);
}
- Query conditional_query{q2};
+ auto& conditional_query = state.queries.emplace_back(q2);
if (branch->is_sync) {
if (branch->address == unassigned_branch) {
branch->address = conditional_query.ssy_stack.top();
@@ -567,21 +567,21 @@ bool TryQuery(CFGRebuildState& state) {
conditional_query.pbk_stack.pop();
}
conditional_query.address = branch->address;
- state.queries.push_back(std::move(conditional_query));
return true;
}
- const auto multi_branch = std::get_if<MultiBranch>(block.branch.get());
+
+ const auto* multi_branch = std::get_if<MultiBranch>(block.branch.get());
for (const auto& branch_case : multi_branch->branches) {
- Query conditional_query{q2};
+ auto& conditional_query = state.queries.emplace_back(q2);
conditional_query.address = branch_case.address;
- state.queries.push_back(std::move(conditional_query));
}
+
return true;
}
void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
- const auto get_expr = ([&](const Condition& cond) -> Expr {
- Expr result{};
+ const auto get_expr = [](const Condition& cond) -> Expr {
+ Expr result;
if (cond.cc != ConditionCode::T) {
result = MakeExpr<ExprCondCode>(cond.cc);
}
@@ -594,10 +594,10 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
}
Expr extra = MakeExpr<ExprPredicate>(pred);
if (negate) {
- extra = MakeExpr<ExprNot>(extra);
+ extra = MakeExpr<ExprNot>(std::move(extra));
}
if (result) {
- return MakeExpr<ExprAnd>(extra, result);
+ return MakeExpr<ExprAnd>(std::move(extra), std::move(result));
}
return extra;
}
@@ -605,9 +605,10 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
return result;
}
return MakeExpr<ExprBoolean>(true);
- });
+ };
+
if (std::holds_alternative<SingleBranch>(*branch_info)) {
- const auto branch = std::get_if<SingleBranch>(branch_info.get());
+ const auto* branch = std::get_if<SingleBranch>(branch_info.get());
if (branch->address < 0) {
if (branch->kill) {
mm.InsertReturn(get_expr(branch->condition), true);
@@ -619,7 +620,7 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
mm.InsertGoto(get_expr(branch->condition), branch->address);
return;
}
- const auto multi_branch = std::get_if<MultiBranch>(branch_info.get());
+ const auto* multi_branch = std::get_if<MultiBranch>(branch_info.get());
for (const auto& branch_case : multi_branch->branches) {
mm.InsertGoto(MakeExpr<ExprGprEqual>(multi_branch->gpr, branch_case.cmp_value),
branch_case.address);
@@ -631,12 +632,12 @@ void DecompileShader(CFGRebuildState& state) {
for (auto label : state.labels) {
state.manager->DeclareLabel(label);
}
- for (auto& block : state.block_info) {
- if (state.labels.count(block.start) != 0) {
+ for (const auto& block : state.block_info) {
+ if (state.labels.contains(block.start)) {
state.manager->InsertLabel(block.start);
}
const bool ignore = BlockBranchIsIgnored(block.branch);
- u32 end = ignore ? block.end + 1 : block.end;
+ const u32 end = ignore ? block.end + 1 : block.end;
state.manager->InsertBlock(block.start, end);
if (!ignore) {
InsertBranch(*state.manager, block.branch);
@@ -736,7 +737,7 @@ std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
auto back = result_out->blocks.begin();
auto next = std::next(back);
while (next != result_out->blocks.end()) {
- if (state.labels.count(next->start) == 0 && next->start == back->end + 1) {
+ if (!state.labels.contains(next->start) && next->start == back->end + 1) {
back->end = next->end;
next = result_out->blocks.erase(next);
continue;
diff --git a/src/video_core/shader/control_flow.h b/src/video_core/shader/control_flow.h
index 62a3510d8..37bf96492 100644
--- a/src/video_core/shader/control_flow.h
+++ b/src/video_core/shader/control_flow.h
@@ -42,10 +42,10 @@ struct Condition {
class SingleBranch {
public:
SingleBranch() = default;
- SingleBranch(Condition condition, s32 address, bool kill, bool is_sync, bool is_brk,
- bool ignore)
- : condition{condition}, address{address}, kill{kill}, is_sync{is_sync}, is_brk{is_brk},
- ignore{ignore} {}
+ explicit SingleBranch(Condition condition_, s32 address_, bool kill_, bool is_sync_,
+ bool is_brk_, bool ignore_)
+ : condition{condition_}, address{address_}, kill{kill_}, is_sync{is_sync_}, is_brk{is_brk_},
+ ignore{ignore_} {}
bool operator==(const SingleBranch& b) const {
return std::tie(condition, address, kill, is_sync, is_brk, ignore) ==
@@ -65,15 +65,15 @@ public:
};
struct CaseBranch {
- CaseBranch(u32 cmp_value, u32 address) : cmp_value{cmp_value}, address{address} {}
+ explicit CaseBranch(u32 cmp_value_, u32 address_) : cmp_value{cmp_value_}, address{address_} {}
u32 cmp_value;
u32 address;
};
class MultiBranch {
public:
- MultiBranch(u32 gpr, std::vector<CaseBranch>&& branches)
- : gpr{gpr}, branches{std::move(branches)} {}
+ explicit MultiBranch(u32 gpr_, std::vector<CaseBranch>&& branches_)
+ : gpr{gpr_}, branches{std::move(branches_)} {}
u32 gpr{};
std::vector<CaseBranch> branches{};
diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp
index eeac328a6..6576d1208 100644
--- a/src/video_core/shader/decode.cpp
+++ b/src/video_core/shader/decode.cpp
@@ -25,7 +25,7 @@ using Tegra::Shader::OpCode;
namespace {
void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile& gpu_driver,
- const std::list<Sampler>& used_samplers) {
+ const std::list<SamplerEntry>& used_samplers) {
if (gpu_driver.IsTextureHandlerSizeKnown() || used_samplers.size() <= 1) {
return;
}
@@ -43,9 +43,9 @@ void DeduceTextureHandlerSize(VideoCore::GuestDriverProfile& gpu_driver,
}
}
-std::optional<u32> TryDeduceSamplerSize(const Sampler& sampler_to_deduce,
+std::optional<u32> TryDeduceSamplerSize(const SamplerEntry& sampler_to_deduce,
VideoCore::GuestDriverProfile& gpu_driver,
- const std::list<Sampler>& used_samplers) {
+ const std::list<SamplerEntry>& used_samplers) {
const u32 base_offset = sampler_to_deduce.offset;
u32 max_offset{std::numeric_limits<u32>::max()};
for (const auto& sampler : used_samplers) {
@@ -66,7 +66,7 @@ std::optional<u32> TryDeduceSamplerSize(const Sampler& sampler_to_deduce,
class ASTDecoder {
public:
- ASTDecoder(ShaderIR& ir) : ir(ir) {}
+ explicit ASTDecoder(ShaderIR& ir_) : ir(ir_) {}
void operator()(ASTProgram& ast) {
ASTNode current = ast.nodes.GetFirst();
@@ -153,8 +153,8 @@ void ShaderIR::Decode() {
const auto& blocks = shader_info.blocks;
NodeBlock current_block;
u32 current_label = static_cast<u32>(exit_branch);
- for (auto& block : blocks) {
- if (shader_info.labels.count(block.start) != 0) {
+ for (const auto& block : blocks) {
+ if (shader_info.labels.contains(block.start)) {
insert_block(current_block, current_label);
current_block.clear();
current_label = block.start;
diff --git a/src/video_core/shader/decode/arithmetic.cpp b/src/video_core/shader/decode/arithmetic.cpp
index 4db329fa5..15eb700e7 100644
--- a/src/video_core/shader/decode/arithmetic.cpp
+++ b/src/video_core/shader/decode/arithmetic.cpp
@@ -110,8 +110,7 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) {
case SubOp::Sqrt:
return Operation(OperationCode::FSqrt, PRECISE, op_a);
default:
- UNIMPLEMENTED_MSG("Unhandled MUFU sub op={0:x}",
- static_cast<unsigned>(instr.sub_op.Value()));
+ UNIMPLEMENTED_MSG("Unhandled MUFU sub op={0:x}", instr.sub_op.Value());
return Immediate(0);
}
}();
@@ -137,7 +136,8 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::FCMP_RR:
- case OpCode::Id::FCMP_RC: {
+ case OpCode::Id::FCMP_RC:
+ case OpCode::Id::FCMP_IMMR: {
UNIMPLEMENTED_IF(instr.fcmp.ftz == 0);
Node op_c = GetRegister(instr.gpr39);
Node comp = GetPredicateComparisonFloat(instr.fcmp.cond, std::move(op_c), Immediate(0.0f));
diff --git a/src/video_core/shader/decode/arithmetic_half.cpp b/src/video_core/shader/decode/arithmetic_half.cpp
index a276aee44..88103fede 100644
--- a/src/video_core/shader/decode/arithmetic_half.cpp
+++ b/src/video_core/shader/decode/arithmetic_half.cpp
@@ -53,6 +53,9 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
absolute_a = ((instr.value >> 44) & 1) != 0;
absolute_b = ((instr.value >> 54) & 1) != 0;
break;
+ default:
+ UNREACHABLE();
+ break;
}
Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a);
diff --git a/src/video_core/shader/decode/arithmetic_integer.cpp b/src/video_core/shader/decode/arithmetic_integer.cpp
index 73155966f..7b5bb7003 100644
--- a/src/video_core/shader/decode/arithmetic_integer.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer.cpp
@@ -83,7 +83,7 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
case IAdd3Height::UpperHalfWord:
return BitfieldExtract(value, 16, 16);
default:
- UNIMPLEMENTED_MSG("Unhandled IADD3 height: {}", static_cast<u32>(height));
+ UNIMPLEMENTED_MSG("Unhandled IADD3 height: {}", height);
return Immediate(0);
}
};
@@ -258,7 +258,7 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
case OpCode::Id::LEA_IMM:
case OpCode::Id::LEA_RZ:
case OpCode::Id::LEA_HI: {
- auto [op_a, op_b, op_c] = [&]() -> std::tuple<Node, Node, Node> {
+ auto [op_a_, op_b_, op_c_] = [&]() -> std::tuple<Node, Node, Node> {
switch (opcode->get().GetId()) {
case OpCode::Id::LEA_R2: {
return {GetRegister(instr.gpr20), GetRegister(instr.gpr39),
@@ -294,8 +294,9 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
UNIMPLEMENTED_IF_MSG(instr.lea.pred48 != static_cast<u64>(Pred::UnusedIndex),
"Unhandled LEA Predicate");
- Node value = Operation(OperationCode::ILogicalShiftLeft, std::move(op_a), std::move(op_c));
- value = Operation(OperationCode::IAdd, std::move(op_b), std::move(value));
+ Node value =
+ Operation(OperationCode::ILogicalShiftLeft, std::move(op_a_), std::move(op_c_));
+ value = Operation(OperationCode::IAdd, std::move(op_b_), std::move(value));
SetRegister(bb, instr.gpr0, std::move(value));
break;
diff --git a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
index 73880db0e..73580277a 100644
--- a/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
+++ b/src/video_core/shader/decode/arithmetic_integer_immediate.cpp
@@ -28,23 +28,26 @@ u32 ShaderIR::DecodeArithmeticIntegerImmediate(NodeBlock& bb, u32 pc) {
case OpCode::Id::IADD32I: {
UNIMPLEMENTED_IF_MSG(instr.iadd32i.saturate, "IADD32I saturation is not implemented");
- op_a = GetOperandAbsNegInteger(op_a, false, instr.iadd32i.negate_a, true);
+ op_a = GetOperandAbsNegInteger(std::move(op_a), false, instr.iadd32i.negate_a != 0, true);
- const Node value = Operation(OperationCode::IAdd, PRECISE, op_a, op_b);
+ Node value = Operation(OperationCode::IAdd, PRECISE, std::move(op_a), std::move(op_b));
- SetInternalFlagsFromInteger(bb, value, instr.op_32.generates_cc);
- SetRegister(bb, instr.gpr0, value);
+ SetInternalFlagsFromInteger(bb, value, instr.op_32.generates_cc != 0);
+ SetRegister(bb, instr.gpr0, std::move(value));
break;
}
case OpCode::Id::LOP32I: {
- if (instr.alu.lop32i.invert_a)
- op_a = Operation(OperationCode::IBitwiseNot, NO_PRECISE, op_a);
+ if (instr.alu.lop32i.invert_a) {
+ op_a = Operation(OperationCode::IBitwiseNot, NO_PRECISE, std::move(op_a));
+ }
- if (instr.alu.lop32i.invert_b)
- op_b = Operation(OperationCode::IBitwiseNot, NO_PRECISE, op_b);
+ if (instr.alu.lop32i.invert_b) {
+ op_b = Operation(OperationCode::IBitwiseNot, NO_PRECISE, std::move(op_b));
+ }
- WriteLogicOperation(bb, instr.gpr0, instr.alu.lop32i.operation, op_a, op_b,
- PredicateResultMode::None, Pred::UnusedIndex, instr.op_32.generates_cc);
+ WriteLogicOperation(bb, instr.gpr0, instr.alu.lop32i.operation, std::move(op_a),
+ std::move(op_b), PredicateResultMode::None, Pred::UnusedIndex,
+ instr.op_32.generates_cc != 0);
break;
}
default:
@@ -58,18 +61,18 @@ u32 ShaderIR::DecodeArithmeticIntegerImmediate(NodeBlock& bb, u32 pc) {
void ShaderIR::WriteLogicOperation(NodeBlock& bb, Register dest, LogicOperation logic_op, Node op_a,
Node op_b, PredicateResultMode predicate_mode, Pred predicate,
bool sets_cc) {
- const Node result = [&]() {
+ Node result = [&] {
switch (logic_op) {
case LogicOperation::And:
- return Operation(OperationCode::IBitwiseAnd, PRECISE, op_a, op_b);
+ return Operation(OperationCode::IBitwiseAnd, PRECISE, std::move(op_a), std::move(op_b));
case LogicOperation::Or:
- return Operation(OperationCode::IBitwiseOr, PRECISE, op_a, op_b);
+ return Operation(OperationCode::IBitwiseOr, PRECISE, std::move(op_a), std::move(op_b));
case LogicOperation::Xor:
- return Operation(OperationCode::IBitwiseXor, PRECISE, op_a, op_b);
+ return Operation(OperationCode::IBitwiseXor, PRECISE, std::move(op_a), std::move(op_b));
case LogicOperation::PassB:
return op_b;
default:
- UNIMPLEMENTED_MSG("Unimplemented logic operation={}", static_cast<u32>(logic_op));
+ UNIMPLEMENTED_MSG("Unimplemented logic operation={}", logic_op);
return Immediate(0);
}
}();
@@ -84,13 +87,12 @@ void ShaderIR::WriteLogicOperation(NodeBlock& bb, Register dest, LogicOperation
return;
case PredicateResultMode::NotZero: {
// Set the predicate to true if the result is not zero.
- const Node compare = Operation(OperationCode::LogicalINotEqual, result, Immediate(0));
- SetPredicate(bb, static_cast<u64>(predicate), compare);
+ Node compare = Operation(OperationCode::LogicalINotEqual, std::move(result), Immediate(0));
+ SetPredicate(bb, static_cast<u64>(predicate), std::move(compare));
break;
}
default:
- UNIMPLEMENTED_MSG("Unimplemented predicate result mode: {}",
- static_cast<u32>(predicate_mode));
+ UNIMPLEMENTED_MSG("Unimplemented predicate result mode: {}", predicate_mode);
}
}
diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp
index b9989c88c..fea7a54df 100644
--- a/src/video_core/shader/decode/conversion.cpp
+++ b/src/video_core/shader/decode/conversion.cpp
@@ -244,7 +244,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
return Operation(OperationCode::FTrunc, value);
default:
UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}",
- static_cast<u32>(instr.conversion.f2f.rounding.Value()));
+ instr.conversion.f2f.rounding.Value());
return value;
}
}();
@@ -300,7 +300,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) {
return Operation(OperationCode::FTrunc, PRECISE, value);
default:
UNIMPLEMENTED_MSG("Unimplemented F2I rounding mode {}",
- static_cast<u32>(instr.conversion.f2i.rounding.Value()));
+ instr.conversion.f2i.rounding.Value());
return Immediate(0);
}
}();
diff --git a/src/video_core/shader/decode/half_set.cpp b/src/video_core/shader/decode/half_set.cpp
index b2e88fa20..fa83108cd 100644
--- a/src/video_core/shader/decode/half_set.cpp
+++ b/src/video_core/shader/decode/half_set.cpp
@@ -22,13 +22,13 @@ u32 ShaderIR::DecodeHalfSet(NodeBlock& bb, u32 pc) {
const Instruction instr = {program_code[pc]};
const auto opcode = OpCode::Decode(instr);
- PredCondition cond;
- bool bf;
- bool ftz;
- bool neg_a;
- bool abs_a;
- bool neg_b;
- bool abs_b;
+ PredCondition cond{};
+ bool bf = false;
+ bool ftz = false;
+ bool neg_a = false;
+ bool abs_a = false;
+ bool neg_b = false;
+ bool abs_b = false;
switch (opcode->get().GetId()) {
case OpCode::Id::HSET2_C:
case OpCode::Id::HSET2_IMM:
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp
index e75ca4fdb..5470e8cf4 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -119,6 +119,8 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor,
return descriptor.r_type;
}
break;
+ default:
+ break;
}
UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
return ComponentType::FLOAT;
@@ -210,19 +212,20 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) {
return 0;
case TextureFormat::R8G24:
if (component == 0) {
- return 8;
+ return 24;
}
if (component == 1) {
- return 24;
+ return 8;
}
return 0;
case TextureFormat::R8G8:
return (component == 0 || component == 1) ? 8 : 0;
case TextureFormat::G4R4:
return (component == 0 || component == 1) ? 4 : 0;
+ default:
+ UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
+ return 0;
}
- UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
- return 0;
}
std::size_t GetImageComponentMask(TextureFormat format) {
@@ -257,9 +260,10 @@ std::size_t GetImageComponentMask(TextureFormat format) {
case TextureFormat::R8:
case TextureFormat::R1:
return std::size_t{R};
+ default:
+ UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
+ return std::size_t{R | G | B | A};
}
- UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
- return std::size_t{R | G | B | A};
}
std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) {
@@ -354,9 +358,9 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
instr.suldst.GetStoreDataLayout() != StoreType::Bits64);
auto descriptor = [this, instr] {
- std::optional<Tegra::Engines::SamplerDescriptor> descriptor;
+ std::optional<Tegra::Engines::SamplerDescriptor> sampler_descriptor;
if (instr.suldst.is_immediate) {
- descriptor =
+ sampler_descriptor =
registry.ObtainBoundSampler(static_cast<u32>(instr.image.index.Value()));
} else {
const Node image_register = GetRegister(instr.gpr39);
@@ -364,12 +368,12 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
static_cast<s64>(global_code.size()));
const auto buffer = std::get<1>(result);
const auto offset = std::get<2>(result);
- descriptor = registry.ObtainBindlessSampler(buffer, offset);
+ sampler_descriptor = registry.ObtainBindlessSampler(buffer, offset);
}
- if (!descriptor) {
+ if (!sampler_descriptor) {
UNREACHABLE_MSG("Failed to obtain image descriptor");
}
- return *descriptor;
+ return *sampler_descriptor;
}();
const auto comp_mask = GetImageComponentMask(descriptor.format);
@@ -463,7 +467,10 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
return OperationCode::AtomicImageXor;
case Tegra::Shader::ImageAtomicOperation::Exch:
return OperationCode::AtomicImageExchange;
+ default:
+ break;
}
+ break;
default:
break;
}
@@ -490,11 +497,12 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
return pc;
}
-Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {
+ImageEntry& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {
const auto offset = static_cast<u32>(image.index.Value());
- const auto it = std::find_if(std::begin(used_images), std::end(used_images),
- [offset](const Image& entry) { return entry.offset == offset; });
+ const auto it =
+ std::find_if(std::begin(used_images), std::end(used_images),
+ [offset](const ImageEntry& entry) { return entry.offset == offset; });
if (it != std::end(used_images)) {
ASSERT(!it->is_bindless && it->type == type);
return *it;
@@ -504,7 +512,7 @@ Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType t
return used_images.emplace_back(next_index, offset, type);
}
-Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) {
+ImageEntry& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) {
const Node image_register = GetRegister(reg);
const auto result =
TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()));
@@ -513,7 +521,7 @@ Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::Im
const auto offset = std::get<2>(result);
const auto it = std::find_if(std::begin(used_images), std::end(used_images),
- [buffer, offset](const Image& entry) {
+ [buffer, offset](const ImageEntry& entry) {
return entry.buffer == buffer && entry.offset == offset;
});
if (it != std::end(used_images)) {
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index e2bba88dd..50f4e7d35 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -47,7 +47,7 @@ OperationCode GetAtomOperation(AtomicOp op) {
case AtomicOp::Exch:
return OperationCode::AtomicIExchange;
default:
- UNIMPLEMENTED_MSG("op={}", static_cast<int>(op));
+ UNIMPLEMENTED_MSG("op={}", op);
return OperationCode::AtomicIAdd;
}
}
@@ -83,7 +83,7 @@ u32 GetMemorySize(Tegra::Shader::UniformType uniform_type) {
case Tegra::Shader::UniformType::UnsignedQuad:
return 128;
default:
- UNIMPLEMENTED_MSG("Unimplemented size={}!", static_cast<u32>(uniform_type));
+ UNIMPLEMENTED_MSG("Unimplemented size={}!", uniform_type);
return 32;
}
}
@@ -175,12 +175,12 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
default:
- UNIMPLEMENTED_MSG("Unhandled type: {}", static_cast<unsigned>(instr.ld_c.type.Value()));
+ UNIMPLEMENTED_MSG("Unhandled type: {}", instr.ld_c.type.Value());
}
break;
}
case OpCode::Id::LD_L:
- LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", static_cast<u64>(instr.ld_l.unknown));
+ LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", instr.ld_l.unknown);
[[fallthrough]];
case OpCode::Id::LD_S: {
const auto GetAddress = [&](s32 offset) {
@@ -224,7 +224,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
}
default:
UNIMPLEMENTED_MSG("{} Unhandled type: {}", opcode->get().GetName(),
- static_cast<u32>(instr.ldst_sl.type.Value()));
+ instr.ldst_sl.type.Value());
}
break;
}
@@ -306,8 +306,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::ST_L:
- LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}",
- static_cast<u64>(instr.st_l.cache_management.Value()));
+ LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}", instr.st_l.cache_management.Value());
[[fallthrough]];
case OpCode::Id::ST_S: {
const auto GetAddress = [&](s32 offset) {
@@ -340,7 +339,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
}
default:
UNIMPLEMENTED_MSG("{} unhandled type: {}", opcode->get().GetName(),
- static_cast<u32>(instr.ldst_sl.type.Value()));
+ instr.ldst_sl.type.Value());
}
break;
}
@@ -387,7 +386,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
}
case OpCode::Id::RED: {
UNIMPLEMENTED_IF_MSG(instr.red.type != GlobalAtomicType::U32, "type={}",
- static_cast<int>(instr.red.type.Value()));
+ instr.red.type.Value());
const auto [real_address, base_address, descriptor] =
TrackGlobalMemory(bb, instr, true, true);
if (!real_address || !base_address) {
@@ -403,12 +402,12 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
UNIMPLEMENTED_IF_MSG(instr.atom.operation == AtomicOp::Inc ||
instr.atom.operation == AtomicOp::Dec ||
instr.atom.operation == AtomicOp::SafeAdd,
- "operation={}", static_cast<int>(instr.atom.operation.Value()));
+ "operation={}", instr.atom.operation.Value());
UNIMPLEMENTED_IF_MSG(instr.atom.type == GlobalAtomicType::S64 ||
instr.atom.type == GlobalAtomicType::U64 ||
instr.atom.type == GlobalAtomicType::F16x2_FTZ_RN ||
instr.atom.type == GlobalAtomicType::F32_FTZ_RN,
- "type={}", static_cast<int>(instr.atom.type.Value()));
+ "type={}", instr.atom.type.Value());
const auto [real_address, base_address, descriptor] =
TrackGlobalMemory(bb, instr, true, true);
@@ -428,10 +427,10 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
case OpCode::Id::ATOMS: {
UNIMPLEMENTED_IF_MSG(instr.atoms.operation == AtomicOp::Inc ||
instr.atoms.operation == AtomicOp::Dec,
- "operation={}", static_cast<int>(instr.atoms.operation.Value()));
+ "operation={}", instr.atoms.operation.Value());
UNIMPLEMENTED_IF_MSG(instr.atoms.type == AtomicType::S64 ||
instr.atoms.type == AtomicType::U64,
- "type={}", static_cast<int>(instr.atoms.type.Value()));
+ "type={}", instr.atoms.type.Value());
const bool is_signed =
instr.atoms.type == AtomicType::S32 || instr.atoms.type == AtomicType::S64;
const s32 offset = instr.atoms.GetImmediateOffset();
diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp
index 29a7cfbfe..5f88537bc 100644
--- a/src/video_core/shader/decode/other.cpp
+++ b/src/video_core/shader/decode/other.cpp
@@ -34,14 +34,13 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::EXIT: {
- const Tegra::Shader::ConditionCode cc = instr.flow_condition_code;
- UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "EXIT condition code used: {}",
- static_cast<u32>(cc));
+ const ConditionCode cc = instr.flow_condition_code;
+ UNIMPLEMENTED_IF_MSG(cc != ConditionCode::T, "EXIT condition code used: {}", cc);
switch (instr.flow.cond) {
case Tegra::Shader::FlowCondition::Always:
bb.push_back(Operation(OperationCode::Exit));
- if (instr.pred.pred_index == static_cast<u64>(Tegra::Shader::Pred::UnusedIndex)) {
+ if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) {
// If this is an unconditional exit then just end processing here,
// otherwise we have to account for the possibility of the condition
// not being met, so continue processing the next instruction.
@@ -56,17 +55,15 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
break;
default:
- UNIMPLEMENTED_MSG("Unhandled flow condition: {}",
- static_cast<u32>(instr.flow.cond.Value()));
+ UNIMPLEMENTED_MSG("Unhandled flow condition: {}", instr.flow.cond.Value());
}
break;
}
case OpCode::Id::KIL: {
UNIMPLEMENTED_IF(instr.flow.cond != Tegra::Shader::FlowCondition::Always);
- const Tegra::Shader::ConditionCode cc = instr.flow_condition_code;
- UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "KIL condition code used: {}",
- static_cast<u32>(cc));
+ const ConditionCode cc = instr.flow_condition_code;
+ UNIMPLEMENTED_IF_MSG(cc != ConditionCode::T, "KIL condition code used: {}", cc);
bb.push_back(Operation(OperationCode::Discard));
break;
@@ -79,6 +76,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
case SystemVariable::InvocationId:
return Operation(OperationCode::InvocationId);
case SystemVariable::Ydirection:
+ uses_y_negate = true;
return Operation(OperationCode::YNegate);
case SystemVariable::InvocationInfo:
LOG_WARNING(HW_GPU, "S2R instruction with InvocationInfo is incomplete");
@@ -90,11 +88,11 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
UNIMPLEMENTED_MSG("S2R WscaleFactorZ is not implemented");
return Immediate(0U);
case SystemVariable::Tid: {
- Node value = Immediate(0);
- value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9);
- value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdY), 16, 9);
- value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdZ), 26, 5);
- return value;
+ Node val = Immediate(0);
+ val = BitfieldInsert(val, Operation(OperationCode::LocalInvocationIdX), 0, 9);
+ val = BitfieldInsert(val, Operation(OperationCode::LocalInvocationIdY), 16, 9);
+ val = BitfieldInsert(val, Operation(OperationCode::LocalInvocationIdZ), 26, 5);
+ return val;
}
case SystemVariable::TidX:
return Operation(OperationCode::LocalInvocationIdX);
@@ -130,8 +128,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
return Immediate(0u);
}
default:
- UNIMPLEMENTED_MSG("Unhandled system move: {}",
- static_cast<u32>(instr.sys20.Value()));
+ UNIMPLEMENTED_MSG("Unhandled system move: {}", instr.sys20.Value());
return Immediate(0u);
}
}();
@@ -181,8 +178,8 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
}
const Node branch = Operation(OperationCode::BranchIndirect, operand);
- const Tegra::Shader::ConditionCode cc = instr.flow_condition_code;
- if (cc != Tegra::Shader::ConditionCode::T) {
+ const ConditionCode cc = instr.flow_condition_code;
+ if (cc != ConditionCode::T) {
bb.push_back(Conditional(GetConditionCode(cc), {branch}));
} else {
bb.push_back(branch);
@@ -218,9 +215,8 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::SYNC: {
- const Tegra::Shader::ConditionCode cc = instr.flow_condition_code;
- UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}",
- static_cast<u32>(cc));
+ const ConditionCode cc = instr.flow_condition_code;
+ UNIMPLEMENTED_IF_MSG(cc != ConditionCode::T, "SYNC condition code used: {}", cc);
if (decompiled) {
break;
@@ -231,9 +227,8 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
break;
}
case OpCode::Id::BRK: {
- const Tegra::Shader::ConditionCode cc = instr.flow_condition_code;
- UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}",
- static_cast<u32>(cc));
+ const ConditionCode cc = instr.flow_condition_code;
+ UNIMPLEMENTED_IF_MSG(cc != ConditionCode::T, "BRK condition code used: {}", cc);
if (decompiled) {
break;
}
@@ -306,7 +301,7 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) {
case Tegra::Shader::MembarType::GL:
return OperationCode::MemoryBarrierGlobal;
default:
- UNIMPLEMENTED_MSG("MEMBAR type={}", static_cast<int>(instr.membar.type.Value()));
+ UNIMPLEMENTED_MSG("MEMBAR type={}", instr.membar.type.Value());
return OperationCode::MemoryBarrierGlobal;
}
}();
diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp
index d4ffa8014..a53819c15 100644
--- a/src/video_core/shader/decode/shift.cpp
+++ b/src/video_core/shader/decode/shift.cpp
@@ -125,7 +125,7 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) {
case OpCode::Id::SHF_LEFT_IMM: {
UNIMPLEMENTED_IF(instr.generates_cc);
UNIMPLEMENTED_IF_MSG(instr.shf.xmode != ShfXmode::None, "xmode={}",
- static_cast<int>(instr.shf.xmode.Value()));
+ instr.shf.xmode.Value());
if (instr.is_b_imm) {
op_b = Immediate(static_cast<u32>(instr.shf.immediate));
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index 29ebf65ba..833fa2a39 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -34,7 +34,7 @@ static std::size_t GetCoordCount(TextureType texture_type) {
case TextureType::TextureCube:
return 3;
default:
- UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type));
+ UNIMPLEMENTED_MSG("Unhandled texture type: {}", texture_type);
return 0;
}
}
@@ -141,7 +141,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
SamplerInfo info;
info.is_shadow = is_depth_compare;
- const std::optional<Sampler> sampler = GetSampler(instr.sampler, info);
+ const std::optional<SamplerEntry> sampler = GetSampler(instr.sampler, info);
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
@@ -173,9 +173,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
SamplerInfo info;
info.type = texture_type;
info.is_array = is_array;
- const std::optional<Sampler> sampler = is_bindless
- ? GetBindlessSampler(base_reg, info, index_var)
- : GetSampler(instr.sampler, info);
+ const std::optional<SamplerEntry> sampler =
+ is_bindless ? GetBindlessSampler(base_reg, info, index_var)
+ : GetSampler(instr.sampler, info);
Node4 values;
if (!sampler) {
std::generate(values.begin(), values.end(), [this] { return Immediate(0); });
@@ -217,9 +217,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
[[fallthrough]];
case OpCode::Id::TXQ: {
Node index_var;
- const std::optional<Sampler> sampler = is_bindless
- ? GetBindlessSampler(instr.gpr8, {}, index_var)
- : GetSampler(instr.sampler, {});
+ const std::optional<SamplerEntry> sampler =
+ is_bindless ? GetBindlessSampler(instr.gpr8, {}, index_var)
+ : GetSampler(instr.sampler, {});
if (!sampler) {
u32 indexer = 0;
@@ -255,8 +255,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
break;
}
default:
- UNIMPLEMENTED_MSG("Unhandled texture query type: {}",
- static_cast<u32>(instr.txq.query_type.Value()));
+ UNIMPLEMENTED_MSG("Unhandled texture query type: {}", instr.txq.query_type.Value());
}
break;
}
@@ -273,7 +272,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
info.type = texture_type;
info.is_array = is_array;
Node index_var;
- const std::optional<Sampler> sampler =
+ const std::optional<SamplerEntry> sampler =
is_bindless ? GetBindlessSampler(instr.gpr20, info, index_var)
: GetSampler(instr.sampler, info);
@@ -292,33 +291,36 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
break;
}
- std::vector<Node> coords;
-
- // TODO: Add coordinates for different samplers once other texture types are implemented.
- switch (texture_type) {
- case TextureType::Texture1D:
- coords.push_back(GetRegister(instr.gpr8));
- break;
- case TextureType::Texture2D:
- coords.push_back(GetRegister(instr.gpr8.Value() + 0));
- coords.push_back(GetRegister(instr.gpr8.Value() + 1));
- break;
- default:
- UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<int>(texture_type));
+ const u64 base_index = is_array ? 1 : 0;
+ const u64 num_components = [texture_type] {
+ switch (texture_type) {
+ case TextureType::Texture1D:
+ return 1;
+ case TextureType::Texture2D:
+ return 2;
+ case TextureType::TextureCube:
+ return 3;
+ default:
+ UNIMPLEMENTED_MSG("Unhandled texture type {}", texture_type);
+ return 2;
+ }
+ }();
+ // TODO: What's the array component used for?
- // Fallback to interpreting as a 2D texture for now
- coords.push_back(GetRegister(instr.gpr8.Value() + 0));
- coords.push_back(GetRegister(instr.gpr8.Value() + 1));
+ std::vector<Node> coords;
+ coords.reserve(num_components);
+ for (u64 component = 0; component < num_components; ++component) {
+ coords.push_back(GetRegister(instr.gpr8.Value() + base_index + component));
}
+
u32 indexer = 0;
for (u32 element = 0; element < 2; ++element) {
if (!instr.tmml.IsComponentEnabled(element)) {
continue;
}
- auto params = coords;
MetaTexture meta{*sampler, {}, {}, {}, {}, {}, {}, {}, {}, element, index_var};
- const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
- SetTemporary(bb, indexer++, value);
+ Node value = Operation(OperationCode::TextureQueryLod, meta, coords);
+ SetTemporary(bb, indexer++, std::move(value));
}
for (u32 i = 0; i < indexer; ++i) {
SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i));
@@ -377,14 +379,15 @@ ShaderIR::SamplerInfo ShaderIR::GetSamplerInfo(
return info;
}
-std::optional<Sampler> ShaderIR::GetSampler(Tegra::Shader::Sampler sampler,
- SamplerInfo sampler_info) {
+std::optional<SamplerEntry> ShaderIR::GetSampler(Tegra::Shader::Sampler sampler,
+ SamplerInfo sampler_info) {
const u32 offset = static_cast<u32>(sampler.index.Value());
const auto info = GetSamplerInfo(sampler_info, registry.ObtainBoundSampler(offset));
// If this sampler has already been used, return the existing mapping.
- const auto it = std::find_if(used_samplers.begin(), used_samplers.end(),
- [offset](const Sampler& entry) { return entry.offset == offset; });
+ const auto it =
+ std::find_if(used_samplers.begin(), used_samplers.end(),
+ [offset](const SamplerEntry& entry) { return entry.offset == offset; });
if (it != used_samplers.end()) {
ASSERT(!it->is_bindless && it->type == info.type && it->is_array == info.is_array &&
it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer);
@@ -397,8 +400,8 @@ std::optional<Sampler> ShaderIR::GetSampler(Tegra::Shader::Sampler sampler,
*info.is_shadow, *info.is_buffer, false);
}
-std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg, SamplerInfo info,
- Node& index_var) {
+std::optional<SamplerEntry> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg,
+ SamplerInfo info, Node& index_var) {
const Node sampler_register = GetRegister(reg);
const auto [base_node, tracked_sampler_info] =
TrackBindlessSampler(sampler_register, global_code, static_cast<s64>(global_code.size()));
@@ -414,7 +417,7 @@ std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg,
// If this sampler has already been used, return the existing mapping.
const auto it = std::find_if(used_samplers.begin(), used_samplers.end(),
- [buffer, offset](const Sampler& entry) {
+ [buffer, offset](const SamplerEntry& entry) {
return entry.buffer == buffer && entry.offset == offset;
});
if (it != used_samplers.end()) {
@@ -434,11 +437,12 @@ std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg,
info = GetSamplerInfo(info, registry.ObtainSeparateSampler(indices, offsets));
// Try to use an already created sampler if it exists
- const auto it = std::find_if(
- used_samplers.begin(), used_samplers.end(), [indices, offsets](const Sampler& entry) {
- return offsets == std::pair{entry.offset, entry.secondary_offset} &&
- indices == std::pair{entry.buffer, entry.secondary_buffer};
- });
+ const auto it =
+ std::find_if(used_samplers.begin(), used_samplers.end(),
+ [indices, offsets](const SamplerEntry& entry) {
+ return offsets == std::pair{entry.offset, entry.secondary_offset} &&
+ indices == std::pair{entry.buffer, entry.secondary_buffer};
+ });
if (it != used_samplers.end()) {
ASSERT(it->is_separated && it->type == info.type && it->is_array == info.is_array &&
it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer);
@@ -458,7 +462,7 @@ std::optional<Sampler> ShaderIR::GetBindlessSampler(Tegra::Shader::Register reg,
// If this sampler has already been used, return the existing mapping.
const auto it = std::find_if(
used_samplers.begin(), used_samplers.end(),
- [base_offset](const Sampler& entry) { return entry.offset == base_offset; });
+ [base_offset](const SamplerEntry& entry) { return entry.offset == base_offset; });
if (it != used_samplers.end()) {
ASSERT(!it->is_bindless && it->type == info.type && it->is_array == info.is_array &&
it->is_shadow == info.is_shadow && it->is_buffer == info.is_buffer &&
@@ -553,7 +557,6 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
const bool is_shadow = depth_compare != nullptr;
const bool is_bindless = bindless_reg.has_value();
- UNIMPLEMENTED_IF(texture_type == TextureType::TextureCube && is_array && is_shadow);
ASSERT_MSG(texture_type != TextureType::Texture3D || !is_array || !is_shadow,
"Illegal texture type");
@@ -564,9 +567,9 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
info.is_buffer = false;
Node index_var;
- const std::optional<Sampler> sampler = is_bindless
- ? GetBindlessSampler(*bindless_reg, info, index_var)
- : GetSampler(instr.sampler, info);
+ const std::optional<SamplerEntry> sampler =
+ is_bindless ? GetBindlessSampler(*bindless_reg, info, index_var)
+ : GetSampler(instr.sampler, info);
if (!sampler) {
return {Immediate(0), Immediate(0), Immediate(0), Immediate(0)};
}
@@ -593,7 +596,7 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
lod = GetRegister(instr.gpr20.Value() + bias_offset);
break;
default:
- UNIMPLEMENTED_MSG("Unimplemented process mode={}", static_cast<u32>(process_mode));
+ UNIMPLEMENTED_MSG("Unimplemented process mode={}", process_mode);
break;
}
@@ -723,7 +726,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
info.is_shadow = depth_compare;
Node index_var;
- const std::optional<Sampler> sampler =
+ const std::optional<SamplerEntry> sampler =
is_bindless ? GetBindlessSampler(parameter_register++, info, index_var)
: GetSampler(instr.sampler, info);
Node4 values;
@@ -763,7 +766,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
const auto texture_type{instr.tld.texture_type};
- const bool is_array{instr.tld.is_array};
+ const bool is_array{instr.tld.is_array != 0};
const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL};
const std::size_t coord_count{GetCoordCount(texture_type)};
@@ -782,7 +785,7 @@ Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
// const Node aoffi_register{is_aoffi ? GetRegister(gpr20_cursor++) : nullptr};
// const Node multisample{is_multisample ? GetRegister(gpr20_cursor++) : nullptr};
- const std::optional<Sampler> sampler = GetSampler(instr.sampler, {});
+ const std::optional<SamplerEntry> sampler = GetSampler(instr.sampler, {});
Node4 values;
for (u32 element = 0; element < values.size(); ++element) {
@@ -799,7 +802,7 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is
info.type = texture_type;
info.is_array = is_array;
info.is_shadow = false;
- const std::optional<Sampler> sampler = GetSampler(instr.sampler, info);
+ const std::optional<SamplerEntry> sampler = GetSampler(instr.sampler, info);
const std::size_t type_coord_count = GetCoordCount(texture_type);
const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL;
diff --git a/src/video_core/shader/decode/warp.cpp b/src/video_core/shader/decode/warp.cpp
index 11b77f795..37433d783 100644
--- a/src/video_core/shader/decode/warp.cpp
+++ b/src/video_core/shader/decode/warp.cpp
@@ -27,7 +27,7 @@ OperationCode GetOperationCode(VoteOperation vote_op) {
case VoteOperation::Eq:
return OperationCode::VoteEqual;
default:
- UNREACHABLE_MSG("Invalid vote operation={}", static_cast<u64>(vote_op));
+ UNREACHABLE_MSG("Invalid vote operation={}", vote_op);
return OperationCode::VoteAll;
}
}
diff --git a/src/video_core/shader/expr.h b/src/video_core/shader/expr.h
index 4e8264367..cda284c72 100644
--- a/src/video_core/shader/expr.h
+++ b/src/video_core/shader/expr.h
@@ -76,7 +76,7 @@ public:
class ExprPredicate final {
public:
- explicit ExprPredicate(u32 predicate) : predicate{predicate} {}
+ explicit ExprPredicate(u32 predicate_) : predicate{predicate_} {}
bool operator==(const ExprPredicate& b) const {
return predicate == b.predicate;
@@ -91,7 +91,7 @@ public:
class ExprCondCode final {
public:
- explicit ExprCondCode(ConditionCode cc) : cc{cc} {}
+ explicit ExprCondCode(ConditionCode condition_code) : cc{condition_code} {}
bool operator==(const ExprCondCode& b) const {
return cc == b.cc;
@@ -121,7 +121,7 @@ public:
class ExprGprEqual final {
public:
- ExprGprEqual(u32 gpr, u32 value) : gpr{gpr}, value{value} {}
+ explicit ExprGprEqual(u32 gpr_, u32 value_) : gpr{gpr_}, value{value_} {}
bool operator==(const ExprGprEqual& b) const {
return gpr == b.gpr && value == b.value;
diff --git a/src/video_core/shader/memory_util.cpp b/src/video_core/shader/memory_util.cpp
index 5071c83ca..e18ccba8e 100644
--- a/src/video_core/shader/memory_util.cpp
+++ b/src/video_core/shader/memory_util.cpp
@@ -16,11 +16,10 @@
namespace VideoCommon::Shader {
-GPUVAddr GetShaderAddress(Core::System& system,
+GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d,
Tegra::Engines::Maxwell3D::Regs::ShaderProgram program) {
- const auto& gpu{system.GPU().Maxwell3D()};
- const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
- return gpu.regs.code_address.CodeAddress() + shader_config.offset;
+ const auto& shader_config{maxwell3d.regs.shader_config[static_cast<std::size_t>(program)]};
+ return maxwell3d.regs.code_address.CodeAddress() + shader_config.offset;
}
bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
diff --git a/src/video_core/shader/memory_util.h b/src/video_core/shader/memory_util.h
index be90d24fd..4624d38e6 100644
--- a/src/video_core/shader/memory_util.h
+++ b/src/video_core/shader/memory_util.h
@@ -11,10 +11,6 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/shader_type.h"
-namespace Core {
-class System;
-}
-
namespace Tegra {
class MemoryManager;
}
@@ -27,7 +23,7 @@ constexpr u32 STAGE_MAIN_OFFSET = 10;
constexpr u32 KERNEL_MAIN_OFFSET = 0;
/// Gets the address for the specified shader stage program
-GPUVAddr GetShaderAddress(Core::System& system,
+GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d,
Tegra::Engines::Maxwell3D::Regs::ShaderProgram program);
/// Gets if the current instruction offset is a scheduler instruction
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index 8f230d57a..b54d33763 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -282,26 +282,27 @@ struct SeparateSamplerNode;
using TrackSamplerData = std::variant<BindlessSamplerNode, SeparateSamplerNode, ArraySamplerNode>;
using TrackSampler = std::shared_ptr<TrackSamplerData>;
-struct Sampler {
+struct SamplerEntry {
/// Bound samplers constructor
- constexpr explicit Sampler(u32 index, u32 offset, Tegra::Shader::TextureType type,
- bool is_array, bool is_shadow, bool is_buffer, bool is_indexed)
- : index{index}, offset{offset}, type{type}, is_array{is_array}, is_shadow{is_shadow},
- is_buffer{is_buffer}, is_indexed{is_indexed} {}
+ explicit SamplerEntry(u32 index_, u32 offset_, Tegra::Shader::TextureType type_, bool is_array_,
+ bool is_shadow_, bool is_buffer_, bool is_indexed_)
+ : index{index_}, offset{offset_}, type{type_}, is_array{is_array_}, is_shadow{is_shadow_},
+ is_buffer{is_buffer_}, is_indexed{is_indexed_} {}
/// Separate sampler constructor
- constexpr explicit Sampler(u32 index, std::pair<u32, u32> offsets, std::pair<u32, u32> buffers,
- Tegra::Shader::TextureType type, bool is_array, bool is_shadow,
- bool is_buffer)
- : index{index}, offset{offsets.first}, secondary_offset{offsets.second},
- buffer{buffers.first}, secondary_buffer{buffers.second}, type{type}, is_array{is_array},
- is_shadow{is_shadow}, is_buffer{is_buffer}, is_separated{true} {}
+ explicit SamplerEntry(u32 index_, std::pair<u32, u32> offsets, std::pair<u32, u32> buffers,
+ Tegra::Shader::TextureType type_, bool is_array_, bool is_shadow_,
+ bool is_buffer_)
+ : index{index_}, offset{offsets.first}, secondary_offset{offsets.second},
+ buffer{buffers.first}, secondary_buffer{buffers.second}, type{type_}, is_array{is_array_},
+ is_shadow{is_shadow_}, is_buffer{is_buffer_}, is_separated{true} {}
/// Bindless samplers constructor
- constexpr explicit Sampler(u32 index, u32 offset, u32 buffer, Tegra::Shader::TextureType type,
- bool is_array, bool is_shadow, bool is_buffer, bool is_indexed)
- : index{index}, offset{offset}, buffer{buffer}, type{type}, is_array{is_array},
- is_shadow{is_shadow}, is_buffer{is_buffer}, is_bindless{true}, is_indexed{is_indexed} {}
+ explicit SamplerEntry(u32 index_, u32 offset_, u32 buffer_, Tegra::Shader::TextureType type_,
+ bool is_array_, bool is_shadow_, bool is_buffer_, bool is_indexed_)
+ : index{index_}, offset{offset_}, buffer{buffer_}, type{type_}, is_array{is_array_},
+ is_shadow{is_shadow_}, is_buffer{is_buffer_}, is_bindless{true}, is_indexed{is_indexed_} {
+ }
u32 index = 0; ///< Emulated index given for the this sampler.
u32 offset = 0; ///< Offset in the const buffer from where the sampler is being read.
@@ -338,15 +339,15 @@ struct BindlessSamplerNode {
u32 offset;
};
-struct Image {
+struct ImageEntry {
public:
/// Bound images constructor
- constexpr explicit Image(u32 index, u32 offset, Tegra::Shader::ImageType type)
- : index{index}, offset{offset}, type{type} {}
+ explicit ImageEntry(u32 index_, u32 offset_, Tegra::Shader::ImageType type_)
+ : index{index_}, offset{offset_}, type{type_} {}
/// Bindless samplers constructor
- constexpr explicit Image(u32 index, u32 offset, u32 buffer, Tegra::Shader::ImageType type)
- : index{index}, offset{offset}, buffer{buffer}, type{type}, is_bindless{true} {}
+ explicit ImageEntry(u32 index_, u32 offset_, u32 buffer_, Tegra::Shader::ImageType type_)
+ : index{index_}, offset{offset_}, buffer{buffer_}, type{type_}, is_bindless{true} {}
void MarkWrite() {
is_written = true;
@@ -377,7 +378,7 @@ struct GlobalMemoryBase {
u32 cbuf_index = 0;
u32 cbuf_offset = 0;
- bool operator<(const GlobalMemoryBase& rhs) const {
+ [[nodiscard]] bool operator<(const GlobalMemoryBase& rhs) const {
return std::tie(cbuf_index, cbuf_offset) < std::tie(rhs.cbuf_index, rhs.cbuf_offset);
}
};
@@ -389,7 +390,7 @@ struct MetaArithmetic {
/// Parameters describing a texture sampler
struct MetaTexture {
- Sampler sampler;
+ SamplerEntry sampler;
Node array;
Node depth_compare;
std::vector<Node> aoffi;
@@ -403,7 +404,7 @@ struct MetaTexture {
};
struct MetaImage {
- const Image& image;
+ const ImageEntry& image;
std::vector<Node> values;
u32 element{};
};
@@ -414,7 +415,7 @@ using Meta =
class AmendNode {
public:
- std::optional<std::size_t> GetAmendIndex() const {
+ [[nodiscard]] std::optional<std::size_t> GetAmendIndex() const {
if (amend_index == amend_null_index) {
return std::nullopt;
}
@@ -437,34 +438,34 @@ private:
/// Holds any kind of operation that can be done in the IR
class OperationNode final : public AmendNode {
public:
- explicit OperationNode(OperationCode code) : OperationNode(code, Meta{}) {}
+ explicit OperationNode(OperationCode code_) : OperationNode(code_, Meta{}) {}
- explicit OperationNode(OperationCode code, Meta meta)
- : OperationNode(code, std::move(meta), std::vector<Node>{}) {}
+ explicit OperationNode(OperationCode code_, Meta meta_)
+ : OperationNode(code_, std::move(meta_), std::vector<Node>{}) {}
- explicit OperationNode(OperationCode code, std::vector<Node> operands)
- : OperationNode(code, Meta{}, std::move(operands)) {}
+ explicit OperationNode(OperationCode code_, std::vector<Node> operands_)
+ : OperationNode(code_, Meta{}, std::move(operands_)) {}
- explicit OperationNode(OperationCode code, Meta meta, std::vector<Node> operands)
- : code{code}, meta{std::move(meta)}, operands{std::move(operands)} {}
+ explicit OperationNode(OperationCode code_, Meta meta_, std::vector<Node> operands_)
+ : code{code_}, meta{std::move(meta_)}, operands{std::move(operands_)} {}
template <typename... Args>
- explicit OperationNode(OperationCode code, Meta meta, Args&&... operands)
- : code{code}, meta{std::move(meta)}, operands{operands...} {}
+ explicit OperationNode(OperationCode code_, Meta meta_, Args&&... operands_)
+ : code{code_}, meta{std::move(meta_)}, operands{operands_...} {}
- OperationCode GetCode() const {
+ [[nodiscard]] OperationCode GetCode() const {
return code;
}
- const Meta& GetMeta() const {
+ [[nodiscard]] const Meta& GetMeta() const {
return meta;
}
- std::size_t GetOperandsCount() const {
+ [[nodiscard]] std::size_t GetOperandsCount() const {
return operands.size();
}
- const Node& operator[](std::size_t operand_index) const {
+ [[nodiscard]] const Node& operator[](std::size_t operand_index) const {
return operands.at(operand_index);
}
@@ -477,14 +478,14 @@ private:
/// Encloses inside any kind of node that returns a boolean conditionally-executed code
class ConditionalNode final : public AmendNode {
public:
- explicit ConditionalNode(Node condition, std::vector<Node>&& code)
- : condition{std::move(condition)}, code{std::move(code)} {}
+ explicit ConditionalNode(Node condition_, std::vector<Node>&& code_)
+ : condition{std::move(condition_)}, code{std::move(code_)} {}
- const Node& GetCondition() const {
+ [[nodiscard]] const Node& GetCondition() const {
return condition;
}
- const std::vector<Node>& GetCode() const {
+ [[nodiscard]] const std::vector<Node>& GetCode() const {
return code;
}
@@ -496,9 +497,9 @@ private:
/// A general purpose register
class GprNode final {
public:
- explicit constexpr GprNode(Tegra::Shader::Register index) : index{index} {}
+ explicit constexpr GprNode(Tegra::Shader::Register index_) : index{index_} {}
- u32 GetIndex() const {
+ [[nodiscard]] constexpr u32 GetIndex() const {
return static_cast<u32>(index);
}
@@ -509,9 +510,9 @@ private:
/// A custom variable
class CustomVarNode final {
public:
- explicit constexpr CustomVarNode(u32 index) : index{index} {}
+ explicit constexpr CustomVarNode(u32 index_) : index{index_} {}
- constexpr u32 GetIndex() const {
+ [[nodiscard]] constexpr u32 GetIndex() const {
return index;
}
@@ -522,9 +523,9 @@ private:
/// A 32-bits value that represents an immediate value
class ImmediateNode final {
public:
- explicit constexpr ImmediateNode(u32 value) : value{value} {}
+ explicit constexpr ImmediateNode(u32 value_) : value{value_} {}
- u32 GetValue() const {
+ [[nodiscard]] constexpr u32 GetValue() const {
return value;
}
@@ -535,9 +536,9 @@ private:
/// One of Maxwell's internal flags
class InternalFlagNode final {
public:
- explicit constexpr InternalFlagNode(InternalFlag flag) : flag{flag} {}
+ explicit constexpr InternalFlagNode(InternalFlag flag_) : flag{flag_} {}
- InternalFlag GetFlag() const {
+ [[nodiscard]] constexpr InternalFlag GetFlag() const {
return flag;
}
@@ -548,14 +549,14 @@ private:
/// A predicate register, it can be negated without additional nodes
class PredicateNode final {
public:
- explicit constexpr PredicateNode(Tegra::Shader::Pred index, bool negated)
- : index{index}, negated{negated} {}
+ explicit constexpr PredicateNode(Tegra::Shader::Pred index_, bool negated_)
+ : index{index_}, negated{negated_} {}
- Tegra::Shader::Pred GetIndex() const {
+ [[nodiscard]] constexpr Tegra::Shader::Pred GetIndex() const {
return index;
}
- bool IsNegated() const {
+ [[nodiscard]] constexpr bool IsNegated() const {
return negated;
}
@@ -568,30 +569,30 @@ private:
class AbufNode final {
public:
// Initialize for standard attributes (index is explicit).
- explicit AbufNode(Tegra::Shader::Attribute::Index index, u32 element, Node buffer = {})
- : buffer{std::move(buffer)}, index{index}, element{element} {}
+ explicit AbufNode(Tegra::Shader::Attribute::Index index_, u32 element_, Node buffer_ = {})
+ : buffer{std::move(buffer_)}, index{index_}, element{element_} {}
// Initialize for physical attributes (index is a variable value).
- explicit AbufNode(Node physical_address, Node buffer = {})
- : physical_address{std::move(physical_address)}, buffer{std::move(buffer)} {}
+ explicit AbufNode(Node physical_address_, Node buffer_ = {})
+ : physical_address{std::move(physical_address_)}, buffer{std::move(buffer_)} {}
- Tegra::Shader::Attribute::Index GetIndex() const {
+ [[nodiscard]] Tegra::Shader::Attribute::Index GetIndex() const {
return index;
}
- u32 GetElement() const {
+ [[nodiscard]] u32 GetElement() const {
return element;
}
- const Node& GetBuffer() const {
+ [[nodiscard]] const Node& GetBuffer() const {
return buffer;
}
- bool IsPhysicalBuffer() const {
+ [[nodiscard]] bool IsPhysicalBuffer() const {
return static_cast<bool>(physical_address);
}
- const Node& GetPhysicalAddress() const {
+ [[nodiscard]] const Node& GetPhysicalAddress() const {
return physical_address;
}
@@ -605,9 +606,9 @@ private:
/// Patch memory (used to communicate tessellation stages).
class PatchNode final {
public:
- explicit PatchNode(u32 offset) : offset{offset} {}
+ explicit constexpr PatchNode(u32 offset_) : offset{offset_} {}
- u32 GetOffset() const {
+ [[nodiscard]] constexpr u32 GetOffset() const {
return offset;
}
@@ -618,13 +619,13 @@ private:
/// Constant buffer node, usually mapped to uniform buffers in GLSL
class CbufNode final {
public:
- explicit CbufNode(u32 index, Node offset) : index{index}, offset{std::move(offset)} {}
+ explicit CbufNode(u32 index_, Node offset_) : index{index_}, offset{std::move(offset_)} {}
- u32 GetIndex() const {
+ [[nodiscard]] u32 GetIndex() const {
return index;
}
- const Node& GetOffset() const {
+ [[nodiscard]] const Node& GetOffset() const {
return offset;
}
@@ -636,9 +637,9 @@ private:
/// Local memory node
class LmemNode final {
public:
- explicit LmemNode(Node address) : address{std::move(address)} {}
+ explicit LmemNode(Node address_) : address{std::move(address_)} {}
- const Node& GetAddress() const {
+ [[nodiscard]] const Node& GetAddress() const {
return address;
}
@@ -649,9 +650,9 @@ private:
/// Shared memory node
class SmemNode final {
public:
- explicit SmemNode(Node address) : address{std::move(address)} {}
+ explicit SmemNode(Node address_) : address{std::move(address_)} {}
- const Node& GetAddress() const {
+ [[nodiscard]] const Node& GetAddress() const {
return address;
}
@@ -662,19 +663,19 @@ private:
/// Global memory node
class GmemNode final {
public:
- explicit GmemNode(Node real_address, Node base_address, const GlobalMemoryBase& descriptor)
- : real_address{std::move(real_address)}, base_address{std::move(base_address)},
- descriptor{descriptor} {}
+ explicit GmemNode(Node real_address_, Node base_address_, const GlobalMemoryBase& descriptor_)
+ : real_address{std::move(real_address_)}, base_address{std::move(base_address_)},
+ descriptor{descriptor_} {}
- const Node& GetRealAddress() const {
+ [[nodiscard]] const Node& GetRealAddress() const {
return real_address;
}
- const Node& GetBaseAddress() const {
+ [[nodiscard]] const Node& GetBaseAddress() const {
return base_address;
}
- const GlobalMemoryBase& GetDescriptor() const {
+ [[nodiscard]] const GlobalMemoryBase& GetDescriptor() const {
return descriptor;
}
@@ -687,9 +688,9 @@ private:
/// Commentary, can be dropped
class CommentNode final {
public:
- explicit CommentNode(std::string text) : text{std::move(text)} {}
+ explicit CommentNode(std::string text_) : text{std::move(text_)} {}
- const std::string& GetText() const {
+ [[nodiscard]] const std::string& GetText() const {
return text;
}
diff --git a/src/video_core/shader/node_helper.cpp b/src/video_core/shader/node_helper.cpp
index 7bf4ff387..6a5b6940d 100644
--- a/src/video_core/shader/node_helper.cpp
+++ b/src/video_core/shader/node_helper.cpp
@@ -107,7 +107,7 @@ OperationCode SignedToUnsignedCode(OperationCode operation_code, bool is_signed)
UNREACHABLE_MSG("Can't apply absolute to an unsigned integer");
return {};
default:
- UNREACHABLE_MSG("Unknown signed operation with code={}", static_cast<u32>(operation_code));
+ UNREACHABLE_MSG("Unknown signed operation with code={}", operation_code);
return {};
}
}
diff --git a/src/video_core/shader/registry.cpp b/src/video_core/shader/registry.cpp
index cdf274e54..148d91fcb 100644
--- a/src/video_core/shader/registry.cpp
+++ b/src/video_core/shader/registry.cpp
@@ -24,44 +24,45 @@ GraphicsInfo MakeGraphicsInfo(ShaderType shader_stage, ConstBufferEngineInterfac
if (shader_stage == ShaderType::Compute) {
return {};
}
- auto& graphics = static_cast<Tegra::Engines::Maxwell3D&>(engine);
-
- GraphicsInfo info;
- info.tfb_layouts = graphics.regs.tfb_layouts;
- info.tfb_varying_locs = graphics.regs.tfb_varying_locs;
- info.primitive_topology = graphics.regs.draw.topology;
- info.tessellation_primitive = graphics.regs.tess_mode.prim;
- info.tessellation_spacing = graphics.regs.tess_mode.spacing;
- info.tfb_enabled = graphics.regs.tfb_enabled;
- info.tessellation_clockwise = graphics.regs.tess_mode.cw;
- return info;
+
+ auto& graphics = dynamic_cast<Tegra::Engines::Maxwell3D&>(engine);
+
+ return {
+ .tfb_layouts = graphics.regs.tfb_layouts,
+ .tfb_varying_locs = graphics.regs.tfb_varying_locs,
+ .primitive_topology = graphics.regs.draw.topology,
+ .tessellation_primitive = graphics.regs.tess_mode.prim,
+ .tessellation_spacing = graphics.regs.tess_mode.spacing,
+ .tfb_enabled = graphics.regs.tfb_enabled != 0,
+ .tessellation_clockwise = graphics.regs.tess_mode.cw.Value() != 0,
+ };
}
ComputeInfo MakeComputeInfo(ShaderType shader_stage, ConstBufferEngineInterface& engine) {
if (shader_stage != ShaderType::Compute) {
return {};
}
- auto& compute = static_cast<Tegra::Engines::KeplerCompute&>(engine);
+
+ auto& compute = dynamic_cast<Tegra::Engines::KeplerCompute&>(engine);
const auto& launch = compute.launch_description;
- ComputeInfo info;
- info.workgroup_size = {launch.block_dim_x, launch.block_dim_y, launch.block_dim_z};
- info.local_memory_size_in_words = launch.local_pos_alloc;
- info.shared_memory_size_in_words = launch.shared_alloc;
- return info;
+ return {
+ .workgroup_size = {launch.block_dim_x, launch.block_dim_y, launch.block_dim_z},
+ .shared_memory_size_in_words = launch.shared_alloc,
+ .local_memory_size_in_words = launch.local_pos_alloc,
+ };
}
} // Anonymous namespace
-Registry::Registry(Tegra::Engines::ShaderType shader_stage, const SerializedRegistryInfo& info)
+Registry::Registry(ShaderType shader_stage, const SerializedRegistryInfo& info)
: stage{shader_stage}, stored_guest_driver_profile{info.guest_driver_profile},
bound_buffer{info.bound_buffer}, graphics_info{info.graphics}, compute_info{info.compute} {}
-Registry::Registry(Tegra::Engines::ShaderType shader_stage,
- Tegra::Engines::ConstBufferEngineInterface& engine)
- : stage{shader_stage}, engine{&engine}, bound_buffer{engine.GetBoundBuffer()},
- graphics_info{MakeGraphicsInfo(shader_stage, engine)}, compute_info{MakeComputeInfo(
- shader_stage, engine)} {}
+Registry::Registry(ShaderType shader_stage, ConstBufferEngineInterface& engine_)
+ : stage{shader_stage}, engine{&engine_}, bound_buffer{engine_.GetBoundBuffer()},
+ graphics_info{MakeGraphicsInfo(shader_stage, engine_)}, compute_info{MakeComputeInfo(
+ shader_stage, engine_)} {}
Registry::~Registry() = default;
@@ -113,8 +114,7 @@ std::optional<Tegra::Engines::SamplerDescriptor> Registry::ObtainSeparateSampler
return value;
}
-std::optional<Tegra::Engines::SamplerDescriptor> Registry::ObtainBindlessSampler(u32 buffer,
- u32 offset) {
+std::optional<SamplerDescriptor> Registry::ObtainBindlessSampler(u32 buffer, u32 offset) {
const std::pair key = {buffer, offset};
const auto iter = bindless_samplers.find(key);
if (iter != bindless_samplers.end()) {
diff --git a/src/video_core/shader/registry.h b/src/video_core/shader/registry.h
index 231206765..4bebefdde 100644
--- a/src/video_core/shader/registry.h
+++ b/src/video_core/shader/registry.h
@@ -94,7 +94,7 @@ public:
explicit Registry(Tegra::Engines::ShaderType shader_stage, const SerializedRegistryInfo& info);
explicit Registry(Tegra::Engines::ShaderType shader_stage,
- Tegra::Engines::ConstBufferEngineInterface& engine);
+ Tegra::Engines::ConstBufferEngineInterface& engine_);
~Registry();
diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp
index 29d794b34..a4987ffc6 100644
--- a/src/video_core/shader/shader_ir.cpp
+++ b/src/video_core/shader/shader_ir.cpp
@@ -25,9 +25,10 @@ using Tegra::Shader::PredCondition;
using Tegra::Shader::PredOperation;
using Tegra::Shader::Register;
-ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSettings settings,
- Registry& registry)
- : program_code{program_code}, main_offset{main_offset}, settings{settings}, registry{registry} {
+ShaderIR::ShaderIR(const ProgramCode& program_code_, u32 main_offset_, CompilerSettings settings_,
+ Registry& registry_)
+ : program_code{program_code_}, main_offset{main_offset_}, settings{settings_}, registry{
+ registry_} {
Decode();
PostDecode();
}
@@ -170,7 +171,7 @@ Node ShaderIR::ConvertIntegerSize(Node value, Register::Size size, bool is_signe
// Default - do nothing
return value;
default:
- UNREACHABLE_MSG("Unimplemented conversion size: {}", static_cast<u32>(size));
+ UNREACHABLE_MSG("Unimplemented conversion size: {}", size);
return value;
}
}
@@ -335,15 +336,15 @@ OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) {
return operation_table[index];
}
-Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) const {
+Node ShaderIR::GetConditionCode(ConditionCode cc) const {
switch (cc) {
- case Tegra::Shader::ConditionCode::NEU:
+ case ConditionCode::NEU:
return GetInternalFlag(InternalFlag::Zero, true);
- case Tegra::Shader::ConditionCode::FCSM_TR:
+ case ConditionCode::FCSM_TR:
UNIMPLEMENTED_MSG("EXIT.FCSM_TR is not implemented");
return MakeNode<PredicateNode>(Pred::NeverExecute, false);
default:
- UNIMPLEMENTED_MSG("Unimplemented condition code: {}", static_cast<u32>(cc));
+ UNIMPLEMENTED_MSG("Unimplemented condition code: {}", cc);
return MakeNode<PredicateNode>(Pred::NeverExecute, false);
}
}
@@ -451,8 +452,8 @@ void ShaderIR::MarkAttributeUsage(Attribute::Index index, u64 element) {
}
std::size_t ShaderIR::DeclareAmend(Node new_amend) {
- const std::size_t id = amend_code.size();
- amend_code.push_back(new_amend);
+ const auto id = amend_code.size();
+ amend_code.push_back(std::move(new_amend));
return id;
}
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 3a98b2104..1cd7c14d7 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -29,8 +29,8 @@ struct ShaderBlock;
constexpr u32 MAX_PROGRAM_LENGTH = 0x1000;
struct ConstBuffer {
- constexpr explicit ConstBuffer(u32 max_offset, bool is_indirect)
- : max_offset{max_offset}, is_indirect{is_indirect} {}
+ constexpr explicit ConstBuffer(u32 max_offset_, bool is_indirect_)
+ : max_offset{max_offset_}, is_indirect{is_indirect_} {}
constexpr ConstBuffer() = default;
@@ -66,8 +66,8 @@ struct GlobalMemoryUsage {
class ShaderIR final {
public:
- explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSettings settings,
- Registry& registry);
+ explicit ShaderIR(const ProgramCode& program_code_, u32 main_offset_,
+ CompilerSettings settings_, Registry& registry_);
~ShaderIR();
const std::map<u32, NodeBlock>& GetBasicBlocks() const {
@@ -94,11 +94,11 @@ public:
return used_cbufs;
}
- const std::list<Sampler>& GetSamplers() const {
+ const std::list<SamplerEntry>& GetSamplers() const {
return used_samplers;
}
- const std::list<Image>& GetImages() const {
+ const std::list<ImageEntry>& GetImages() const {
return used_images;
}
@@ -139,6 +139,10 @@ public:
return uses_legacy_varyings;
}
+ bool UsesYNegate() const {
+ return uses_y_negate;
+ }
+
bool UsesWarps() const {
return uses_warps;
}
@@ -334,17 +338,17 @@ private:
std::optional<Tegra::Engines::SamplerDescriptor> sampler);
/// Accesses a texture sampler.
- std::optional<Sampler> GetSampler(Tegra::Shader::Sampler sampler, SamplerInfo info);
+ std::optional<SamplerEntry> GetSampler(Tegra::Shader::Sampler sampler, SamplerInfo info);
/// Accesses a texture sampler for a bindless texture.
- std::optional<Sampler> GetBindlessSampler(Tegra::Shader::Register reg, SamplerInfo info,
- Node& index_var);
+ std::optional<SamplerEntry> GetBindlessSampler(Tegra::Shader::Register reg, SamplerInfo info,
+ Node& index_var);
/// Accesses an image.
- Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
+ ImageEntry& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
/// Access a bindless image sampler.
- Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);
+ ImageEntry& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);
/// Extracts a sequence of bits from a node
Node BitfieldExtract(Node value, u32 offset, u32 bits);
@@ -454,8 +458,8 @@ private:
std::set<Tegra::Shader::Attribute::Index> used_input_attributes;
std::set<Tegra::Shader::Attribute::Index> used_output_attributes;
std::map<u32, ConstBuffer> used_cbufs;
- std::list<Sampler> used_samplers;
- std::list<Image> used_images;
+ std::list<SamplerEntry> used_samplers;
+ std::list<ImageEntry> used_images;
std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{};
std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory;
bool uses_layer{};
@@ -465,6 +469,7 @@ private:
bool uses_instance_id{};
bool uses_vertex_id{};
bool uses_legacy_varyings{};
+ bool uses_y_negate{};
bool uses_warps{};
bool uses_indexed_samplers{};
diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp
index d5ed81442..6be3ea92b 100644
--- a/src/video_core/shader/track.cpp
+++ b/src/video_core/shader/track.cpp
@@ -205,12 +205,12 @@ std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code,
const auto result = TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1);
const auto& found = result.first;
if (!found) {
- return {};
+ return std::nullopt;
}
if (const auto immediate = std::get_if<ImmediateNode>(&*found)) {
return immediate->GetValue();
}
- return {};
+ return std::nullopt;
}
std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code,
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index 1688267bb..6308aef94 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -28,7 +28,7 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t
case Tegra::Texture::TextureType::Texture2DArray:
return SurfaceTarget::Texture2DArray;
default:
- LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", static_cast<u32>(texture_type));
+ LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", texture_type);
UNREACHABLE();
return SurfaceTarget::Texture2D;
}
@@ -47,7 +47,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) {
case SurfaceTarget::TextureCubeArray:
return true;
default:
- LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
+ LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target);
UNREACHABLE();
return false;
}
@@ -66,7 +66,7 @@ bool SurfaceTargetIsArray(SurfaceTarget target) {
case SurfaceTarget::TextureCubeArray:
return true;
default:
- LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
+ LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", target);
UNREACHABLE();
return false;
}
@@ -85,7 +85,7 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
case Tegra::DepthFormat::D32_FLOAT_S8X24_UINT:
return PixelFormat::D32_FLOAT_S8_UINT;
default:
- UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format));
+ UNIMPLEMENTED_MSG("Unimplemented format={}", format);
return PixelFormat::S8_UINT_D24_UNORM;
}
}
@@ -183,7 +183,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
case Tegra::RenderTargetFormat::R8_UINT:
return PixelFormat::R8_UINT;
default:
- UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<int>(format));
+ UNIMPLEMENTED_MSG("Unimplemented format={}", format);
return PixelFormat::A8B8G8R8_UNORM;
}
}
@@ -197,7 +197,7 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat
case Tegra::FramebufferConfig::PixelFormat::B8G8R8A8_UNORM:
return PixelFormat::B8G8R8A8_UNORM;
default:
- UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format));
+ UNIMPLEMENTED_MSG("Unimplemented format={}", format);
return PixelFormat::A8B8G8R8_UNORM;
}
}
@@ -280,7 +280,7 @@ bool IsPixelFormatSRGB(PixelFormat format) {
}
std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
- return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)};
+ return {DefaultBlockWidth(format), DefaultBlockHeight(format)};
}
} // namespace VideoCore::Surface
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index cfd12fa61..c40ab89d0 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -120,7 +120,7 @@ enum class PixelFormat {
Max = MaxDepthStencilFormat,
Invalid = 255,
};
-static constexpr std::size_t MaxPixelFormat = static_cast<std::size_t>(PixelFormat::Max);
+constexpr std::size_t MaxPixelFormat = static_cast<std::size_t>(PixelFormat::Max);
enum class SurfaceType {
ColorTexture = 0,
@@ -140,117 +140,7 @@ enum class SurfaceTarget {
TextureCubeArray,
};
-constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{
- 0, // A8B8G8R8_UNORM
- 0, // A8B8G8R8_SNORM
- 0, // A8B8G8R8_SINT
- 0, // A8B8G8R8_UINT
- 0, // R5G6B5_UNORM
- 0, // B5G6R5_UNORM
- 0, // A1R5G5B5_UNORM
- 0, // A2B10G10R10_UNORM
- 0, // A2B10G10R10_UINT
- 0, // A1B5G5R5_UNORM
- 0, // R8_UNORM
- 0, // R8_SNORM
- 0, // R8_SINT
- 0, // R8_UINT
- 0, // R16G16B16A16_FLOAT
- 0, // R16G16B16A16_UNORM
- 0, // R16G16B16A16_SNORM
- 0, // R16G16B16A16_SINT
- 0, // R16G16B16A16_UINT
- 0, // B10G11R11_FLOAT
- 0, // R32G32B32A32_UINT
- 2, // BC1_RGBA_UNORM
- 2, // BC2_UNORM
- 2, // BC3_UNORM
- 2, // BC4_UNORM
- 2, // BC4_SNORM
- 2, // BC5_UNORM
- 2, // BC5_SNORM
- 2, // BC7_UNORM
- 2, // BC6H_UFLOAT
- 2, // BC6H_SFLOAT
- 2, // ASTC_2D_4X4_UNORM
- 0, // B8G8R8A8_UNORM
- 0, // R32G32B32A32_FLOAT
- 0, // R32G32B32A32_SINT
- 0, // R32G32_FLOAT
- 0, // R32G32_SINT
- 0, // R32_FLOAT
- 0, // R16_FLOAT
- 0, // R16_UNORM
- 0, // R16_SNORM
- 0, // R16_UINT
- 0, // R16_SINT
- 0, // R16G16_UNORM
- 0, // R16G16_FLOAT
- 0, // R16G16_UINT
- 0, // R16G16_SINT
- 0, // R16G16_SNORM
- 0, // R32G32B32_FLOAT
- 0, // A8B8G8R8_SRGB
- 0, // R8G8_UNORM
- 0, // R8G8_SNORM
- 0, // R8G8_SINT
- 0, // R8G8_UINT
- 0, // R32G32_UINT
- 0, // R16G16B16X16_FLOAT
- 0, // R32_UINT
- 0, // R32_SINT
- 2, // ASTC_2D_8X8_UNORM
- 2, // ASTC_2D_8X5_UNORM
- 2, // ASTC_2D_5X4_UNORM
- 0, // B8G8R8A8_SRGB
- 2, // BC1_RGBA_SRGB
- 2, // BC2_SRGB
- 2, // BC3_SRGB
- 2, // BC7_SRGB
- 0, // A4B4G4R4_UNORM
- 2, // ASTC_2D_4X4_SRGB
- 2, // ASTC_2D_8X8_SRGB
- 2, // ASTC_2D_8X5_SRGB
- 2, // ASTC_2D_5X4_SRGB
- 2, // ASTC_2D_5X5_UNORM
- 2, // ASTC_2D_5X5_SRGB
- 2, // ASTC_2D_10X8_UNORM
- 2, // ASTC_2D_10X8_SRGB
- 2, // ASTC_2D_6X6_UNORM
- 2, // ASTC_2D_6X6_SRGB
- 2, // ASTC_2D_10X10_UNORM
- 2, // ASTC_2D_10X10_SRGB
- 2, // ASTC_2D_12X12_UNORM
- 2, // ASTC_2D_12X12_SRGB
- 2, // ASTC_2D_8X6_UNORM
- 2, // ASTC_2D_8X6_SRGB
- 2, // ASTC_2D_6X5_UNORM
- 2, // ASTC_2D_6X5_SRGB
- 0, // E5B9G9R9_FLOAT
- 0, // D32_FLOAT
- 0, // D16_UNORM
- 0, // D24_UNORM_S8_UINT
- 0, // S8_UINT_D24_UNORM
- 0, // D32_FLOAT_S8_UINT
-}};
-
-/**
- * Gets the compression factor for the specified PixelFormat. This applies to just the
- * "compressed width" and "compressed height", not the overall compression factor of a
- * compressed image. This is used for maintaining proper surface sizes for compressed
- * texture formats.
- */
-inline constexpr u32 GetCompressionFactorShift(PixelFormat format) {
- DEBUG_ASSERT(format != PixelFormat::Invalid);
- DEBUG_ASSERT(static_cast<std::size_t>(format) < compression_factor_shift_table.size());
- return compression_factor_shift_table[static_cast<std::size_t>(format)];
-}
-
-inline constexpr u32 GetCompressionFactor(PixelFormat format) {
- return 1U << GetCompressionFactorShift(format);
-}
-
-constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
+constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
1, // A8B8G8R8_UNORM
1, // A8B8G8R8_SNORM
1, // A8B8G8R8_SINT
@@ -344,15 +234,12 @@ constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
1, // D32_FLOAT_S8_UINT
}};
-static constexpr u32 GetDefaultBlockWidth(PixelFormat format) {
- if (format == PixelFormat::Invalid)
- return 0;
-
- ASSERT(static_cast<std::size_t>(format) < block_width_table.size());
- return block_width_table[static_cast<std::size_t>(format)];
+constexpr u32 DefaultBlockWidth(PixelFormat format) {
+ ASSERT(static_cast<std::size_t>(format) < BLOCK_WIDTH_TABLE.size());
+ return BLOCK_WIDTH_TABLE[static_cast<std::size_t>(format)];
}
-constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
+constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
1, // A8B8G8R8_UNORM
1, // A8B8G8R8_SNORM
1, // A8B8G8R8_SINT
@@ -446,15 +333,12 @@ constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
1, // D32_FLOAT_S8_UINT
}};
-static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
- if (format == PixelFormat::Invalid)
- return 0;
-
- ASSERT(static_cast<std::size_t>(format) < block_height_table.size());
- return block_height_table[static_cast<std::size_t>(format)];
+constexpr u32 DefaultBlockHeight(PixelFormat format) {
+ ASSERT(static_cast<std::size_t>(format) < BLOCK_HEIGHT_TABLE.size());
+ return BLOCK_HEIGHT_TABLE[static_cast<std::size_t>(format)];
}
-constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
+constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
32, // A8B8G8R8_UNORM
32, // A8B8G8R8_SNORM
32, // A8B8G8R8_SINT
@@ -548,20 +432,14 @@ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
64, // D32_FLOAT_S8_UINT
}};
-static constexpr u32 GetFormatBpp(PixelFormat format) {
- if (format == PixelFormat::Invalid)
- return 0;
-
- ASSERT(static_cast<std::size_t>(format) < bpp_table.size());
- return bpp_table[static_cast<std::size_t>(format)];
+constexpr u32 BitsPerBlock(PixelFormat format) {
+ ASSERT(static_cast<std::size_t>(format) < BITS_PER_BLOCK_TABLE.size());
+ return BITS_PER_BLOCK_TABLE[static_cast<std::size_t>(format)];
}
/// Returns the sizer in bytes of the specified pixel format
-static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) {
- if (pixel_format == PixelFormat::Invalid) {
- return 0;
- }
- return GetFormatBpp(pixel_format) / CHAR_BIT;
+constexpr u32 BytesPerBlock(PixelFormat pixel_format) {
+ return BitsPerBlock(pixel_format) / CHAR_BIT;
}
SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type);
diff --git a/src/video_core/texture_cache/accelerated_swizzle.cpp b/src/video_core/texture_cache/accelerated_swizzle.cpp
new file mode 100644
index 000000000..15585caeb
--- /dev/null
+++ b/src/video_core/texture_cache/accelerated_swizzle.cpp
@@ -0,0 +1,70 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <bit>
+
+#include "common/alignment.h"
+#include "common/common_types.h"
+#include "common/div_ceil.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/accelerated_swizzle.h"
+#include "video_core/texture_cache/util.h"
+#include "video_core/textures/decoders.h"
+
+namespace VideoCommon::Accelerated {
+
+using Tegra::Texture::GOB_SIZE_SHIFT;
+using Tegra::Texture::GOB_SIZE_X;
+using Tegra::Texture::GOB_SIZE_X_SHIFT;
+using Tegra::Texture::GOB_SIZE_Y_SHIFT;
+using VideoCore::Surface::BytesPerBlock;
+
+BlockLinearSwizzle2DParams MakeBlockLinearSwizzle2DParams(const SwizzleParameters& swizzle,
+ const ImageInfo& info) {
+ const Extent3D block = swizzle.block;
+ const Extent3D num_tiles = swizzle.num_tiles;
+ const u32 bytes_per_block = BytesPerBlock(info.format);
+ const u32 stride_alignment = CalculateLevelStrideAlignment(info, swizzle.level);
+ const u32 stride = Common::AlignUpLog2(num_tiles.width, stride_alignment) * bytes_per_block;
+ const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
+ return BlockLinearSwizzle2DParams{
+ .origin{0, 0, 0},
+ .destination{0, 0, 0},
+ .bytes_per_block_log2 = static_cast<u32>(std::countr_zero(bytes_per_block)),
+ .layer_stride = info.layer_stride,
+ .block_size = gobs_in_x << (GOB_SIZE_SHIFT + block.height + block.depth),
+ .x_shift = GOB_SIZE_SHIFT + block.height + block.depth,
+ .block_height = block.height,
+ .block_height_mask = (1U << block.height) - 1,
+ };
+}
+
+BlockLinearSwizzle3DParams MakeBlockLinearSwizzle3DParams(const SwizzleParameters& swizzle,
+ const ImageInfo& info) {
+ const Extent3D block = swizzle.block;
+ const Extent3D num_tiles = swizzle.num_tiles;
+ const u32 bytes_per_block = BytesPerBlock(info.format);
+ const u32 stride_alignment = CalculateLevelStrideAlignment(info, swizzle.level);
+ const u32 stride = Common::AlignUpLog2(num_tiles.width, stride_alignment) * bytes_per_block;
+
+ const u32 gobs_in_x = (stride + GOB_SIZE_X - 1) >> GOB_SIZE_X_SHIFT;
+ const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block.height + block.depth);
+ const u32 slice_size =
+ Common::DivCeilLog2(num_tiles.height, block.height + GOB_SIZE_Y_SHIFT) * block_size;
+ return BlockLinearSwizzle3DParams{
+ .origin{0, 0, 0},
+ .destination{0, 0, 0},
+ .bytes_per_block_log2 = static_cast<u32>(std::countr_zero(bytes_per_block)),
+ .slice_size = slice_size,
+ .block_size = block_size,
+ .x_shift = GOB_SIZE_SHIFT + block.height + block.depth,
+ .block_height = block.height,
+ .block_height_mask = (1U << block.height) - 1,
+ .block_depth = block.depth,
+ .block_depth_mask = (1U << block.depth) - 1,
+ };
+}
+
+} // namespace VideoCommon::Accelerated \ No newline at end of file
diff --git a/src/video_core/texture_cache/accelerated_swizzle.h b/src/video_core/texture_cache/accelerated_swizzle.h
new file mode 100644
index 000000000..6ec5c78c4
--- /dev/null
+++ b/src/video_core/texture_cache/accelerated_swizzle.h
@@ -0,0 +1,45 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "video_core/texture_cache/image_info.h"
+#include "video_core/texture_cache/types.h"
+
+namespace VideoCommon::Accelerated {
+
+struct BlockLinearSwizzle2DParams {
+ std::array<u32, 3> origin;
+ std::array<s32, 3> destination;
+ u32 bytes_per_block_log2;
+ u32 layer_stride;
+ u32 block_size;
+ u32 x_shift;
+ u32 block_height;
+ u32 block_height_mask;
+};
+
+struct BlockLinearSwizzle3DParams {
+ std::array<u32, 3> origin;
+ std::array<s32, 3> destination;
+ u32 bytes_per_block_log2;
+ u32 slice_size;
+ u32 block_size;
+ u32 x_shift;
+ u32 block_height;
+ u32 block_height_mask;
+ u32 block_depth;
+ u32 block_depth_mask;
+};
+
+[[nodiscard]] BlockLinearSwizzle2DParams MakeBlockLinearSwizzle2DParams(
+ const SwizzleParameters& swizzle, const ImageInfo& info);
+
+[[nodiscard]] BlockLinearSwizzle3DParams MakeBlockLinearSwizzle3DParams(
+ const SwizzleParameters& swizzle, const ImageInfo& info);
+
+} // namespace VideoCommon::Accelerated
diff --git a/src/video_core/texture_cache/copy_params.h b/src/video_core/texture_cache/copy_params.h
deleted file mode 100644
index 9c21a0649..000000000
--- a/src/video_core/texture_cache/copy_params.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_types.h"
-
-namespace VideoCommon {
-
-struct CopyParams {
- constexpr CopyParams(u32 source_x, u32 source_y, u32 source_z, u32 dest_x, u32 dest_y,
- u32 dest_z, u32 source_level, u32 dest_level, u32 width, u32 height,
- u32 depth)
- : source_x{source_x}, source_y{source_y}, source_z{source_z}, dest_x{dest_x},
- dest_y{dest_y}, dest_z{dest_z}, source_level{source_level},
- dest_level{dest_level}, width{width}, height{height}, depth{depth} {}
-
- constexpr CopyParams(u32 width, u32 height, u32 depth, u32 level)
- : source_x{}, source_y{}, source_z{}, dest_x{}, dest_y{}, dest_z{}, source_level{level},
- dest_level{level}, width{width}, height{height}, depth{depth} {}
-
- u32 source_x;
- u32 source_y;
- u32 source_z;
- u32 dest_x;
- u32 dest_y;
- u32 dest_z;
- u32 source_level;
- u32 dest_level;
- u32 width;
- u32 height;
- u32 depth;
-};
-
-} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/decode_bc4.cpp b/src/video_core/texture_cache/decode_bc4.cpp
new file mode 100644
index 000000000..017327975
--- /dev/null
+++ b/src/video_core/texture_cache/decode_bc4.cpp
@@ -0,0 +1,97 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <span>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "video_core/texture_cache/decode_bc4.h"
+#include "video_core/texture_cache/types.h"
+
+namespace VideoCommon {
+
+// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_rgtc.txt
+[[nodiscard]] constexpr u32 DecompressBlock(u64 bits, u32 x, u32 y) {
+ const u32 code_offset = 16 + 3 * (4 * y + x);
+ const u32 code = (bits >> code_offset) & 7;
+ const u32 red0 = (bits >> 0) & 0xff;
+ const u32 red1 = (bits >> 8) & 0xff;
+ if (red0 > red1) {
+ switch (code) {
+ case 0:
+ return red0;
+ case 1:
+ return red1;
+ case 2:
+ return (6 * red0 + 1 * red1) / 7;
+ case 3:
+ return (5 * red0 + 2 * red1) / 7;
+ case 4:
+ return (4 * red0 + 3 * red1) / 7;
+ case 5:
+ return (3 * red0 + 4 * red1) / 7;
+ case 6:
+ return (2 * red0 + 5 * red1) / 7;
+ case 7:
+ return (1 * red0 + 6 * red1) / 7;
+ }
+ } else {
+ switch (code) {
+ case 0:
+ return red0;
+ case 1:
+ return red1;
+ case 2:
+ return (4 * red0 + 1 * red1) / 5;
+ case 3:
+ return (3 * red0 + 2 * red1) / 5;
+ case 4:
+ return (2 * red0 + 3 * red1) / 5;
+ case 5:
+ return (1 * red0 + 4 * red1) / 5;
+ case 6:
+ return 0;
+ case 7:
+ return 0xff;
+ }
+ }
+ return 0;
+}
+
+void DecompressBC4(std::span<const u8> input, Extent3D extent, std::span<u8> output) {
+ UNIMPLEMENTED_IF_MSG(extent.width % 4 != 0, "Unaligned width={}", extent.width);
+ UNIMPLEMENTED_IF_MSG(extent.height % 4 != 0, "Unaligned height={}", extent.height);
+ static constexpr u32 BLOCK_SIZE = 4;
+ size_t input_offset = 0;
+ for (u32 slice = 0; slice < extent.depth; ++slice) {
+ for (u32 block_y = 0; block_y < extent.height / 4; ++block_y) {
+ for (u32 block_x = 0; block_x < extent.width / 4; ++block_x) {
+ u64 bits;
+ std::memcpy(&bits, &input[input_offset], sizeof(bits));
+ input_offset += sizeof(bits);
+
+ for (u32 y = 0; y < BLOCK_SIZE; ++y) {
+ for (u32 x = 0; x < BLOCK_SIZE; ++x) {
+ const u32 linear_z = slice;
+ const u32 linear_y = block_y * BLOCK_SIZE + y;
+ const u32 linear_x = block_x * BLOCK_SIZE + x;
+ const u32 offset_z = linear_z * extent.width * extent.height;
+ const u32 offset_y = linear_y * extent.width;
+ const u32 offset_x = linear_x;
+ const u32 output_offset = (offset_z + offset_y + offset_x) * 4ULL;
+ const u32 color = DecompressBlock(bits, x, y);
+ output[output_offset + 0] = static_cast<u8>(color);
+ output[output_offset + 1] = 0;
+ output[output_offset + 2] = 0;
+ output[output_offset + 3] = 0xff;
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/decode_bc4.h b/src/video_core/texture_cache/decode_bc4.h
new file mode 100644
index 000000000..63fb23508
--- /dev/null
+++ b/src/video_core/texture_cache/decode_bc4.h
@@ -0,0 +1,16 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <span>
+
+#include "common/common_types.h"
+#include "video_core/texture_cache/types.h"
+
+namespace VideoCommon {
+
+void DecompressBC4(std::span<const u8> data, Extent3D extent, std::span<u8> output);
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/descriptor_table.h b/src/video_core/texture_cache/descriptor_table.h
new file mode 100644
index 000000000..3a03b786f
--- /dev/null
+++ b/src/video_core/texture_cache/descriptor_table.h
@@ -0,0 +1,82 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/div_ceil.h"
+#include "common/logging/log.h"
+#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_interface.h"
+
+namespace VideoCommon {
+
+template <typename Descriptor>
+class DescriptorTable {
+public:
+ explicit DescriptorTable(Tegra::MemoryManager& gpu_memory_) : gpu_memory{gpu_memory_} {}
+
+ [[nodiscard]] bool Synchornize(GPUVAddr gpu_addr, u32 limit) {
+ [[likely]] if (current_gpu_addr == gpu_addr && current_limit == limit) {
+ return false;
+ }
+ Refresh(gpu_addr, limit);
+ return true;
+ }
+
+ void Invalidate() noexcept {
+ std::ranges::fill(read_descriptors, 0);
+ }
+
+ [[nodiscard]] std::pair<Descriptor, bool> Read(u32 index) {
+ DEBUG_ASSERT(index <= current_limit);
+ const GPUVAddr gpu_addr = current_gpu_addr + index * sizeof(Descriptor);
+ std::pair<Descriptor, bool> result;
+ gpu_memory.ReadBlockUnsafe(gpu_addr, &result.first, sizeof(Descriptor));
+ if (IsDescriptorRead(index)) {
+ result.second = result.first != descriptors[index];
+ } else {
+ MarkDescriptorAsRead(index);
+ result.second = true;
+ }
+ if (result.second) {
+ descriptors[index] = result.first;
+ }
+ return result;
+ }
+
+ [[nodiscard]] u32 Limit() const noexcept {
+ return current_limit;
+ }
+
+private:
+ void Refresh(GPUVAddr gpu_addr, u32 limit) {
+ current_gpu_addr = gpu_addr;
+ current_limit = limit;
+
+ const size_t num_descriptors = static_cast<size_t>(limit) + 1;
+ read_descriptors.clear();
+ read_descriptors.resize(Common::DivCeil(num_descriptors, 64U), 0);
+ descriptors.resize(num_descriptors);
+ }
+
+ void MarkDescriptorAsRead(u32 index) noexcept {
+ read_descriptors[index / 64] |= 1ULL << (index % 64);
+ }
+
+ [[nodiscard]] bool IsDescriptorRead(u32 index) const noexcept {
+ return (read_descriptors[index / 64] & (1ULL << (index % 64))) != 0;
+ }
+
+ Tegra::MemoryManager& gpu_memory;
+ GPUVAddr current_gpu_addr{};
+ u32 current_limit{};
+ std::vector<u64> read_descriptors;
+ std::vector<Descriptor> descriptors;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index 7d5a75648..ddfb726fe 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <array>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "video_core/texture_cache/format_lookup_table.h"
@@ -20,198 +19,207 @@ constexpr auto UNORM = ComponentType::UNORM;
constexpr auto SINT = ComponentType::SINT;
constexpr auto UINT = ComponentType::UINT;
constexpr auto FLOAT = ComponentType::FLOAT;
-constexpr bool C = false; // Normal color
-constexpr bool S = true; // Srgb
-
-struct Table {
- constexpr Table(TextureFormat texture_format, bool is_srgb, ComponentType red_component,
- ComponentType green_component, ComponentType blue_component,
- ComponentType alpha_component, PixelFormat pixel_format)
- : texture_format{texture_format}, pixel_format{pixel_format}, red_component{red_component},
- green_component{green_component}, blue_component{blue_component},
- alpha_component{alpha_component}, is_srgb{is_srgb} {}
-
- TextureFormat texture_format;
- PixelFormat pixel_format;
- ComponentType red_component;
- ComponentType green_component;
- ComponentType blue_component;
- ComponentType alpha_component;
- bool is_srgb;
-};
-constexpr std::array<Table, 86> DefinitionTable = {{
- {TextureFormat::A8R8G8B8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A8B8G8R8_UNORM},
- {TextureFormat::A8R8G8B8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::A8B8G8R8_SNORM},
- {TextureFormat::A8R8G8B8, C, UINT, UINT, UINT, UINT, PixelFormat::A8B8G8R8_UINT},
- {TextureFormat::A8R8G8B8, C, SINT, SINT, SINT, SINT, PixelFormat::A8B8G8R8_SINT},
- {TextureFormat::A8R8G8B8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::A8B8G8R8_SRGB},
-
- {TextureFormat::B5G6R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::B5G6R5_UNORM},
-
- {TextureFormat::A2B10G10R10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A2B10G10R10_UNORM},
- {TextureFormat::A2B10G10R10, C, UINT, UINT, UINT, UINT, PixelFormat::A2B10G10R10_UINT},
-
- {TextureFormat::A1B5G5R5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A1B5G5R5_UNORM},
-
- {TextureFormat::A4B4G4R4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::A4B4G4R4_UNORM},
-
- {TextureFormat::R8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R8_UNORM},
- {TextureFormat::R8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R8_SNORM},
- {TextureFormat::R8, C, UINT, UINT, UINT, UINT, PixelFormat::R8_UINT},
- {TextureFormat::R8, C, SINT, SINT, SINT, SINT, PixelFormat::R8_SINT},
-
- {TextureFormat::R8G8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R8G8_UNORM},
- {TextureFormat::R8G8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R8G8_SNORM},
- {TextureFormat::R8G8, C, UINT, UINT, UINT, UINT, PixelFormat::R8G8_UINT},
- {TextureFormat::R8G8, C, SINT, SINT, SINT, SINT, PixelFormat::R8G8_SINT},
-
- {TextureFormat::R16G16B16A16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16G16B16A16_SNORM},
- {TextureFormat::R16G16B16A16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16G16B16A16_UNORM},
- {TextureFormat::R16G16B16A16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16G16B16A16_FLOAT},
- {TextureFormat::R16G16B16A16, C, UINT, UINT, UINT, UINT, PixelFormat::R16G16B16A16_UINT},
- {TextureFormat::R16G16B16A16, C, SINT, SINT, SINT, SINT, PixelFormat::R16G16B16A16_SINT},
-
- {TextureFormat::R16G16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16G16_FLOAT},
- {TextureFormat::R16G16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16G16_UNORM},
- {TextureFormat::R16G16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16G16_SNORM},
- {TextureFormat::R16G16, C, UINT, UINT, UINT, UINT, PixelFormat::R16G16_UINT},
- {TextureFormat::R16G16, C, SINT, SINT, SINT, SINT, PixelFormat::R16G16_SINT},
-
- {TextureFormat::R16, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R16_FLOAT},
- {TextureFormat::R16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::R16_UNORM},
- {TextureFormat::R16, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::R16_SNORM},
- {TextureFormat::R16, C, UINT, UINT, UINT, UINT, PixelFormat::R16_UINT},
- {TextureFormat::R16, C, SINT, SINT, SINT, SINT, PixelFormat::R16_SINT},
-
- {TextureFormat::B10G11R11, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::B10G11R11_FLOAT},
-
- {TextureFormat::R32G32B32A32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32B32A32_FLOAT},
- {TextureFormat::R32G32B32A32, C, UINT, UINT, UINT, UINT, PixelFormat::R32G32B32A32_UINT},
- {TextureFormat::R32G32B32A32, C, SINT, SINT, SINT, SINT, PixelFormat::R32G32B32A32_SINT},
-
- {TextureFormat::R32G32B32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32B32_FLOAT},
-
- {TextureFormat::R32G32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32G32_FLOAT},
- {TextureFormat::R32G32, C, UINT, UINT, UINT, UINT, PixelFormat::R32G32_UINT},
- {TextureFormat::R32G32, C, SINT, SINT, SINT, SINT, PixelFormat::R32G32_SINT},
-
- {TextureFormat::R32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32_FLOAT},
- {TextureFormat::R32, C, UINT, UINT, UINT, UINT, PixelFormat::R32_UINT},
- {TextureFormat::R32, C, SINT, SINT, SINT, SINT, PixelFormat::R32_SINT},
-
- {TextureFormat::E5B9G9R9, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::E5B9G9R9_FLOAT},
-
- {TextureFormat::D32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::D32_FLOAT},
- {TextureFormat::D16, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::D16_UNORM},
- {TextureFormat::S8D24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8_UINT_D24_UNORM},
- {TextureFormat::R8G24, C, UINT, UNORM, UNORM, UNORM, PixelFormat::S8_UINT_D24_UNORM},
- {TextureFormat::D32S8, C, FLOAT, UINT, UNORM, UNORM, PixelFormat::D32_FLOAT_S8_UINT},
-
- {TextureFormat::BC1_RGBA, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC1_RGBA_UNORM},
- {TextureFormat::BC1_RGBA, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC1_RGBA_SRGB},
-
- {TextureFormat::BC2, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC2_UNORM},
- {TextureFormat::BC2, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC2_SRGB},
-
- {TextureFormat::BC3, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC3_UNORM},
- {TextureFormat::BC3, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC3_SRGB},
-
- {TextureFormat::BC4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC4_UNORM},
- {TextureFormat::BC4, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::BC4_SNORM},
-
- {TextureFormat::BC5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC5_UNORM},
- {TextureFormat::BC5, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::BC5_SNORM},
-
- {TextureFormat::BC7, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7_UNORM},
- {TextureFormat::BC7, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::BC7_SRGB},
-
- {TextureFormat::BC6H_SFLOAT, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_SFLOAT},
- {TextureFormat::BC6H_UFLOAT, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::BC6H_UFLOAT},
-
- {TextureFormat::ASTC_2D_4X4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_4X4_UNORM},
- {TextureFormat::ASTC_2D_4X4, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_4X4_SRGB},
-
- {TextureFormat::ASTC_2D_5X4, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X4_UNORM},
- {TextureFormat::ASTC_2D_5X4, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X4_SRGB},
-
- {TextureFormat::ASTC_2D_5X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X5_UNORM},
- {TextureFormat::ASTC_2D_5X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_5X5_SRGB},
-
- {TextureFormat::ASTC_2D_8X8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X8_UNORM},
- {TextureFormat::ASTC_2D_8X8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X8_SRGB},
-
- {TextureFormat::ASTC_2D_8X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X5_UNORM},
- {TextureFormat::ASTC_2D_8X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X5_SRGB},
-
- {TextureFormat::ASTC_2D_10X8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X8_UNORM},
- {TextureFormat::ASTC_2D_10X8, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X8_SRGB},
-
- {TextureFormat::ASTC_2D_6X6, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X6_UNORM},
- {TextureFormat::ASTC_2D_6X6, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X6_SRGB},
-
- {TextureFormat::ASTC_2D_10X10, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X10_UNORM},
- {TextureFormat::ASTC_2D_10X10, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_10X10_SRGB},
-
- {TextureFormat::ASTC_2D_12X12, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_12X12_UNORM},
- {TextureFormat::ASTC_2D_12X12, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_12X12_SRGB},
-
- {TextureFormat::ASTC_2D_8X6, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X6_UNORM},
- {TextureFormat::ASTC_2D_8X6, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_8X6_SRGB},
+constexpr bool LINEAR = false;
+constexpr bool SRGB = true;
+
+constexpr u32 Hash(TextureFormat format, ComponentType red_component, ComponentType green_component,
+ ComponentType blue_component, ComponentType alpha_component, bool is_srgb) {
+ u32 hash = is_srgb ? 1 : 0;
+ hash |= static_cast<u32>(red_component) << 1;
+ hash |= static_cast<u32>(green_component) << 4;
+ hash |= static_cast<u32>(blue_component) << 7;
+ hash |= static_cast<u32>(alpha_component) << 10;
+ hash |= static_cast<u32>(format) << 13;
+ return hash;
+}
- {TextureFormat::ASTC_2D_6X5, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X5_UNORM},
- {TextureFormat::ASTC_2D_6X5, S, UNORM, UNORM, UNORM, UNORM, PixelFormat::ASTC_2D_6X5_SRGB},
-}};
+constexpr u32 Hash(TextureFormat format, ComponentType component, bool is_srgb = LINEAR) {
+ return Hash(format, component, component, component, component, is_srgb);
+}
} // Anonymous namespace
-FormatLookupTable::FormatLookupTable() {
- table.fill(static_cast<u8>(PixelFormat::Invalid));
-
- for (const auto& entry : DefinitionTable) {
- table[CalculateIndex(entry.texture_format, entry.is_srgb != 0, entry.red_component,
- entry.green_component, entry.blue_component, entry.alpha_component)] =
- static_cast<u8>(entry.pixel_format);
- }
-}
-
-PixelFormat FormatLookupTable::GetPixelFormat(TextureFormat format, bool is_srgb,
- ComponentType red_component,
- ComponentType green_component,
- ComponentType blue_component,
- ComponentType alpha_component) const noexcept {
- const auto pixel_format = static_cast<PixelFormat>(table[CalculateIndex(
- format, is_srgb, red_component, green_component, blue_component, alpha_component)]);
- // [[likely]]
- if (pixel_format != PixelFormat::Invalid) {
- return pixel_format;
+PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, ComponentType green,
+ ComponentType blue, ComponentType alpha,
+ bool is_srgb) noexcept {
+ switch (Hash(format, red, green, blue, alpha, is_srgb)) {
+ case Hash(TextureFormat::A8R8G8B8, UNORM):
+ return PixelFormat::A8B8G8R8_UNORM;
+ case Hash(TextureFormat::A8R8G8B8, SNORM):
+ return PixelFormat::A8B8G8R8_SNORM;
+ case Hash(TextureFormat::A8R8G8B8, UINT):
+ return PixelFormat::A8B8G8R8_UINT;
+ case Hash(TextureFormat::A8R8G8B8, SINT):
+ return PixelFormat::A8B8G8R8_SINT;
+ case Hash(TextureFormat::A8R8G8B8, UNORM, SRGB):
+ return PixelFormat::A8B8G8R8_SRGB;
+ case Hash(TextureFormat::B5G6R5, UNORM):
+ return PixelFormat::B5G6R5_UNORM;
+ case Hash(TextureFormat::A2B10G10R10, UNORM):
+ return PixelFormat::A2B10G10R10_UNORM;
+ case Hash(TextureFormat::A2B10G10R10, UINT):
+ return PixelFormat::A2B10G10R10_UINT;
+ case Hash(TextureFormat::A1B5G5R5, UNORM):
+ return PixelFormat::A1B5G5R5_UNORM;
+ case Hash(TextureFormat::A4B4G4R4, UNORM):
+ return PixelFormat::A4B4G4R4_UNORM;
+ case Hash(TextureFormat::R8, UNORM):
+ return PixelFormat::R8_UNORM;
+ case Hash(TextureFormat::R8, SNORM):
+ return PixelFormat::R8_SNORM;
+ case Hash(TextureFormat::R8, UINT):
+ return PixelFormat::R8_UINT;
+ case Hash(TextureFormat::R8, SINT):
+ return PixelFormat::R8_SINT;
+ case Hash(TextureFormat::R8G8, UNORM):
+ return PixelFormat::R8G8_UNORM;
+ case Hash(TextureFormat::R8G8, SNORM):
+ return PixelFormat::R8G8_SNORM;
+ case Hash(TextureFormat::R8G8, UINT):
+ return PixelFormat::R8G8_UINT;
+ case Hash(TextureFormat::R8G8, SINT):
+ return PixelFormat::R8G8_SINT;
+ case Hash(TextureFormat::R16G16B16A16, FLOAT):
+ return PixelFormat::R16G16B16A16_FLOAT;
+ case Hash(TextureFormat::R16G16B16A16, UNORM):
+ return PixelFormat::R16G16B16A16_UNORM;
+ case Hash(TextureFormat::R16G16B16A16, SNORM):
+ return PixelFormat::R16G16B16A16_SNORM;
+ case Hash(TextureFormat::R16G16B16A16, UINT):
+ return PixelFormat::R16G16B16A16_UINT;
+ case Hash(TextureFormat::R16G16B16A16, SINT):
+ return PixelFormat::R16G16B16A16_SINT;
+ case Hash(TextureFormat::R16G16, FLOAT):
+ return PixelFormat::R16G16_FLOAT;
+ case Hash(TextureFormat::R16G16, UNORM):
+ return PixelFormat::R16G16_UNORM;
+ case Hash(TextureFormat::R16G16, SNORM):
+ return PixelFormat::R16G16_SNORM;
+ case Hash(TextureFormat::R16G16, UINT):
+ return PixelFormat::R16G16_UINT;
+ case Hash(TextureFormat::R16G16, SINT):
+ return PixelFormat::R16G16_SINT;
+ case Hash(TextureFormat::R16, FLOAT):
+ return PixelFormat::R16_FLOAT;
+ case Hash(TextureFormat::R16, UNORM):
+ return PixelFormat::R16_UNORM;
+ case Hash(TextureFormat::R16, SNORM):
+ return PixelFormat::R16_SNORM;
+ case Hash(TextureFormat::R16, UINT):
+ return PixelFormat::R16_UINT;
+ case Hash(TextureFormat::R16, SINT):
+ return PixelFormat::R16_SINT;
+ case Hash(TextureFormat::B10G11R11, FLOAT):
+ return PixelFormat::B10G11R11_FLOAT;
+ case Hash(TextureFormat::R32G32B32A32, FLOAT):
+ return PixelFormat::R32G32B32A32_FLOAT;
+ case Hash(TextureFormat::R32G32B32A32, UINT):
+ return PixelFormat::R32G32B32A32_UINT;
+ case Hash(TextureFormat::R32G32B32A32, SINT):
+ return PixelFormat::R32G32B32A32_SINT;
+ case Hash(TextureFormat::R32G32B32, FLOAT):
+ return PixelFormat::R32G32B32_FLOAT;
+ case Hash(TextureFormat::R32G32, FLOAT):
+ return PixelFormat::R32G32_FLOAT;
+ case Hash(TextureFormat::R32G32, UINT):
+ return PixelFormat::R32G32_UINT;
+ case Hash(TextureFormat::R32G32, SINT):
+ return PixelFormat::R32G32_SINT;
+ case Hash(TextureFormat::R32, FLOAT):
+ return PixelFormat::R32_FLOAT;
+ case Hash(TextureFormat::R32, UINT):
+ return PixelFormat::R32_UINT;
+ case Hash(TextureFormat::R32, SINT):
+ return PixelFormat::R32_SINT;
+ case Hash(TextureFormat::E5B9G9R9, FLOAT):
+ return PixelFormat::E5B9G9R9_FLOAT;
+ case Hash(TextureFormat::D32, FLOAT):
+ return PixelFormat::D32_FLOAT;
+ case Hash(TextureFormat::D16, UNORM):
+ return PixelFormat::D16_UNORM;
+ case Hash(TextureFormat::S8D24, UINT, UNORM, UNORM, UNORM, LINEAR):
+ return PixelFormat::S8_UINT_D24_UNORM;
+ case Hash(TextureFormat::R8G24, UINT, UNORM, UNORM, UNORM, LINEAR):
+ return PixelFormat::S8_UINT_D24_UNORM;
+ case Hash(TextureFormat::D32S8, FLOAT, UINT, UNORM, UNORM, LINEAR):
+ return PixelFormat::D32_FLOAT_S8_UINT;
+ case Hash(TextureFormat::BC1_RGBA, UNORM, LINEAR):
+ return PixelFormat::BC1_RGBA_UNORM;
+ case Hash(TextureFormat::BC1_RGBA, UNORM, SRGB):
+ return PixelFormat::BC1_RGBA_SRGB;
+ case Hash(TextureFormat::BC2, UNORM, LINEAR):
+ return PixelFormat::BC2_UNORM;
+ case Hash(TextureFormat::BC2, UNORM, SRGB):
+ return PixelFormat::BC2_SRGB;
+ case Hash(TextureFormat::BC3, UNORM, LINEAR):
+ return PixelFormat::BC3_UNORM;
+ case Hash(TextureFormat::BC3, UNORM, SRGB):
+ return PixelFormat::BC3_SRGB;
+ case Hash(TextureFormat::BC4, UNORM):
+ return PixelFormat::BC4_UNORM;
+ case Hash(TextureFormat::BC4, SNORM):
+ return PixelFormat::BC4_SNORM;
+ case Hash(TextureFormat::BC5, UNORM):
+ return PixelFormat::BC5_UNORM;
+ case Hash(TextureFormat::BC5, SNORM):
+ return PixelFormat::BC5_SNORM;
+ case Hash(TextureFormat::BC7, UNORM, LINEAR):
+ return PixelFormat::BC7_UNORM;
+ case Hash(TextureFormat::BC7, UNORM, SRGB):
+ return PixelFormat::BC7_SRGB;
+ case Hash(TextureFormat::BC6H_SFLOAT, FLOAT):
+ return PixelFormat::BC6H_SFLOAT;
+ case Hash(TextureFormat::BC6H_UFLOAT, FLOAT):
+ return PixelFormat::BC6H_UFLOAT;
+ case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_4X4_UNORM;
+ case Hash(TextureFormat::ASTC_2D_4X4, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_4X4_SRGB;
+ case Hash(TextureFormat::ASTC_2D_5X4, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_5X4_UNORM;
+ case Hash(TextureFormat::ASTC_2D_5X4, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_5X4_SRGB;
+ case Hash(TextureFormat::ASTC_2D_5X5, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_5X5_UNORM;
+ case Hash(TextureFormat::ASTC_2D_5X5, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_5X5_SRGB;
+ case Hash(TextureFormat::ASTC_2D_8X8, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_8X8_UNORM;
+ case Hash(TextureFormat::ASTC_2D_8X8, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_8X8_SRGB;
+ case Hash(TextureFormat::ASTC_2D_8X5, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_8X5_UNORM;
+ case Hash(TextureFormat::ASTC_2D_8X5, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_8X5_SRGB;
+ case Hash(TextureFormat::ASTC_2D_10X8, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_10X8_UNORM;
+ case Hash(TextureFormat::ASTC_2D_10X8, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_10X8_SRGB;
+ case Hash(TextureFormat::ASTC_2D_6X6, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_6X6_UNORM;
+ case Hash(TextureFormat::ASTC_2D_6X6, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_6X6_SRGB;
+ case Hash(TextureFormat::ASTC_2D_10X10, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_10X10_UNORM;
+ case Hash(TextureFormat::ASTC_2D_10X10, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_10X10_SRGB;
+ case Hash(TextureFormat::ASTC_2D_12X12, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_12X12_UNORM;
+ case Hash(TextureFormat::ASTC_2D_12X12, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_12X12_SRGB;
+ case Hash(TextureFormat::ASTC_2D_8X6, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_8X6_UNORM;
+ case Hash(TextureFormat::ASTC_2D_8X6, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_8X6_SRGB;
+ case Hash(TextureFormat::ASTC_2D_6X5, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_6X5_UNORM;
+ case Hash(TextureFormat::ASTC_2D_6X5, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_6X5_SRGB;
}
UNIMPLEMENTED_MSG("texture format={} srgb={} components={{{} {} {} {}}}",
- static_cast<int>(format), is_srgb, static_cast<int>(red_component),
- static_cast<int>(green_component), static_cast<int>(blue_component),
- static_cast<int>(alpha_component));
+ static_cast<int>(format), is_srgb, static_cast<int>(red),
+ static_cast<int>(green), static_cast<int>(blue), static_cast<int>(alpha));
return PixelFormat::A8B8G8R8_UNORM;
}
-void FormatLookupTable::Set(TextureFormat format, bool is_srgb, ComponentType red_component,
- ComponentType green_component, ComponentType blue_component,
- ComponentType alpha_component, PixelFormat pixel_format) {}
-
-std::size_t FormatLookupTable::CalculateIndex(TextureFormat format, bool is_srgb,
- ComponentType red_component,
- ComponentType green_component,
- ComponentType blue_component,
- ComponentType alpha_component) noexcept {
- const auto format_index = static_cast<std::size_t>(format);
- const auto red_index = static_cast<std::size_t>(red_component);
- const auto green_index = static_cast<std::size_t>(green_component);
- const auto blue_index = static_cast<std::size_t>(blue_component);
- const auto alpha_index = static_cast<std::size_t>(alpha_component);
- const std::size_t srgb_index = is_srgb ? 1 : 0;
-
- return format_index * PerFormat +
- srgb_index * PerComponent * PerComponent * PerComponent * PerComponent +
- alpha_index * PerComponent * PerComponent * PerComponent +
- blue_index * PerComponent * PerComponent + green_index * PerComponent + red_index;
-}
-
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/format_lookup_table.h b/src/video_core/texture_cache/format_lookup_table.h
index aa77e0a5a..729533999 100644
--- a/src/video_core/texture_cache/format_lookup_table.h
+++ b/src/video_core/texture_cache/format_lookup_table.h
@@ -4,48 +4,14 @@
#pragma once
-#include <array>
-#include <limits>
#include "video_core/surface.h"
#include "video_core/textures/texture.h"
namespace VideoCommon {
-class FormatLookupTable {
-public:
- explicit FormatLookupTable();
-
- VideoCore::Surface::PixelFormat GetPixelFormat(
- Tegra::Texture::TextureFormat format, bool is_srgb,
- Tegra::Texture::ComponentType red_component, Tegra::Texture::ComponentType green_component,
- Tegra::Texture::ComponentType blue_component,
- Tegra::Texture::ComponentType alpha_component) const noexcept;
-
-private:
- static_assert(VideoCore::Surface::MaxPixelFormat <= std::numeric_limits<u8>::max());
-
- static constexpr std::size_t NumTextureFormats = 128;
-
- static constexpr std::size_t PerComponent = 8;
- static constexpr std::size_t PerComponents2 = PerComponent * PerComponent;
- static constexpr std::size_t PerComponents3 = PerComponents2 * PerComponent;
- static constexpr std::size_t PerComponents4 = PerComponents3 * PerComponent;
- static constexpr std::size_t PerFormat = PerComponents4 * 2;
-
- static std::size_t CalculateIndex(Tegra::Texture::TextureFormat format, bool is_srgb,
- Tegra::Texture::ComponentType red_component,
- Tegra::Texture::ComponentType green_component,
- Tegra::Texture::ComponentType blue_component,
- Tegra::Texture::ComponentType alpha_component) noexcept;
-
- void Set(Tegra::Texture::TextureFormat format, bool is_srgb,
- Tegra::Texture::ComponentType red_component,
- Tegra::Texture::ComponentType green_component,
- Tegra::Texture::ComponentType blue_component,
- Tegra::Texture::ComponentType alpha_component,
- VideoCore::Surface::PixelFormat pixel_format);
-
- std::array<u8, NumTextureFormats * PerFormat> table;
-};
+VideoCore::Surface::PixelFormat PixelFormatFromTextureInfo(
+ Tegra::Texture::TextureFormat format, Tegra::Texture::ComponentType red_component,
+ Tegra::Texture::ComponentType green_component, Tegra::Texture::ComponentType blue_component,
+ Tegra::Texture::ComponentType alpha_component, bool is_srgb) noexcept;
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/formatter.cpp b/src/video_core/texture_cache/formatter.cpp
new file mode 100644
index 000000000..d10ba4ccd
--- /dev/null
+++ b/src/video_core/texture_cache/formatter.cpp
@@ -0,0 +1,95 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <string>
+
+#include "video_core/texture_cache/formatter.h"
+#include "video_core/texture_cache/image_base.h"
+#include "video_core/texture_cache/image_info.h"
+#include "video_core/texture_cache/image_view_base.h"
+#include "video_core/texture_cache/render_targets.h"
+
+namespace VideoCommon {
+
+std::string Name(const ImageBase& image) {
+ const GPUVAddr gpu_addr = image.gpu_addr;
+ const ImageInfo& info = image.info;
+ const u32 width = info.size.width;
+ const u32 height = info.size.height;
+ const u32 depth = info.size.depth;
+ const u32 num_layers = image.info.resources.layers;
+ const u32 num_levels = image.info.resources.levels;
+ std::string resource;
+ if (num_layers > 1) {
+ resource += fmt::format(":L{}", num_layers);
+ }
+ if (num_levels > 1) {
+ resource += fmt::format(":M{}", num_levels);
+ }
+ switch (image.info.type) {
+ case ImageType::e1D:
+ return fmt::format("Image 1D 0x{:x} {}{}", gpu_addr, width, resource);
+ case ImageType::e2D:
+ return fmt::format("Image 2D 0x{:x} {}x{}{}", gpu_addr, width, height, resource);
+ case ImageType::e3D:
+ return fmt::format("Image 2D 0x{:x} {}x{}x{}{}", gpu_addr, width, height, depth, resource);
+ case ImageType::Linear:
+ return fmt::format("Image Linear 0x{:x} {}x{}", gpu_addr, width, height);
+ case ImageType::Buffer:
+ return fmt::format("Buffer 0x{:x} {}", image.gpu_addr, image.info.size.width);
+ }
+ return "Invalid";
+}
+
+std::string Name(const ImageViewBase& image_view, std::optional<ImageViewType> type) {
+ const u32 width = image_view.size.width;
+ const u32 height = image_view.size.height;
+ const u32 depth = image_view.size.depth;
+ const u32 num_levels = image_view.range.extent.levels;
+ const u32 num_layers = image_view.range.extent.layers;
+
+ const std::string level = num_levels > 1 ? fmt::format(":{}", num_levels) : "";
+ switch (type.value_or(image_view.type)) {
+ case ImageViewType::e1D:
+ return fmt::format("ImageView 1D {}{}", width, level);
+ case ImageViewType::e2D:
+ return fmt::format("ImageView 2D {}x{}{}", width, height, level);
+ case ImageViewType::Cube:
+ return fmt::format("ImageView Cube {}x{}{}", width, height, level);
+ case ImageViewType::e3D:
+ return fmt::format("ImageView 3D {}x{}x{}{}", width, height, depth, level);
+ case ImageViewType::e1DArray:
+ return fmt::format("ImageView 1DArray {}{}|{}", width, level, num_layers);
+ case ImageViewType::e2DArray:
+ return fmt::format("ImageView 2DArray {}x{}{}|{}", width, height, level, num_layers);
+ case ImageViewType::CubeArray:
+ return fmt::format("ImageView CubeArray {}x{}{}|{}", width, height, level, num_layers);
+ case ImageViewType::Rect:
+ return fmt::format("ImageView Rect {}x{}{}", width, height, level);
+ case ImageViewType::Buffer:
+ return fmt::format("BufferView {}", width);
+ }
+ return "Invalid";
+}
+
+std::string Name(const RenderTargets& render_targets) {
+ std::string_view debug_prefix;
+ const auto num_color = std::ranges::count_if(
+ render_targets.color_buffer_ids, [](ImageViewId id) { return static_cast<bool>(id); });
+ if (render_targets.depth_buffer_id) {
+ debug_prefix = num_color > 0 ? "R" : "Z";
+ } else {
+ debug_prefix = num_color > 0 ? "C" : "X";
+ }
+ const Extent2D size = render_targets.size;
+ if (num_color > 0) {
+ return fmt::format("Framebuffer {}{} {}x{}", debug_prefix, num_color, size.width,
+ size.height);
+ } else {
+ return fmt::format("Framebuffer {} {}x{}", debug_prefix, size.width, size.height);
+ }
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h
new file mode 100644
index 000000000..a48413983
--- /dev/null
+++ b/src/video_core/texture_cache/formatter.h
@@ -0,0 +1,263 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+#include <fmt/format.h>
+
+#include "video_core/surface.h"
+#include "video_core/texture_cache/types.h"
+
+template <>
+struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::string_view> {
+ template <typename FormatContext>
+ auto format(VideoCore::Surface::PixelFormat format, FormatContext& ctx) {
+ using VideoCore::Surface::PixelFormat;
+ const string_view name = [format] {
+ switch (format) {
+ case PixelFormat::A8B8G8R8_UNORM:
+ return "A8B8G8R8_UNORM";
+ case PixelFormat::A8B8G8R8_SNORM:
+ return "A8B8G8R8_SNORM";
+ case PixelFormat::A8B8G8R8_SINT:
+ return "A8B8G8R8_SINT";
+ case PixelFormat::A8B8G8R8_UINT:
+ return "A8B8G8R8_UINT";
+ case PixelFormat::R5G6B5_UNORM:
+ return "R5G6B5_UNORM";
+ case PixelFormat::B5G6R5_UNORM:
+ return "B5G6R5_UNORM";
+ case PixelFormat::A1R5G5B5_UNORM:
+ return "A1R5G5B5_UNORM";
+ case PixelFormat::A2B10G10R10_UNORM:
+ return "A2B10G10R10_UNORM";
+ case PixelFormat::A2B10G10R10_UINT:
+ return "A2B10G10R10_UINT";
+ case PixelFormat::A1B5G5R5_UNORM:
+ return "A1B5G5R5_UNORM";
+ case PixelFormat::R8_UNORM:
+ return "R8_UNORM";
+ case PixelFormat::R8_SNORM:
+ return "R8_SNORM";
+ case PixelFormat::R8_SINT:
+ return "R8_SINT";
+ case PixelFormat::R8_UINT:
+ return "R8_UINT";
+ case PixelFormat::R16G16B16A16_FLOAT:
+ return "R16G16B16A16_FLOAT";
+ case PixelFormat::R16G16B16A16_UNORM:
+ return "R16G16B16A16_UNORM";
+ case PixelFormat::R16G16B16A16_SNORM:
+ return "R16G16B16A16_SNORM";
+ case PixelFormat::R16G16B16A16_SINT:
+ return "R16G16B16A16_SINT";
+ case PixelFormat::R16G16B16A16_UINT:
+ return "R16G16B16A16_UINT";
+ case PixelFormat::B10G11R11_FLOAT:
+ return "B10G11R11_FLOAT";
+ case PixelFormat::R32G32B32A32_UINT:
+ return "R32G32B32A32_UINT";
+ case PixelFormat::BC1_RGBA_UNORM:
+ return "BC1_RGBA_UNORM";
+ case PixelFormat::BC2_UNORM:
+ return "BC2_UNORM";
+ case PixelFormat::BC3_UNORM:
+ return "BC3_UNORM";
+ case PixelFormat::BC4_UNORM:
+ return "BC4_UNORM";
+ case PixelFormat::BC4_SNORM:
+ return "BC4_SNORM";
+ case PixelFormat::BC5_UNORM:
+ return "BC5_UNORM";
+ case PixelFormat::BC5_SNORM:
+ return "BC5_SNORM";
+ case PixelFormat::BC7_UNORM:
+ return "BC7_UNORM";
+ case PixelFormat::BC6H_UFLOAT:
+ return "BC6H_UFLOAT";
+ case PixelFormat::BC6H_SFLOAT:
+ return "BC6H_SFLOAT";
+ case PixelFormat::ASTC_2D_4X4_UNORM:
+ return "ASTC_2D_4X4_UNORM";
+ case PixelFormat::B8G8R8A8_UNORM:
+ return "B8G8R8A8_UNORM";
+ case PixelFormat::R32G32B32A32_FLOAT:
+ return "R32G32B32A32_FLOAT";
+ case PixelFormat::R32G32B32A32_SINT:
+ return "R32G32B32A32_SINT";
+ case PixelFormat::R32G32_FLOAT:
+ return "R32G32_FLOAT";
+ case PixelFormat::R32G32_SINT:
+ return "R32G32_SINT";
+ case PixelFormat::R32_FLOAT:
+ return "R32_FLOAT";
+ case PixelFormat::R16_FLOAT:
+ return "R16_FLOAT";
+ case PixelFormat::R16_UNORM:
+ return "R16_UNORM";
+ case PixelFormat::R16_SNORM:
+ return "R16_SNORM";
+ case PixelFormat::R16_UINT:
+ return "R16_UINT";
+ case PixelFormat::R16_SINT:
+ return "R16_SINT";
+ case PixelFormat::R16G16_UNORM:
+ return "R16G16_UNORM";
+ case PixelFormat::R16G16_FLOAT:
+ return "R16G16_FLOAT";
+ case PixelFormat::R16G16_UINT:
+ return "R16G16_UINT";
+ case PixelFormat::R16G16_SINT:
+ return "R16G16_SINT";
+ case PixelFormat::R16G16_SNORM:
+ return "R16G16_SNORM";
+ case PixelFormat::R32G32B32_FLOAT:
+ return "R32G32B32_FLOAT";
+ case PixelFormat::A8B8G8R8_SRGB:
+ return "A8B8G8R8_SRGB";
+ case PixelFormat::R8G8_UNORM:
+ return "R8G8_UNORM";
+ case PixelFormat::R8G8_SNORM:
+ return "R8G8_SNORM";
+ case PixelFormat::R8G8_SINT:
+ return "R8G8_SINT";
+ case PixelFormat::R8G8_UINT:
+ return "R8G8_UINT";
+ case PixelFormat::R32G32_UINT:
+ return "R32G32_UINT";
+ case PixelFormat::R16G16B16X16_FLOAT:
+ return "R16G16B16X16_FLOAT";
+ case PixelFormat::R32_UINT:
+ return "R32_UINT";
+ case PixelFormat::R32_SINT:
+ return "R32_SINT";
+ case PixelFormat::ASTC_2D_8X8_UNORM:
+ return "ASTC_2D_8X8_UNORM";
+ case PixelFormat::ASTC_2D_8X5_UNORM:
+ return "ASTC_2D_8X5_UNORM";
+ case PixelFormat::ASTC_2D_5X4_UNORM:
+ return "ASTC_2D_5X4_UNORM";
+ case PixelFormat::B8G8R8A8_SRGB:
+ return "B8G8R8A8_SRGB";
+ case PixelFormat::BC1_RGBA_SRGB:
+ return "BC1_RGBA_SRGB";
+ case PixelFormat::BC2_SRGB:
+ return "BC2_SRGB";
+ case PixelFormat::BC3_SRGB:
+ return "BC3_SRGB";
+ case PixelFormat::BC7_SRGB:
+ return "BC7_SRGB";
+ case PixelFormat::A4B4G4R4_UNORM:
+ return "A4B4G4R4_UNORM";
+ case PixelFormat::ASTC_2D_4X4_SRGB:
+ return "ASTC_2D_4X4_SRGB";
+ case PixelFormat::ASTC_2D_8X8_SRGB:
+ return "ASTC_2D_8X8_SRGB";
+ case PixelFormat::ASTC_2D_8X5_SRGB:
+ return "ASTC_2D_8X5_SRGB";
+ case PixelFormat::ASTC_2D_5X4_SRGB:
+ return "ASTC_2D_5X4_SRGB";
+ case PixelFormat::ASTC_2D_5X5_UNORM:
+ return "ASTC_2D_5X5_UNORM";
+ case PixelFormat::ASTC_2D_5X5_SRGB:
+ return "ASTC_2D_5X5_SRGB";
+ case PixelFormat::ASTC_2D_10X8_UNORM:
+ return "ASTC_2D_10X8_UNORM";
+ case PixelFormat::ASTC_2D_10X8_SRGB:
+ return "ASTC_2D_10X8_SRGB";
+ case PixelFormat::ASTC_2D_6X6_UNORM:
+ return "ASTC_2D_6X6_UNORM";
+ case PixelFormat::ASTC_2D_6X6_SRGB:
+ return "ASTC_2D_6X6_SRGB";
+ case PixelFormat::ASTC_2D_10X10_UNORM:
+ return "ASTC_2D_10X10_UNORM";
+ case PixelFormat::ASTC_2D_10X10_SRGB:
+ return "ASTC_2D_10X10_SRGB";
+ case PixelFormat::ASTC_2D_12X12_UNORM:
+ return "ASTC_2D_12X12_UNORM";
+ case PixelFormat::ASTC_2D_12X12_SRGB:
+ return "ASTC_2D_12X12_SRGB";
+ case PixelFormat::ASTC_2D_8X6_UNORM:
+ return "ASTC_2D_8X6_UNORM";
+ case PixelFormat::ASTC_2D_8X6_SRGB:
+ return "ASTC_2D_8X6_SRGB";
+ case PixelFormat::ASTC_2D_6X5_UNORM:
+ return "ASTC_2D_6X5_UNORM";
+ case PixelFormat::ASTC_2D_6X5_SRGB:
+ return "ASTC_2D_6X5_SRGB";
+ case PixelFormat::E5B9G9R9_FLOAT:
+ return "E5B9G9R9_FLOAT";
+ case PixelFormat::D32_FLOAT:
+ return "D32_FLOAT";
+ case PixelFormat::D16_UNORM:
+ return "D16_UNORM";
+ case PixelFormat::D24_UNORM_S8_UINT:
+ return "D24_UNORM_S8_UINT";
+ case PixelFormat::S8_UINT_D24_UNORM:
+ return "S8_UINT_D24_UNORM";
+ case PixelFormat::D32_FLOAT_S8_UINT:
+ return "D32_FLOAT_S8_UINT";
+ case PixelFormat::MaxDepthStencilFormat:
+ case PixelFormat::Invalid:
+ return "Invalid";
+ }
+ return "Invalid";
+ }();
+ return formatter<string_view>::format(name, ctx);
+ }
+};
+
+template <>
+struct fmt::formatter<VideoCommon::ImageType> : fmt::formatter<fmt::string_view> {
+ template <typename FormatContext>
+ auto format(VideoCommon::ImageType type, FormatContext& ctx) {
+ const string_view name = [type] {
+ using VideoCommon::ImageType;
+ switch (type) {
+ case ImageType::e1D:
+ return "1D";
+ case ImageType::e2D:
+ return "2D";
+ case ImageType::e3D:
+ return "3D";
+ case ImageType::Linear:
+ return "Linear";
+ case ImageType::Buffer:
+ return "Buffer";
+ }
+ return "Invalid";
+ }();
+ return formatter<string_view>::format(name, ctx);
+ }
+};
+
+template <>
+struct fmt::formatter<VideoCommon::Extent3D> {
+ constexpr auto parse(fmt::format_parse_context& ctx) {
+ return ctx.begin();
+ }
+
+ template <typename FormatContext>
+ auto format(const VideoCommon::Extent3D& extent, FormatContext& ctx) {
+ return fmt::format_to(ctx.out(), "{{{}, {}, {}}}", extent.width, extent.height,
+ extent.depth);
+ }
+};
+
+namespace VideoCommon {
+
+struct ImageBase;
+struct ImageViewBase;
+struct RenderTargets;
+
+[[nodiscard]] std::string Name(const ImageBase& image);
+
+[[nodiscard]] std::string Name(const ImageViewBase& image_view,
+ std::optional<ImageViewType> type = std::nullopt);
+
+[[nodiscard]] std::string Name(const RenderTargets& render_targets);
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_base.cpp b/src/video_core/texture_cache/image_base.cpp
new file mode 100644
index 000000000..959b3f115
--- /dev/null
+++ b/src/video_core/texture_cache/image_base.cpp
@@ -0,0 +1,218 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <optional>
+#include <utility>
+#include <vector>
+
+#include "common/common_types.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/formatter.h"
+#include "video_core/texture_cache/image_base.h"
+#include "video_core/texture_cache/image_view_info.h"
+#include "video_core/texture_cache/util.h"
+
+namespace VideoCommon {
+
+using VideoCore::Surface::DefaultBlockHeight;
+using VideoCore::Surface::DefaultBlockWidth;
+
+namespace {
+/// Returns the base layer and mip level offset
+[[nodiscard]] std::pair<s32, s32> LayerMipOffset(s32 diff, u32 layer_stride) {
+ if (layer_stride == 0) {
+ return {0, diff};
+ } else {
+ return {diff / layer_stride, diff % layer_stride};
+ }
+}
+
+[[nodiscard]] bool ValidateLayers(const SubresourceLayers& layers, const ImageInfo& info) {
+ return layers.base_level < info.resources.levels &&
+ layers.base_layer + layers.num_layers <= info.resources.layers;
+}
+
+[[nodiscard]] bool ValidateCopy(const ImageCopy& copy, const ImageInfo& dst, const ImageInfo& src) {
+ const Extent3D src_size = MipSize(src.size, copy.src_subresource.base_level);
+ const Extent3D dst_size = MipSize(dst.size, copy.dst_subresource.base_level);
+ if (!ValidateLayers(copy.src_subresource, src)) {
+ return false;
+ }
+ if (!ValidateLayers(copy.dst_subresource, dst)) {
+ return false;
+ }
+ if (copy.src_offset.x + copy.extent.width > src_size.width ||
+ copy.src_offset.y + copy.extent.height > src_size.height ||
+ copy.src_offset.z + copy.extent.depth > src_size.depth) {
+ return false;
+ }
+ if (copy.dst_offset.x + copy.extent.width > dst_size.width ||
+ copy.dst_offset.y + copy.extent.height > dst_size.height ||
+ copy.dst_offset.z + copy.extent.depth > dst_size.depth) {
+ return false;
+ }
+ return true;
+}
+} // Anonymous namespace
+
+ImageBase::ImageBase(const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_)
+ : info{info_}, guest_size_bytes{CalculateGuestSizeInBytes(info)},
+ unswizzled_size_bytes{CalculateUnswizzledSizeBytes(info)},
+ converted_size_bytes{CalculateConvertedSizeBytes(info)}, gpu_addr{gpu_addr_},
+ cpu_addr{cpu_addr_}, cpu_addr_end{cpu_addr + guest_size_bytes},
+ mip_level_offsets{CalculateMipLevelOffsets(info)} {
+ if (info.type == ImageType::e3D) {
+ slice_offsets = CalculateSliceOffsets(info);
+ slice_subresources = CalculateSliceSubresources(info);
+ }
+}
+
+std::optional<SubresourceBase> ImageBase::TryFindBase(GPUVAddr other_addr) const noexcept {
+ if (other_addr < gpu_addr) {
+ // Subresource address can't be lower than the base
+ return std::nullopt;
+ }
+ const u32 diff = static_cast<u32>(other_addr - gpu_addr);
+ if (diff > guest_size_bytes) {
+ // This can happen when two CPU addresses are used for different GPU addresses
+ return std::nullopt;
+ }
+ if (info.type != ImageType::e3D) {
+ const auto [layer, mip_offset] = LayerMipOffset(diff, info.layer_stride);
+ const auto end = mip_level_offsets.begin() + info.resources.levels;
+ const auto it = std::find(mip_level_offsets.begin(), end, mip_offset);
+ if (layer > info.resources.layers || it == end) {
+ return std::nullopt;
+ }
+ return SubresourceBase{
+ .level = static_cast<s32>(std::distance(mip_level_offsets.begin(), it)),
+ .layer = layer,
+ };
+ } else {
+ // TODO: Consider using binary_search after a threshold
+ const auto it = std::ranges::find(slice_offsets, diff);
+ if (it == slice_offsets.cend()) {
+ return std::nullopt;
+ }
+ return slice_subresources[std::distance(slice_offsets.begin(), it)];
+ }
+}
+
+ImageViewId ImageBase::FindView(const ImageViewInfo& view_info) const noexcept {
+ const auto it = std::ranges::find(image_view_infos, view_info);
+ if (it == image_view_infos.end()) {
+ return ImageViewId{};
+ }
+ return image_view_ids[std::distance(image_view_infos.begin(), it)];
+}
+
+void ImageBase::InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id) {
+ image_view_infos.push_back(view_info);
+ image_view_ids.push_back(image_view_id);
+}
+
+void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_id) {
+ static constexpr auto OPTIONS = RelaxedOptions::Size | RelaxedOptions::Format;
+ ASSERT(lhs.info.type == rhs.info.type);
+ std::optional<SubresourceBase> base;
+ if (lhs.info.type == ImageType::Linear) {
+ base = SubresourceBase{.level = 0, .layer = 0};
+ } else {
+ // We are passing relaxed formats as an option, having broken views or not won't matter
+ static constexpr bool broken_views = false;
+ base = FindSubresource(rhs.info, lhs, rhs.gpu_addr, OPTIONS, broken_views);
+ }
+ if (!base) {
+ LOG_ERROR(HW_GPU, "Image alias should have been flipped");
+ return;
+ }
+ const PixelFormat lhs_format = lhs.info.format;
+ const PixelFormat rhs_format = rhs.info.format;
+ const Extent2D lhs_block{
+ .width = DefaultBlockWidth(lhs_format),
+ .height = DefaultBlockHeight(lhs_format),
+ };
+ const Extent2D rhs_block{
+ .width = DefaultBlockWidth(rhs_format),
+ .height = DefaultBlockHeight(rhs_format),
+ };
+ const bool is_lhs_compressed = lhs_block.width > 1 || lhs_block.height > 1;
+ const bool is_rhs_compressed = rhs_block.width > 1 || rhs_block.height > 1;
+ if (is_lhs_compressed && is_rhs_compressed) {
+ LOG_ERROR(HW_GPU, "Compressed to compressed image aliasing is not implemented");
+ return;
+ }
+ const s32 lhs_mips = lhs.info.resources.levels;
+ const s32 rhs_mips = rhs.info.resources.levels;
+ const s32 num_mips = std::min(lhs_mips - base->level, rhs_mips);
+ AliasedImage lhs_alias;
+ AliasedImage rhs_alias;
+ lhs_alias.id = rhs_id;
+ rhs_alias.id = lhs_id;
+ lhs_alias.copies.reserve(num_mips);
+ rhs_alias.copies.reserve(num_mips);
+ for (s32 mip_level = 0; mip_level < num_mips; ++mip_level) {
+ Extent3D lhs_size = MipSize(lhs.info.size, base->level + mip_level);
+ Extent3D rhs_size = MipSize(rhs.info.size, mip_level);
+ if (is_lhs_compressed) {
+ lhs_size.width /= lhs_block.width;
+ lhs_size.height /= lhs_block.height;
+ }
+ if (is_rhs_compressed) {
+ rhs_size.width /= rhs_block.width;
+ rhs_size.height /= rhs_block.height;
+ }
+ const Extent3D copy_size{
+ .width = std::min(lhs_size.width, rhs_size.width),
+ .height = std::min(lhs_size.height, rhs_size.height),
+ .depth = std::min(lhs_size.depth, rhs_size.depth),
+ };
+ if (copy_size.width == 0 || copy_size.height == 0) {
+ LOG_WARNING(HW_GPU, "Copy size is smaller than block size. Mip cannot be aliased.");
+ continue;
+ }
+ const bool is_lhs_3d = lhs.info.type == ImageType::e3D;
+ const bool is_rhs_3d = rhs.info.type == ImageType::e3D;
+ const Offset3D lhs_offset{0, 0, 0};
+ const Offset3D rhs_offset{0, 0, is_rhs_3d ? base->layer : 0};
+ const s32 lhs_layers = is_lhs_3d ? 1 : lhs.info.resources.layers - base->layer;
+ const s32 rhs_layers = is_rhs_3d ? 1 : rhs.info.resources.layers;
+ const s32 num_layers = std::min(lhs_layers, rhs_layers);
+ const SubresourceLayers lhs_subresource{
+ .base_level = mip_level,
+ .base_layer = 0,
+ .num_layers = num_layers,
+ };
+ const SubresourceLayers rhs_subresource{
+ .base_level = base->level + mip_level,
+ .base_layer = is_rhs_3d ? 0 : base->layer,
+ .num_layers = num_layers,
+ };
+ [[maybe_unused]] const ImageCopy& to_lhs_copy = lhs_alias.copies.emplace_back(ImageCopy{
+ .src_subresource = lhs_subresource,
+ .dst_subresource = rhs_subresource,
+ .src_offset = lhs_offset,
+ .dst_offset = rhs_offset,
+ .extent = copy_size,
+ });
+ [[maybe_unused]] const ImageCopy& to_rhs_copy = rhs_alias.copies.emplace_back(ImageCopy{
+ .src_subresource = rhs_subresource,
+ .dst_subresource = lhs_subresource,
+ .src_offset = rhs_offset,
+ .dst_offset = lhs_offset,
+ .extent = copy_size,
+ });
+ ASSERT_MSG(ValidateCopy(to_lhs_copy, lhs.info, rhs.info), "Invalid RHS to LHS copy");
+ ASSERT_MSG(ValidateCopy(to_rhs_copy, rhs.info, lhs.info), "Invalid LHS to RHS copy");
+ }
+ ASSERT(lhs_alias.copies.empty() == rhs_alias.copies.empty());
+ if (lhs_alias.copies.empty()) {
+ return;
+ }
+ lhs.aliased_images.push_back(std::move(lhs_alias));
+ rhs.aliased_images.push_back(std::move(rhs_alias));
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
new file mode 100644
index 000000000..b7f3b7e43
--- /dev/null
+++ b/src/video_core/texture_cache/image_base.h
@@ -0,0 +1,83 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <optional>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/texture_cache/image_info.h"
+#include "video_core/texture_cache/image_view_info.h"
+#include "video_core/texture_cache/types.h"
+
+namespace VideoCommon {
+
+enum class ImageFlagBits : u32 {
+ AcceleratedUpload = 1 << 0, ///< Upload can be accelerated in the GPU
+ Converted = 1 << 1, ///< Guest format is not supported natively and it has to be converted
+ CpuModified = 1 << 2, ///< Contents have been modified from the CPU
+ GpuModified = 1 << 3, ///< Contents have been modified from the GPU
+ Tracked = 1 << 4, ///< Writes and reads are being hooked from the CPU JIT
+ Strong = 1 << 5, ///< Exists in the image table, the dimensions are can be trusted
+ Registered = 1 << 6, ///< True when the image is registered
+ Picked = 1 << 7, ///< Temporary flag to mark the image as picked
+};
+DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
+
+struct ImageViewInfo;
+
+struct AliasedImage {
+ std::vector<ImageCopy> copies;
+ ImageId id;
+};
+
+struct ImageBase {
+ explicit ImageBase(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr);
+
+ [[nodiscard]] std::optional<SubresourceBase> TryFindBase(GPUVAddr other_addr) const noexcept;
+
+ [[nodiscard]] ImageViewId FindView(const ImageViewInfo& view_info) const noexcept;
+
+ void InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id);
+
+ [[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept {
+ const VAddr overlap_end = overlap_cpu_addr + overlap_size;
+ return cpu_addr < overlap_end && overlap_cpu_addr < cpu_addr_end;
+ }
+
+ ImageInfo info;
+
+ u32 guest_size_bytes = 0;
+ u32 unswizzled_size_bytes = 0;
+ u32 converted_size_bytes = 0;
+ ImageFlagBits flags = ImageFlagBits::CpuModified;
+
+ GPUVAddr gpu_addr = 0;
+ VAddr cpu_addr = 0;
+ VAddr cpu_addr_end = 0;
+
+ u64 modification_tick = 0;
+ u64 frame_tick = 0;
+
+ std::array<u32, MAX_MIP_LEVELS> mip_level_offsets{};
+
+ std::vector<ImageViewInfo> image_view_infos;
+ std::vector<ImageViewId> image_view_ids;
+
+ std::vector<u32> slice_offsets;
+ std::vector<SubresourceBase> slice_subresources;
+
+ std::vector<AliasedImage> aliased_images;
+};
+
+struct ImageAllocBase {
+ std::vector<ImageId> images;
+};
+
+void AddImageAlias(ImageBase& lhs, ImageBase& rhs, ImageId lhs_id, ImageId rhs_id);
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp
new file mode 100644
index 000000000..64fd7010a
--- /dev/null
+++ b/src/video_core/texture_cache/image_info.cpp
@@ -0,0 +1,189 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/format_lookup_table.h"
+#include "video_core/texture_cache/image_info.h"
+#include "video_core/texture_cache/samples_helper.h"
+#include "video_core/texture_cache/types.h"
+#include "video_core/texture_cache/util.h"
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+using Tegra::Texture::TextureType;
+using Tegra::Texture::TICEntry;
+using VideoCore::Surface::PixelFormat;
+
+ImageInfo::ImageInfo(const TICEntry& config) noexcept {
+ format = PixelFormatFromTextureInfo(config.format, config.r_type, config.g_type, config.b_type,
+ config.a_type, config.srgb_conversion);
+ num_samples = NumSamples(config.msaa_mode);
+ resources.levels = config.max_mip_level + 1;
+ if (config.IsPitchLinear()) {
+ pitch = config.Pitch();
+ } else if (config.IsBlockLinear()) {
+ block = Extent3D{
+ .width = config.block_width,
+ .height = config.block_height,
+ .depth = config.block_depth,
+ };
+ }
+ tile_width_spacing = config.tile_width_spacing;
+ if (config.texture_type != TextureType::Texture2D &&
+ config.texture_type != TextureType::Texture2DNoMipmap) {
+ ASSERT(!config.IsPitchLinear());
+ }
+ switch (config.texture_type) {
+ case TextureType::Texture1D:
+ ASSERT(config.BaseLayer() == 0);
+ type = ImageType::e1D;
+ size.width = config.Width();
+ break;
+ case TextureType::Texture1DArray:
+ UNIMPLEMENTED_IF(config.BaseLayer() != 0);
+ type = ImageType::e1D;
+ size.width = config.Width();
+ resources.layers = config.Depth();
+ break;
+ case TextureType::Texture2D:
+ case TextureType::Texture2DNoMipmap:
+ ASSERT(config.Depth() == 1);
+ type = config.IsPitchLinear() ? ImageType::Linear : ImageType::e2D;
+ size.width = config.Width();
+ size.height = config.Height();
+ resources.layers = config.BaseLayer() + 1;
+ break;
+ case TextureType::Texture2DArray:
+ type = ImageType::e2D;
+ size.width = config.Width();
+ size.height = config.Height();
+ resources.layers = config.BaseLayer() + config.Depth();
+ break;
+ case TextureType::TextureCubemap:
+ ASSERT(config.Depth() == 1);
+ type = ImageType::e2D;
+ size.width = config.Width();
+ size.height = config.Height();
+ resources.layers = config.BaseLayer() + 6;
+ break;
+ case TextureType::TextureCubeArray:
+ UNIMPLEMENTED_IF(config.load_store_hint != 0);
+ type = ImageType::e2D;
+ size.width = config.Width();
+ size.height = config.Height();
+ resources.layers = config.BaseLayer() + config.Depth() * 6;
+ break;
+ case TextureType::Texture3D:
+ ASSERT(config.BaseLayer() == 0);
+ type = ImageType::e3D;
+ size.width = config.Width();
+ size.height = config.Height();
+ size.depth = config.Depth();
+ break;
+ case TextureType::Texture1DBuffer:
+ type = ImageType::Buffer;
+ size.width = config.Width();
+ break;
+ default:
+ UNREACHABLE_MSG("Invalid texture_type={}", static_cast<int>(config.texture_type.Value()));
+ break;
+ }
+ if (type != ImageType::Linear) {
+ // FIXME: Call this without passing *this
+ layer_stride = CalculateLayerStride(*this);
+ maybe_unaligned_layer_stride = CalculateLayerSize(*this);
+ }
+}
+
+ImageInfo::ImageInfo(const Tegra::Engines::Maxwell3D::Regs& regs, size_t index) noexcept {
+ const auto& rt = regs.rt[index];
+ format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(rt.format);
+ if (rt.tile_mode.is_pitch_linear) {
+ ASSERT(rt.tile_mode.is_3d == 0);
+ type = ImageType::Linear;
+ pitch = rt.width;
+ size = Extent3D{
+ .width = pitch / BytesPerBlock(format),
+ .height = rt.height,
+ .depth = 1,
+ };
+ return;
+ }
+ size.width = rt.width;
+ size.height = rt.height;
+ layer_stride = rt.layer_stride * 4;
+ maybe_unaligned_layer_stride = layer_stride;
+ num_samples = NumSamples(regs.multisample_mode);
+ block = Extent3D{
+ .width = rt.tile_mode.block_width,
+ .height = rt.tile_mode.block_height,
+ .depth = rt.tile_mode.block_depth,
+ };
+ if (rt.tile_mode.is_3d) {
+ type = ImageType::e3D;
+ size.depth = rt.depth;
+ } else {
+ type = ImageType::e2D;
+ resources.layers = rt.depth;
+ }
+}
+
+ImageInfo::ImageInfo(const Tegra::Engines::Maxwell3D::Regs& regs) noexcept {
+ format = VideoCore::Surface::PixelFormatFromDepthFormat(regs.zeta.format);
+ size.width = regs.zeta_width;
+ size.height = regs.zeta_height;
+ resources.levels = 1;
+ layer_stride = regs.zeta.layer_stride * 4;
+ maybe_unaligned_layer_stride = layer_stride;
+ num_samples = NumSamples(regs.multisample_mode);
+ block = Extent3D{
+ .width = regs.zeta.tile_mode.block_width,
+ .height = regs.zeta.tile_mode.block_height,
+ .depth = regs.zeta.tile_mode.block_depth,
+ };
+ if (regs.zeta.tile_mode.is_pitch_linear) {
+ ASSERT(regs.zeta.tile_mode.is_3d == 0);
+ type = ImageType::Linear;
+ pitch = size.width * BytesPerBlock(format);
+ } else if (regs.zeta.tile_mode.is_3d) {
+ ASSERT(regs.zeta.tile_mode.is_pitch_linear == 0);
+ type = ImageType::e3D;
+ size.depth = regs.zeta_depth;
+ } else {
+ type = ImageType::e2D;
+ resources.layers = regs.zeta_depth;
+ }
+}
+
+ImageInfo::ImageInfo(const Tegra::Engines::Fermi2D::Surface& config) noexcept {
+ UNIMPLEMENTED_IF_MSG(config.layer != 0, "Surface layer is not zero");
+ format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(config.format);
+ if (config.linear == Tegra::Engines::Fermi2D::MemoryLayout::Pitch) {
+ type = ImageType::Linear;
+ size = Extent3D{
+ .width = config.pitch / VideoCore::Surface::BytesPerBlock(format),
+ .height = config.height,
+ .depth = 1,
+ };
+ pitch = config.pitch;
+ } else {
+ type = config.block_depth > 0 ? ImageType::e3D : ImageType::e2D;
+ block = Extent3D{
+ .width = config.block_width,
+ .height = config.block_height,
+ .depth = config.block_depth,
+ };
+ // 3D blits with more than once slice are not implemented for now
+ // Render to individual slices
+ size = Extent3D{
+ .width = config.width,
+ .height = config.height,
+ .depth = 1,
+ };
+ }
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h
new file mode 100644
index 000000000..5049fc36e
--- /dev/null
+++ b/src/video_core/texture_cache/image_info.h
@@ -0,0 +1,38 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/types.h"
+
+namespace VideoCommon {
+
+using Tegra::Texture::TICEntry;
+using VideoCore::Surface::PixelFormat;
+
+struct ImageInfo {
+ explicit ImageInfo() = default;
+ explicit ImageInfo(const TICEntry& config) noexcept;
+ explicit ImageInfo(const Tegra::Engines::Maxwell3D::Regs& regs, size_t index) noexcept;
+ explicit ImageInfo(const Tegra::Engines::Maxwell3D::Regs& regs) noexcept;
+ explicit ImageInfo(const Tegra::Engines::Fermi2D::Surface& config) noexcept;
+
+ PixelFormat format = PixelFormat::Invalid;
+ ImageType type = ImageType::e1D;
+ SubresourceExtent resources;
+ Extent3D size{1, 1, 1};
+ union {
+ Extent3D block{0, 0, 0};
+ u32 pitch;
+ };
+ u32 layer_stride = 0;
+ u32 maybe_unaligned_layer_stride = 0;
+ u32 num_samples = 1;
+ u32 tile_width_spacing = 0;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
new file mode 100644
index 000000000..18f72e508
--- /dev/null
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -0,0 +1,41 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+
+#include "common/assert.h"
+#include "core/settings.h"
+#include "video_core/compatible_formats.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/formatter.h"
+#include "video_core/texture_cache/image_info.h"
+#include "video_core/texture_cache/image_view_base.h"
+#include "video_core/texture_cache/image_view_info.h"
+#include "video_core/texture_cache/types.h"
+
+namespace VideoCommon {
+
+ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info,
+ ImageId image_id_)
+ : image_id{image_id_}, format{info.format}, type{info.type}, range{info.range},
+ size{
+ .width = std::max(image_info.size.width >> range.base.level, 1u),
+ .height = std::max(image_info.size.height >> range.base.level, 1u),
+ .depth = std::max(image_info.size.depth >> range.base.level, 1u),
+ } {
+ ASSERT_MSG(VideoCore::Surface::IsViewCompatible(image_info.format, info.format, false),
+ "Image view format {} is incompatible with image format {}", info.format,
+ image_info.format);
+ const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue();
+ if (image_info.type == ImageType::Linear && is_async) {
+ flags |= ImageViewFlagBits::PreemtiveDownload;
+ }
+ if (image_info.type == ImageType::e3D && info.type != ImageViewType::e3D) {
+ flags |= ImageViewFlagBits::Slice;
+ }
+}
+
+ImageViewBase::ImageViewBase(const NullImageParams&) {}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h
new file mode 100644
index 000000000..73954167e
--- /dev/null
+++ b/src/video_core/texture_cache/image_view_base.h
@@ -0,0 +1,47 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/types.h"
+
+namespace VideoCommon {
+
+using VideoCore::Surface::PixelFormat;
+
+struct ImageViewInfo;
+struct ImageInfo;
+
+struct NullImageParams {};
+
+enum class ImageViewFlagBits : u16 {
+ PreemtiveDownload = 1 << 0,
+ Strong = 1 << 1,
+ Slice = 1 << 2,
+};
+DECLARE_ENUM_FLAG_OPERATORS(ImageViewFlagBits)
+
+struct ImageViewBase {
+ explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info,
+ ImageId image_id);
+ explicit ImageViewBase(const NullImageParams&);
+
+ [[nodiscard]] bool IsBuffer() const noexcept {
+ return type == ImageViewType::Buffer;
+ }
+
+ ImageId image_id{};
+ PixelFormat format{};
+ ImageViewType type{};
+ SubresourceRange range;
+ Extent3D size{0, 0, 0};
+ ImageViewFlagBits flags{};
+
+ u64 invalidation_tick = 0;
+ u64 modification_tick = 0;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_view_info.cpp b/src/video_core/texture_cache/image_view_info.cpp
new file mode 100644
index 000000000..faf5b151f
--- /dev/null
+++ b/src/video_core/texture_cache/image_view_info.cpp
@@ -0,0 +1,88 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <limits>
+
+#include "common/assert.h"
+#include "video_core/texture_cache/image_view_info.h"
+#include "video_core/texture_cache/texture_cache.h"
+#include "video_core/texture_cache/types.h"
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+namespace {
+
+constexpr u8 RENDER_TARGET_SWIZZLE = std::numeric_limits<u8>::max();
+
+[[nodiscard]] u8 CastSwizzle(SwizzleSource source) {
+ const u8 casted = static_cast<u8>(source);
+ ASSERT(static_cast<SwizzleSource>(casted) == source);
+ return casted;
+}
+
+} // Anonymous namespace
+
+ImageViewInfo::ImageViewInfo(const TICEntry& config, s32 base_layer) noexcept
+ : format{PixelFormatFromTIC(config)}, x_source{CastSwizzle(config.x_source)},
+ y_source{CastSwizzle(config.y_source)}, z_source{CastSwizzle(config.z_source)},
+ w_source{CastSwizzle(config.w_source)} {
+ range.base = SubresourceBase{
+ .level = static_cast<s32>(config.res_min_mip_level),
+ .layer = base_layer,
+ };
+ range.extent.levels = config.res_max_mip_level - config.res_min_mip_level + 1;
+
+ switch (config.texture_type) {
+ case TextureType::Texture1D:
+ ASSERT(config.Height() == 1);
+ ASSERT(config.Depth() == 1);
+ type = ImageViewType::e1D;
+ break;
+ case TextureType::Texture2D:
+ case TextureType::Texture2DNoMipmap:
+ ASSERT(config.Depth() == 1);
+ type = config.normalized_coords ? ImageViewType::e2D : ImageViewType::Rect;
+ break;
+ case TextureType::Texture3D:
+ type = ImageViewType::e3D;
+ break;
+ case TextureType::TextureCubemap:
+ ASSERT(config.Depth() == 1);
+ type = ImageViewType::Cube;
+ range.extent.layers = 6;
+ break;
+ case TextureType::Texture1DArray:
+ type = ImageViewType::e1DArray;
+ range.extent.layers = config.Depth();
+ break;
+ case TextureType::Texture2DArray:
+ type = ImageViewType::e2DArray;
+ range.extent.layers = config.Depth();
+ break;
+ case TextureType::Texture1DBuffer:
+ type = ImageViewType::Buffer;
+ break;
+ case TextureType::TextureCubeArray:
+ type = ImageViewType::CubeArray;
+ range.extent.layers = config.Depth() * 6;
+ break;
+ default:
+ UNREACHABLE_MSG("Invalid texture_type={}", static_cast<int>(config.texture_type.Value()));
+ break;
+ }
+}
+
+ImageViewInfo::ImageViewInfo(ImageViewType type_, PixelFormat format_,
+ SubresourceRange range_) noexcept
+ : type{type_}, format{format_}, range{range_}, x_source{RENDER_TARGET_SWIZZLE},
+ y_source{RENDER_TARGET_SWIZZLE}, z_source{RENDER_TARGET_SWIZZLE},
+ w_source{RENDER_TARGET_SWIZZLE} {}
+
+bool ImageViewInfo::IsRenderTarget() const noexcept {
+ return x_source == RENDER_TARGET_SWIZZLE && y_source == RENDER_TARGET_SWIZZLE &&
+ z_source == RENDER_TARGET_SWIZZLE && w_source == RENDER_TARGET_SWIZZLE;
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_view_info.h b/src/video_core/texture_cache/image_view_info.h
new file mode 100644
index 000000000..0c1f99117
--- /dev/null
+++ b/src/video_core/texture_cache/image_view_info.h
@@ -0,0 +1,50 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <type_traits>
+
+#include "video_core/surface.h"
+#include "video_core/texture_cache/types.h"
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+using Tegra::Texture::SwizzleSource;
+using Tegra::Texture::TICEntry;
+using VideoCore::Surface::PixelFormat;
+
+/// Properties used to determine a image view
+struct ImageViewInfo {
+ explicit ImageViewInfo() noexcept = default;
+ explicit ImageViewInfo(const TICEntry& config, s32 base_layer) noexcept;
+ explicit ImageViewInfo(ImageViewType type, PixelFormat format,
+ SubresourceRange range = {}) noexcept;
+
+ auto operator<=>(const ImageViewInfo&) const noexcept = default;
+
+ [[nodiscard]] bool IsRenderTarget() const noexcept;
+
+ [[nodiscard]] std::array<SwizzleSource, 4> Swizzle() const noexcept {
+ return std::array{
+ static_cast<SwizzleSource>(x_source),
+ static_cast<SwizzleSource>(y_source),
+ static_cast<SwizzleSource>(z_source),
+ static_cast<SwizzleSource>(w_source),
+ };
+ }
+
+ ImageViewType type{};
+ PixelFormat format{};
+ SubresourceRange range;
+ u8 x_source = static_cast<u8>(SwizzleSource::R);
+ u8 y_source = static_cast<u8>(SwizzleSource::G);
+ u8 z_source = static_cast<u8>(SwizzleSource::B);
+ u8 w_source = static_cast<u8>(SwizzleSource::A);
+};
+static_assert(std::has_unique_object_representations_v<ImageViewInfo>);
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/render_targets.h b/src/video_core/texture_cache/render_targets.h
new file mode 100644
index 000000000..9b9544b07
--- /dev/null
+++ b/src/video_core/texture_cache/render_targets.h
@@ -0,0 +1,51 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <span>
+#include <utility>
+
+#include "common/bit_cast.h"
+#include "video_core/texture_cache/types.h"
+
+namespace VideoCommon {
+
+/// Framebuffer properties used to lookup a framebuffer
+struct RenderTargets {
+ constexpr auto operator<=>(const RenderTargets&) const noexcept = default;
+
+ constexpr bool Contains(std::span<const ImageViewId> elements) const noexcept {
+ const auto contains = [elements](ImageViewId item) {
+ return std::ranges::find(elements, item) != elements.end();
+ };
+ return std::ranges::any_of(color_buffer_ids, contains) || contains(depth_buffer_id);
+ }
+
+ std::array<ImageViewId, NUM_RT> color_buffer_ids;
+ ImageViewId depth_buffer_id;
+ std::array<u8, NUM_RT> draw_buffers{};
+ Extent2D size;
+};
+
+} // namespace VideoCommon
+
+namespace std {
+
+template <>
+struct hash<VideoCommon::RenderTargets> {
+ size_t operator()(const VideoCommon::RenderTargets& rt) const noexcept {
+ using VideoCommon::ImageViewId;
+ size_t value = std::hash<ImageViewId>{}(rt.depth_buffer_id);
+ for (const ImageViewId color_buffer_id : rt.color_buffer_ids) {
+ value ^= std::hash<ImageViewId>{}(color_buffer_id);
+ }
+ value ^= Common::BitCast<u64>(rt.draw_buffers);
+ value ^= Common::BitCast<u64>(rt.size);
+ return value;
+ }
+};
+
+} // namespace std
diff --git a/src/video_core/texture_cache/samples_helper.h b/src/video_core/texture_cache/samples_helper.h
new file mode 100644
index 000000000..04539a43c
--- /dev/null
+++ b/src/video_core/texture_cache/samples_helper.h
@@ -0,0 +1,55 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <utility>
+
+#include "common/assert.h"
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+[[nodiscard]] inline std::pair<int, int> SamplesLog2(int num_samples) {
+ switch (num_samples) {
+ case 1:
+ return {0, 0};
+ case 2:
+ return {1, 0};
+ case 4:
+ return {1, 1};
+ case 8:
+ return {2, 1};
+ case 16:
+ return {2, 2};
+ }
+ UNREACHABLE_MSG("Invalid number of samples={}", num_samples);
+ return {1, 1};
+}
+
+[[nodiscard]] inline int NumSamples(Tegra::Texture::MsaaMode msaa_mode) {
+ using Tegra::Texture::MsaaMode;
+ switch (msaa_mode) {
+ case MsaaMode::Msaa1x1:
+ return 1;
+ case MsaaMode::Msaa2x1:
+ case MsaaMode::Msaa2x1_D3D:
+ return 2;
+ case MsaaMode::Msaa2x2:
+ case MsaaMode::Msaa2x2_VC4:
+ case MsaaMode::Msaa2x2_VC12:
+ return 4;
+ case MsaaMode::Msaa4x2:
+ case MsaaMode::Msaa4x2_D3D:
+ case MsaaMode::Msaa4x2_VC8:
+ case MsaaMode::Msaa4x2_VC24:
+ return 8;
+ case MsaaMode::Msaa4x4:
+ return 16;
+ }
+ UNREACHABLE_MSG("Invalid MSAA mode={}", static_cast<int>(msaa_mode));
+ return 1;
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/slot_vector.h b/src/video_core/texture_cache/slot_vector.h
new file mode 100644
index 000000000..eae3be6ea
--- /dev/null
+++ b/src/video_core/texture_cache/slot_vector.h
@@ -0,0 +1,156 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <concepts>
+#include <numeric>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "common/assert.h"
+#include "common/common_types.h"
+
+namespace VideoCommon {
+
+struct SlotId {
+ static constexpr u32 INVALID_INDEX = std::numeric_limits<u32>::max();
+
+ constexpr auto operator<=>(const SlotId&) const noexcept = default;
+
+ constexpr explicit operator bool() const noexcept {
+ return index != INVALID_INDEX;
+ }
+
+ u32 index = INVALID_INDEX;
+};
+
+template <class T>
+requires std::is_nothrow_move_assignable_v<T>&&
+ std::is_nothrow_move_constructible_v<T> class SlotVector {
+public:
+ ~SlotVector() noexcept {
+ size_t index = 0;
+ for (u64 bits : stored_bitset) {
+ for (size_t bit = 0; bits; ++bit, bits >>= 1) {
+ if ((bits & 1) != 0) {
+ values[index + bit].object.~T();
+ }
+ }
+ index += 64;
+ }
+ delete[] values;
+ }
+
+ [[nodiscard]] T& operator[](SlotId id) noexcept {
+ ValidateIndex(id);
+ return values[id.index].object;
+ }
+
+ [[nodiscard]] const T& operator[](SlotId id) const noexcept {
+ ValidateIndex(id);
+ return values[id.index].object;
+ }
+
+ template <typename... Args>
+ [[nodiscard]] SlotId insert(Args&&... args) noexcept {
+ const u32 index = FreeValueIndex();
+ new (&values[index].object) T(std::forward<Args>(args)...);
+ SetStorageBit(index);
+
+ return SlotId{index};
+ }
+
+ void erase(SlotId id) noexcept {
+ values[id.index].object.~T();
+ free_list.push_back(id.index);
+ ResetStorageBit(id.index);
+ }
+
+private:
+ struct NonTrivialDummy {
+ NonTrivialDummy() noexcept {}
+ };
+
+ union Entry {
+ Entry() noexcept : dummy{} {}
+ ~Entry() noexcept {}
+
+ NonTrivialDummy dummy;
+ T object;
+ };
+
+ void SetStorageBit(u32 index) noexcept {
+ stored_bitset[index / 64] |= u64(1) << (index % 64);
+ }
+
+ void ResetStorageBit(u32 index) noexcept {
+ stored_bitset[index / 64] &= ~(u64(1) << (index % 64));
+ }
+
+ bool ReadStorageBit(u32 index) noexcept {
+ return ((stored_bitset[index / 64] >> (index % 64)) & 1) != 0;
+ }
+
+ void ValidateIndex(SlotId id) const noexcept {
+ DEBUG_ASSERT(id);
+ DEBUG_ASSERT(id.index / 64 < stored_bitset.size());
+ DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0);
+ }
+
+ [[nodiscard]] u32 FreeValueIndex() noexcept {
+ if (free_list.empty()) {
+ Reserve(values_capacity ? (values_capacity << 1) : 1);
+ }
+ const u32 free_index = free_list.back();
+ free_list.pop_back();
+ return free_index;
+ }
+
+ void Reserve(size_t new_capacity) noexcept {
+ Entry* const new_values = new Entry[new_capacity];
+ size_t index = 0;
+ for (u64 bits : stored_bitset) {
+ for (size_t bit = 0; bits; ++bit, bits >>= 1) {
+ const size_t i = index + bit;
+ if ((bits & 1) == 0) {
+ continue;
+ }
+ T& old_value = values[i].object;
+ new (&new_values[i].object) T(std::move(old_value));
+ old_value.~T();
+ }
+ index += 64;
+ }
+
+ stored_bitset.resize((new_capacity + 63) / 64);
+
+ const size_t old_free_size = free_list.size();
+ free_list.resize(old_free_size + (new_capacity - values_capacity));
+ std::iota(free_list.begin() + old_free_size, free_list.end(),
+ static_cast<u32>(values_capacity));
+
+ delete[] values;
+ values = new_values;
+ values_capacity = new_capacity;
+ }
+
+ Entry* values = nullptr;
+ size_t values_capacity = 0;
+ size_t values_size = 0;
+
+ std::vector<u64> stored_bitset;
+ std::vector<u32> free_list;
+};
+
+} // namespace VideoCommon
+
+template <>
+struct std::hash<VideoCommon::SlotId> {
+ size_t operator()(const VideoCommon::SlotId& id) const noexcept {
+ return std::hash<u32>{}(id.index);
+ }
+};
diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp
deleted file mode 100644
index dfcf36e0b..000000000
--- a/src/video_core/texture_cache/surface_base.cpp
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/algorithm.h"
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "common/microprofile.h"
-#include "video_core/memory_manager.h"
-#include "video_core/texture_cache/surface_base.h"
-#include "video_core/texture_cache/surface_params.h"
-#include "video_core/textures/convert.h"
-
-namespace VideoCommon {
-
-MICROPROFILE_DEFINE(GPU_Load_Texture, "GPU", "Texture Load", MP_RGB(128, 192, 128));
-MICROPROFILE_DEFINE(GPU_Flush_Texture, "GPU", "Texture Flush", MP_RGB(128, 192, 128));
-
-using Tegra::Texture::ConvertFromGuestToHost;
-using VideoCore::MortonSwizzleMode;
-using VideoCore::Surface::IsPixelFormatASTC;
-using VideoCore::Surface::PixelFormat;
-
-StagingCache::StagingCache() = default;
-
-StagingCache::~StagingCache() = default;
-
-SurfaceBaseImpl::SurfaceBaseImpl(GPUVAddr gpu_addr, const SurfaceParams& params,
- bool is_astc_supported)
- : params{params}, gpu_addr{gpu_addr}, mipmap_sizes(params.num_levels),
- mipmap_offsets(params.num_levels) {
- is_converted = IsPixelFormatASTC(params.pixel_format) && !is_astc_supported;
- host_memory_size = params.GetHostSizeInBytes(is_converted);
-
- std::size_t offset = 0;
- for (u32 level = 0; level < params.num_levels; ++level) {
- const std::size_t mipmap_size{params.GetGuestMipmapSize(level)};
- mipmap_sizes[level] = mipmap_size;
- mipmap_offsets[level] = offset;
- offset += mipmap_size;
- }
- layer_size = offset;
- if (params.is_layered) {
- if (params.is_tiled) {
- layer_size =
- SurfaceParams::AlignLayered(layer_size, params.block_height, params.block_depth);
- }
- guest_memory_size = layer_size * params.depth;
- } else {
- guest_memory_size = layer_size;
- }
-}
-
-MatchTopologyResult SurfaceBaseImpl::MatchesTopology(const SurfaceParams& rhs) const {
- const u32 src_bpp{params.GetBytesPerPixel()};
- const u32 dst_bpp{rhs.GetBytesPerPixel()};
- const bool ib1 = params.IsBuffer();
- const bool ib2 = rhs.IsBuffer();
- if (std::tie(src_bpp, params.is_tiled, ib1) == std::tie(dst_bpp, rhs.is_tiled, ib2)) {
- const bool cb1 = params.IsCompressed();
- const bool cb2 = rhs.IsCompressed();
- if (cb1 == cb2) {
- return MatchTopologyResult::FullMatch;
- }
- return MatchTopologyResult::CompressUnmatch;
- }
- return MatchTopologyResult::None;
-}
-
-MatchStructureResult SurfaceBaseImpl::MatchesStructure(const SurfaceParams& rhs) const {
- // Buffer surface Check
- if (params.IsBuffer()) {
- const std::size_t wd1 = params.width * params.GetBytesPerPixel();
- const std::size_t wd2 = rhs.width * rhs.GetBytesPerPixel();
- if (wd1 == wd2) {
- return MatchStructureResult::FullMatch;
- }
- return MatchStructureResult::None;
- }
-
- // Linear Surface check
- if (!params.is_tiled) {
- if (std::tie(params.height, params.pitch) == std::tie(rhs.height, rhs.pitch)) {
- if (params.width == rhs.width) {
- return MatchStructureResult::FullMatch;
- } else {
- return MatchStructureResult::SemiMatch;
- }
- }
- return MatchStructureResult::None;
- }
-
- // Tiled Surface check
- if (std::tie(params.depth, params.block_width, params.block_height, params.block_depth,
- params.tile_width_spacing, params.num_levels) ==
- std::tie(rhs.depth, rhs.block_width, rhs.block_height, rhs.block_depth,
- rhs.tile_width_spacing, rhs.num_levels)) {
- if (std::tie(params.width, params.height) == std::tie(rhs.width, rhs.height)) {
- return MatchStructureResult::FullMatch;
- }
- const u32 ws = SurfaceParams::ConvertWidth(rhs.GetBlockAlignedWidth(), params.pixel_format,
- rhs.pixel_format);
- const u32 hs =
- SurfaceParams::ConvertHeight(rhs.height, params.pixel_format, rhs.pixel_format);
- const u32 w1 = params.GetBlockAlignedWidth();
- if (std::tie(w1, params.height) == std::tie(ws, hs)) {
- return MatchStructureResult::SemiMatch;
- }
- }
- return MatchStructureResult::None;
-}
-
-std::optional<std::pair<u32, u32>> SurfaceBaseImpl::GetLayerMipmap(
- const GPUVAddr candidate_gpu_addr) const {
- if (gpu_addr == candidate_gpu_addr) {
- return {{0, 0}};
- }
- if (candidate_gpu_addr < gpu_addr) {
- return {};
- }
- const auto relative_address{static_cast<GPUVAddr>(candidate_gpu_addr - gpu_addr)};
- const auto layer{static_cast<u32>(relative_address / layer_size)};
- if (layer >= params.depth) {
- return {};
- }
- const GPUVAddr mipmap_address = relative_address - layer_size * layer;
- const auto mipmap_it =
- Common::BinaryFind(mipmap_offsets.begin(), mipmap_offsets.end(), mipmap_address);
- if (mipmap_it == mipmap_offsets.end()) {
- return {};
- }
- const auto level{static_cast<u32>(std::distance(mipmap_offsets.begin(), mipmap_it))};
- return std::make_pair(layer, level);
-}
-
-std::vector<CopyParams> SurfaceBaseImpl::BreakDownLayered(const SurfaceParams& in_params) const {
- const u32 layers{params.depth};
- const u32 mipmaps{params.num_levels};
- std::vector<CopyParams> result;
- result.reserve(static_cast<std::size_t>(layers) * static_cast<std::size_t>(mipmaps));
-
- for (u32 layer = 0; layer < layers; layer++) {
- for (u32 level = 0; level < mipmaps; level++) {
- const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level);
- const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level);
- result.emplace_back(0, 0, layer, 0, 0, layer, level, level, width, height, 1);
- }
- }
- return result;
-}
-
-std::vector<CopyParams> SurfaceBaseImpl::BreakDownNonLayered(const SurfaceParams& in_params) const {
- const u32 mipmaps{params.num_levels};
- std::vector<CopyParams> result;
- result.reserve(mipmaps);
-
- for (u32 level = 0; level < mipmaps; level++) {
- const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level);
- const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level);
- const u32 depth{std::min(params.GetMipDepth(level), in_params.GetMipDepth(level))};
- result.emplace_back(width, height, depth, level);
- }
- return result;
-}
-
-void SurfaceBaseImpl::SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params,
- u8* buffer, u32 level) {
- const u32 width{params.GetMipWidth(level)};
- const u32 height{params.GetMipHeight(level)};
- const u32 block_height{params.GetMipBlockHeight(level)};
- const u32 block_depth{params.GetMipBlockDepth(level)};
-
- std::size_t guest_offset{mipmap_offsets[level]};
- if (params.is_layered) {
- std::size_t host_offset = 0;
- const std::size_t guest_stride = layer_size;
- const std::size_t host_stride = params.GetHostLayerSize(level);
- for (u32 layer = 0; layer < params.depth; ++layer) {
- MortonSwizzle(mode, params.pixel_format, width, block_height, height, block_depth, 1,
- params.tile_width_spacing, buffer + host_offset, memory + guest_offset);
- guest_offset += guest_stride;
- host_offset += host_stride;
- }
- } else {
- MortonSwizzle(mode, params.pixel_format, width, block_height, height, block_depth,
- params.GetMipDepth(level), params.tile_width_spacing, buffer,
- memory + guest_offset);
- }
-}
-
-void SurfaceBaseImpl::LoadBuffer(Tegra::MemoryManager& memory_manager,
- StagingCache& staging_cache) {
- MICROPROFILE_SCOPE(GPU_Load_Texture);
- auto& staging_buffer = staging_cache.GetBuffer(0);
- u8* host_ptr;
- // Use an extra temporal buffer
- auto& tmp_buffer = staging_cache.GetBuffer(1);
- tmp_buffer.resize(guest_memory_size);
- host_ptr = tmp_buffer.data();
- memory_manager.ReadBlockUnsafe(gpu_addr, host_ptr, guest_memory_size);
-
- if (params.is_tiled) {
- ASSERT_MSG(params.block_width == 0, "Block width is defined as {} on texture target {}",
- params.block_width, static_cast<u32>(params.target));
- for (u32 level = 0; level < params.num_levels; ++level) {
- const std::size_t host_offset{params.GetHostMipmapLevelOffset(level, false)};
- SwizzleFunc(MortonSwizzleMode::MortonToLinear, host_ptr, params,
- staging_buffer.data() + host_offset, level);
- }
- } else {
- ASSERT_MSG(params.num_levels == 1, "Linear mipmap loading is not implemented");
- const u32 bpp{params.GetBytesPerPixel()};
- const u32 block_width{params.GetDefaultBlockWidth()};
- const u32 block_height{params.GetDefaultBlockHeight()};
- const u32 width{(params.width + block_width - 1) / block_width};
- const u32 height{(params.height + block_height - 1) / block_height};
- const u32 copy_size{width * bpp};
- if (params.pitch == copy_size) {
- std::memcpy(staging_buffer.data(), host_ptr, params.GetHostSizeInBytes(false));
- } else {
- const u8* start{host_ptr};
- u8* write_to{staging_buffer.data()};
- for (u32 h = height; h > 0; --h) {
- std::memcpy(write_to, start, copy_size);
- start += params.pitch;
- write_to += copy_size;
- }
- }
- }
-
- if (!is_converted && params.pixel_format != PixelFormat::S8_UINT_D24_UNORM) {
- return;
- }
-
- for (u32 level = params.num_levels; level--;) {
- const std::size_t in_host_offset{params.GetHostMipmapLevelOffset(level, false)};
- const std::size_t out_host_offset{params.GetHostMipmapLevelOffset(level, is_converted)};
- u8* const in_buffer = staging_buffer.data() + in_host_offset;
- u8* const out_buffer = staging_buffer.data() + out_host_offset;
- ConvertFromGuestToHost(in_buffer, out_buffer, params.pixel_format,
- params.GetMipWidth(level), params.GetMipHeight(level),
- params.GetMipDepth(level), true, true);
- }
-}
-
-void SurfaceBaseImpl::FlushBuffer(Tegra::MemoryManager& memory_manager,
- StagingCache& staging_cache) {
- MICROPROFILE_SCOPE(GPU_Flush_Texture);
- auto& staging_buffer = staging_cache.GetBuffer(0);
- u8* host_ptr;
-
- // Use an extra temporal buffer
- auto& tmp_buffer = staging_cache.GetBuffer(1);
- tmp_buffer.resize(guest_memory_size);
- host_ptr = tmp_buffer.data();
-
- if (params.target == SurfaceTarget::Texture3D) {
- // Special case for 3D texture segments
- memory_manager.ReadBlockUnsafe(gpu_addr, host_ptr, guest_memory_size);
- }
-
- if (params.is_tiled) {
- ASSERT_MSG(params.block_width == 0, "Block width is defined as {}", params.block_width);
- for (u32 level = 0; level < params.num_levels; ++level) {
- const std::size_t host_offset{params.GetHostMipmapLevelOffset(level, false)};
- SwizzleFunc(MortonSwizzleMode::LinearToMorton, host_ptr, params,
- staging_buffer.data() + host_offset, level);
- }
- } else if (params.IsBuffer()) {
- // Buffers don't have pitch or any fancy layout property. We can just memcpy them to guest
- // memory.
- std::memcpy(host_ptr, staging_buffer.data(), guest_memory_size);
- } else {
- ASSERT(params.target == SurfaceTarget::Texture2D);
- ASSERT(params.num_levels == 1);
-
- const u32 bpp{params.GetBytesPerPixel()};
- const u32 copy_size{params.width * bpp};
- if (params.pitch == copy_size) {
- std::memcpy(host_ptr, staging_buffer.data(), guest_memory_size);
- } else {
- u8* start{host_ptr};
- const u8* read_to{staging_buffer.data()};
- for (u32 h = params.height; h > 0; --h) {
- std::memcpy(start, read_to, copy_size);
- start += params.pitch;
- read_to += copy_size;
- }
- }
- }
- memory_manager.WriteBlockUnsafe(gpu_addr, host_ptr, guest_memory_size);
-}
-
-} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h
deleted file mode 100644
index 173f2edba..000000000
--- a/src/video_core/texture_cache/surface_base.h
+++ /dev/null
@@ -1,333 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <optional>
-#include <tuple>
-#include <unordered_map>
-#include <vector>
-
-#include "common/common_types.h"
-#include "video_core/gpu.h"
-#include "video_core/morton.h"
-#include "video_core/texture_cache/copy_params.h"
-#include "video_core/texture_cache/surface_params.h"
-#include "video_core/texture_cache/surface_view.h"
-
-namespace Tegra {
-class MemoryManager;
-}
-
-namespace VideoCommon {
-
-using VideoCore::MortonSwizzleMode;
-using VideoCore::Surface::SurfaceTarget;
-
-enum class MatchStructureResult : u32 {
- FullMatch = 0,
- SemiMatch = 1,
- None = 2,
-};
-
-enum class MatchTopologyResult : u32 {
- FullMatch = 0,
- CompressUnmatch = 1,
- None = 2,
-};
-
-class StagingCache {
-public:
- explicit StagingCache();
- ~StagingCache();
-
- std::vector<u8>& GetBuffer(std::size_t index) {
- return staging_buffer[index];
- }
-
- const std::vector<u8>& GetBuffer(std::size_t index) const {
- return staging_buffer[index];
- }
-
- void SetSize(std::size_t size) {
- staging_buffer.resize(size);
- }
-
-private:
- std::vector<std::vector<u8>> staging_buffer;
-};
-
-class SurfaceBaseImpl {
-public:
- void LoadBuffer(Tegra::MemoryManager& memory_manager, StagingCache& staging_cache);
-
- void FlushBuffer(Tegra::MemoryManager& memory_manager, StagingCache& staging_cache);
-
- GPUVAddr GetGpuAddr() const {
- return gpu_addr;
- }
-
- bool Overlaps(const VAddr start, const VAddr end) const {
- return (cpu_addr < end) && (cpu_addr_end > start);
- }
-
- bool IsInside(const GPUVAddr other_start, const GPUVAddr other_end) const {
- const GPUVAddr gpu_addr_end = gpu_addr + guest_memory_size;
- return gpu_addr <= other_start && other_end <= gpu_addr_end;
- }
-
- // Use only when recycling a surface
- void SetGpuAddr(const GPUVAddr new_addr) {
- gpu_addr = new_addr;
- }
-
- VAddr GetCpuAddr() const {
- return cpu_addr;
- }
-
- VAddr GetCpuAddrEnd() const {
- return cpu_addr_end;
- }
-
- void SetCpuAddr(const VAddr new_addr) {
- cpu_addr = new_addr;
- cpu_addr_end = new_addr + guest_memory_size;
- }
-
- const SurfaceParams& GetSurfaceParams() const {
- return params;
- }
-
- std::size_t GetSizeInBytes() const {
- return guest_memory_size;
- }
-
- std::size_t GetHostSizeInBytes() const {
- return host_memory_size;
- }
-
- std::size_t GetMipmapSize(const u32 level) const {
- return mipmap_sizes[level];
- }
-
- bool IsLinear() const {
- return !params.is_tiled;
- }
-
- bool IsConverted() const {
- return is_converted;
- }
-
- bool MatchFormat(VideoCore::Surface::PixelFormat pixel_format) const {
- return params.pixel_format == pixel_format;
- }
-
- VideoCore::Surface::PixelFormat GetFormat() const {
- return params.pixel_format;
- }
-
- bool MatchTarget(VideoCore::Surface::SurfaceTarget target) const {
- return params.target == target;
- }
-
- MatchTopologyResult MatchesTopology(const SurfaceParams& rhs) const;
-
- MatchStructureResult MatchesStructure(const SurfaceParams& rhs) const;
-
- bool MatchesSubTexture(const SurfaceParams& rhs, const GPUVAddr other_gpu_addr) const {
- return std::tie(gpu_addr, params.target, params.num_levels) ==
- std::tie(other_gpu_addr, rhs.target, rhs.num_levels) &&
- params.target == SurfaceTarget::Texture2D && params.num_levels == 1;
- }
-
- std::optional<std::pair<u32, u32>> GetLayerMipmap(const GPUVAddr candidate_gpu_addr) const;
-
- std::vector<CopyParams> BreakDown(const SurfaceParams& in_params) const {
- return params.is_layered ? BreakDownLayered(in_params) : BreakDownNonLayered(in_params);
- }
-
-protected:
- explicit SurfaceBaseImpl(GPUVAddr gpu_addr, const SurfaceParams& params,
- bool is_astc_supported);
- ~SurfaceBaseImpl() = default;
-
- virtual void DecorateSurfaceName() = 0;
-
- const SurfaceParams params;
- std::size_t layer_size;
- std::size_t guest_memory_size;
- std::size_t host_memory_size;
- GPUVAddr gpu_addr{};
- VAddr cpu_addr{};
- VAddr cpu_addr_end{};
- bool is_converted{};
-
- std::vector<std::size_t> mipmap_sizes;
- std::vector<std::size_t> mipmap_offsets;
-
-private:
- void SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, u8* buffer,
- u32 level);
-
- std::vector<CopyParams> BreakDownLayered(const SurfaceParams& in_params) const;
-
- std::vector<CopyParams> BreakDownNonLayered(const SurfaceParams& in_params) const;
-};
-
-template <typename TView>
-class SurfaceBase : public SurfaceBaseImpl {
-public:
- virtual void UploadTexture(const std::vector<u8>& staging_buffer) = 0;
-
- virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0;
-
- void MarkAsModified(bool is_modified_, u64 tick) {
- is_modified = is_modified_ || is_target;
- modification_tick = tick;
- }
-
- void MarkAsRenderTarget(bool is_target_, u32 index_) {
- is_target = is_target_;
- index = index_;
- }
-
- void SetMemoryMarked(bool is_memory_marked_) {
- is_memory_marked = is_memory_marked_;
- }
-
- bool IsMemoryMarked() const {
- return is_memory_marked;
- }
-
- void SetSyncPending(bool is_sync_pending_) {
- is_sync_pending = is_sync_pending_;
- }
-
- bool IsSyncPending() const {
- return is_sync_pending;
- }
-
- void MarkAsPicked(bool is_picked_) {
- is_picked = is_picked_;
- }
-
- bool IsModified() const {
- return is_modified;
- }
-
- bool IsProtected() const {
- // Only 3D slices are to be protected
- return is_target && params.target == SurfaceTarget::Texture3D;
- }
-
- bool IsRenderTarget() const {
- return is_target;
- }
-
- u32 GetRenderTarget() const {
- return index;
- }
-
- bool IsRegistered() const {
- return is_registered;
- }
-
- bool IsPicked() const {
- return is_picked;
- }
-
- void MarkAsRegistered(bool is_reg) {
- is_registered = is_reg;
- }
-
- u64 GetModificationTick() const {
- return modification_tick;
- }
-
- TView EmplaceOverview(const SurfaceParams& overview_params) {
- const u32 num_layers{(params.is_layered && !overview_params.is_layered) ? 1 : params.depth};
- return GetView(ViewParams(overview_params.target, 0, num_layers, 0, params.num_levels));
- }
-
- TView Emplace3DView(u32 slice, u32 depth, u32 base_level, u32 num_levels) {
- return GetView(ViewParams(VideoCore::Surface::SurfaceTarget::Texture3D, slice, depth,
- base_level, num_levels));
- }
-
- std::optional<TView> EmplaceIrregularView(const SurfaceParams& view_params,
- const GPUVAddr view_addr,
- const std::size_t candidate_size, const u32 mipmap,
- const u32 layer) {
- const auto layer_mipmap{GetLayerMipmap(view_addr + candidate_size)};
- if (!layer_mipmap) {
- return {};
- }
- const auto [end_layer, end_mipmap] = *layer_mipmap;
- if (layer != end_layer) {
- if (mipmap == 0 && end_mipmap == 0) {
- return GetView(ViewParams(view_params.target, layer, end_layer - layer, 0, 1));
- }
- return {};
- } else {
- return GetView(ViewParams(view_params.target, layer, 1, mipmap, end_mipmap - mipmap));
- }
- }
-
- std::optional<TView> EmplaceView(const SurfaceParams& view_params, const GPUVAddr view_addr,
- const std::size_t candidate_size) {
- if (params.target == SurfaceTarget::Texture3D ||
- view_params.target == SurfaceTarget::Texture3D ||
- (params.num_levels == 1 && !params.is_layered)) {
- return {};
- }
- const auto layer_mipmap{GetLayerMipmap(view_addr)};
- if (!layer_mipmap) {
- return {};
- }
- const auto [layer, mipmap] = *layer_mipmap;
- if (GetMipmapSize(mipmap) != candidate_size) {
- return EmplaceIrregularView(view_params, view_addr, candidate_size, mipmap, layer);
- }
- return GetView(ViewParams(view_params.target, layer, 1, mipmap, 1));
- }
-
- TView GetMainView() const {
- return main_view;
- }
-
-protected:
- explicit SurfaceBase(const GPUVAddr gpu_addr, const SurfaceParams& params,
- bool is_astc_supported)
- : SurfaceBaseImpl(gpu_addr, params, is_astc_supported) {}
-
- ~SurfaceBase() = default;
-
- virtual TView CreateView(const ViewParams& view_key) = 0;
-
- TView main_view;
- std::unordered_map<ViewParams, TView> views;
-
-private:
- TView GetView(const ViewParams& key) {
- const auto [entry, is_cache_miss] = views.try_emplace(key);
- auto& view{entry->second};
- if (is_cache_miss) {
- view = CreateView(key);
- }
- return view;
- }
-
- static constexpr u32 NO_RT = 0xFFFFFFFF;
-
- bool is_modified{};
- bool is_target{};
- bool is_registered{};
- bool is_picked{};
- bool is_memory_marked{};
- bool is_sync_pending{};
- u32 index{NO_RT};
- u64 modification_tick{};
-};
-
-} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
deleted file mode 100644
index e614a92df..000000000
--- a/src/video_core/texture_cache/surface_params.cpp
+++ /dev/null
@@ -1,445 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <string>
-#include <tuple>
-
-#include "common/alignment.h"
-#include "common/bit_util.h"
-#include "core/core.h"
-#include "video_core/engines/shader_bytecode.h"
-#include "video_core/surface.h"
-#include "video_core/texture_cache/format_lookup_table.h"
-#include "video_core/texture_cache/surface_params.h"
-
-namespace VideoCommon {
-
-using VideoCore::Surface::PixelFormat;
-using VideoCore::Surface::PixelFormatFromDepthFormat;
-using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
-using VideoCore::Surface::SurfaceTarget;
-using VideoCore::Surface::SurfaceTargetFromTextureType;
-using VideoCore::Surface::SurfaceType;
-
-namespace {
-
-SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) {
- switch (type) {
- case Tegra::Shader::TextureType::Texture1D:
- return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D;
- case Tegra::Shader::TextureType::Texture2D:
- return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D;
- case Tegra::Shader::TextureType::Texture3D:
- ASSERT(!is_array);
- return SurfaceTarget::Texture3D;
- case Tegra::Shader::TextureType::TextureCube:
- return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap;
- default:
- UNREACHABLE();
- return SurfaceTarget::Texture2D;
- }
-}
-
-SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) {
- switch (type) {
- case Tegra::Shader::ImageType::Texture1D:
- return SurfaceTarget::Texture1D;
- case Tegra::Shader::ImageType::TextureBuffer:
- return SurfaceTarget::TextureBuffer;
- case Tegra::Shader::ImageType::Texture1DArray:
- return SurfaceTarget::Texture1DArray;
- case Tegra::Shader::ImageType::Texture2D:
- return SurfaceTarget::Texture2D;
- case Tegra::Shader::ImageType::Texture2DArray:
- return SurfaceTarget::Texture2DArray;
- case Tegra::Shader::ImageType::Texture3D:
- return SurfaceTarget::Texture3D;
- default:
- UNREACHABLE();
- return SurfaceTarget::Texture2D;
- }
-}
-
-constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
- return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
-}
-
-} // Anonymous namespace
-
-SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_table,
- const Tegra::Texture::TICEntry& tic,
- const VideoCommon::Shader::Sampler& entry) {
- SurfaceParams params;
- params.is_tiled = tic.IsTiled();
- params.srgb_conversion = tic.IsSrgbConversionEnabled();
- params.block_width = params.is_tiled ? tic.BlockWidth() : 0;
- params.block_height = params.is_tiled ? tic.BlockHeight() : 0;
- params.block_depth = params.is_tiled ? tic.BlockDepth() : 0;
- params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
- params.pixel_format = lookup_table.GetPixelFormat(
- tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type);
- params.type = GetFormatType(params.pixel_format);
- if (entry.is_shadow && params.type == SurfaceType::ColorTexture) {
- switch (params.pixel_format) {
- case PixelFormat::R16_UNORM:
- case PixelFormat::R16_FLOAT:
- params.pixel_format = PixelFormat::D16_UNORM;
- break;
- case PixelFormat::R32_FLOAT:
- params.pixel_format = PixelFormat::D32_FLOAT;
- break;
- default:
- UNIMPLEMENTED_MSG("Unimplemented shadow convert format: {}",
- static_cast<u32>(params.pixel_format));
- }
- params.type = GetFormatType(params.pixel_format);
- }
- // TODO: on 1DBuffer we should use the tic info.
- if (tic.IsBuffer()) {
- params.target = SurfaceTarget::TextureBuffer;
- params.width = tic.Width();
- params.pitch = params.width * params.GetBytesPerPixel();
- params.height = 1;
- params.depth = 1;
- params.num_levels = 1;
- params.emulated_levels = 1;
- params.is_layered = false;
- } else {
- params.target = TextureTypeToSurfaceTarget(entry.type, entry.is_array);
- params.width = tic.Width();
- params.height = tic.Height();
- params.depth = tic.Depth();
- params.pitch = params.is_tiled ? 0 : tic.Pitch();
- if (params.target == SurfaceTarget::TextureCubemap ||
- params.target == SurfaceTarget::TextureCubeArray) {
- params.depth *= 6;
- }
- params.num_levels = tic.max_mip_level + 1;
- params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
- params.is_layered = params.IsLayered();
- }
- return params;
-}
-
-SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_table,
- const Tegra::Texture::TICEntry& tic,
- const VideoCommon::Shader::Image& entry) {
- SurfaceParams params;
- params.is_tiled = tic.IsTiled();
- params.srgb_conversion = tic.IsSrgbConversionEnabled();
- params.block_width = params.is_tiled ? tic.BlockWidth() : 0;
- params.block_height = params.is_tiled ? tic.BlockHeight() : 0;
- params.block_depth = params.is_tiled ? tic.BlockDepth() : 0;
- params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
- params.pixel_format = lookup_table.GetPixelFormat(
- tic.format, params.srgb_conversion, tic.r_type, tic.g_type, tic.b_type, tic.a_type);
- params.type = GetFormatType(params.pixel_format);
- params.target = ImageTypeToSurfaceTarget(entry.type);
- // TODO: on 1DBuffer we should use the tic info.
- if (tic.IsBuffer()) {
- params.target = SurfaceTarget::TextureBuffer;
- params.width = tic.Width();
- params.pitch = params.width * params.GetBytesPerPixel();
- params.height = 1;
- params.depth = 1;
- params.num_levels = 1;
- params.emulated_levels = 1;
- params.is_layered = false;
- } else {
- params.width = tic.Width();
- params.height = tic.Height();
- params.depth = tic.Depth();
- params.pitch = params.is_tiled ? 0 : tic.Pitch();
- if (params.target == SurfaceTarget::TextureCubemap ||
- params.target == SurfaceTarget::TextureCubeArray) {
- params.depth *= 6;
- }
- params.num_levels = tic.max_mip_level + 1;
- params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
- params.is_layered = params.IsLayered();
- }
- return params;
-}
-
-SurfaceParams SurfaceParams::CreateForDepthBuffer(Core::System& system) {
- const auto& regs = system.GPU().Maxwell3D().regs;
-
- const auto block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U);
- const bool is_layered = regs.zeta_layers > 1 && block_depth == 0;
- const auto pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
-
- return {
- .is_tiled = regs.zeta.memory_layout.type ==
- Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear,
- .srgb_conversion = false,
- .is_layered = is_layered,
- .block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U),
- .block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U),
- .block_depth = block_depth,
- .tile_width_spacing = 1,
- .width = regs.zeta_width,
- .height = regs.zeta_height,
- .depth = is_layered ? regs.zeta_layers.Value() : 1U,
- .pitch = 0,
- .num_levels = 1,
- .emulated_levels = 1,
- .pixel_format = pixel_format,
- .type = GetFormatType(pixel_format),
- .target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D,
- };
-}
-
-SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) {
- const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
- SurfaceParams params;
- params.is_tiled =
- config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
- params.srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB ||
- config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB;
- params.block_width = config.memory_layout.block_width;
- params.block_height = config.memory_layout.block_height;
- params.block_depth = config.memory_layout.block_depth;
- params.tile_width_spacing = 1;
- params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
- params.type = GetFormatType(params.pixel_format);
- if (params.is_tiled) {
- params.pitch = 0;
- params.width = config.width;
- } else {
- const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT;
- params.pitch = config.width;
- params.width = params.pitch / bpp;
- }
- params.height = config.height;
- params.num_levels = 1;
- params.emulated_levels = 1;
-
- if (config.memory_layout.is_3d != 0) {
- params.depth = config.layers.Value();
- params.is_layered = false;
- params.target = SurfaceTarget::Texture3D;
- } else if (config.layers > 1) {
- params.depth = config.layers.Value();
- params.is_layered = true;
- params.target = SurfaceTarget::Texture2DArray;
- } else {
- params.depth = 1;
- params.is_layered = false;
- params.target = SurfaceTarget::Texture2D;
- }
- return params;
-}
-
-SurfaceParams SurfaceParams::CreateForFermiCopySurface(
- const Tegra::Engines::Fermi2D::Regs::Surface& config) {
- const bool is_tiled = !config.linear;
- const auto pixel_format = PixelFormatFromRenderTargetFormat(config.format);
-
- SurfaceParams params{
- .is_tiled = is_tiled,
- .srgb_conversion = config.format == Tegra::RenderTargetFormat::B8G8R8A8_SRGB ||
- config.format == Tegra::RenderTargetFormat::A8B8G8R8_SRGB,
- .block_width = is_tiled ? std::min(config.BlockWidth(), 5U) : 0U,
- .block_height = is_tiled ? std::min(config.BlockHeight(), 5U) : 0U,
- .block_depth = is_tiled ? std::min(config.BlockDepth(), 5U) : 0U,
- .tile_width_spacing = 1,
- .width = config.width,
- .height = config.height,
- .depth = 1,
- .pitch = config.pitch,
- .num_levels = 1,
- .emulated_levels = 1,
- .pixel_format = pixel_format,
- .type = GetFormatType(pixel_format),
- // TODO(Rodrigo): Try to guess texture arrays from parameters
- .target = SurfaceTarget::Texture2D,
- };
-
- params.is_layered = params.IsLayered();
- return params;
-}
-
-VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
- const VideoCommon::Shader::Sampler& entry) {
- return TextureTypeToSurfaceTarget(entry.type, entry.is_array);
-}
-
-VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
- const VideoCommon::Shader::Image& entry) {
- return ImageTypeToSurfaceTarget(entry.type);
-}
-
-bool SurfaceParams::IsLayered() const {
- switch (target) {
- case SurfaceTarget::Texture1DArray:
- case SurfaceTarget::Texture2DArray:
- case SurfaceTarget::TextureCubemap:
- case SurfaceTarget::TextureCubeArray:
- return true;
- default:
- return false;
- }
-}
-
-// Auto block resizing algorithm from:
-// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
-u32 SurfaceParams::GetMipBlockHeight(u32 level) const {
- if (level == 0) {
- return this->block_height;
- }
-
- const u32 height_new{GetMipHeight(level)};
- const u32 default_block_height{GetDefaultBlockHeight()};
- const u32 blocks_in_y{(height_new + default_block_height - 1) / default_block_height};
- const u32 block_height_new = Common::Log2Ceil32(blocks_in_y);
- return std::clamp(block_height_new, 3U, 7U) - 3U;
-}
-
-u32 SurfaceParams::GetMipBlockDepth(u32 level) const {
- if (level == 0) {
- return this->block_depth;
- }
- if (is_layered) {
- return 0;
- }
-
- const u32 depth_new{GetMipDepth(level)};
- const u32 block_depth_new = Common::Log2Ceil32(depth_new);
- if (block_depth_new > 4) {
- return 5 - (GetMipBlockHeight(level) >= 2);
- }
- return block_depth_new;
-}
-
-std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const {
- std::size_t offset = 0;
- for (u32 i = 0; i < level; i++) {
- offset += GetInnerMipmapMemorySize(i, false, false);
- }
- return offset;
-}
-
-std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level, bool is_converted) const {
- std::size_t offset = 0;
- if (is_converted) {
- for (u32 i = 0; i < level; ++i) {
- offset += GetConvertedMipmapSize(i) * GetNumLayers();
- }
- } else {
- for (u32 i = 0; i < level; ++i) {
- offset += GetInnerMipmapMemorySize(i, true, false) * GetNumLayers();
- }
- }
- return offset;
-}
-
-std::size_t SurfaceParams::GetConvertedMipmapSize(u32 level) const {
- constexpr std::size_t rgba8_bpp = 4ULL;
- const std::size_t mip_width = GetMipWidth(level);
- const std::size_t mip_height = GetMipHeight(level);
- const std::size_t mip_depth = is_layered ? 1 : GetMipDepth(level);
- return mip_width * mip_height * mip_depth * rgba8_bpp;
-}
-
-std::size_t SurfaceParams::GetLayerSize(bool as_host_size, bool uncompressed) const {
- std::size_t size = 0;
- for (u32 level = 0; level < num_levels; ++level) {
- size += GetInnerMipmapMemorySize(level, as_host_size, uncompressed);
- }
- if (is_tiled && is_layered) {
- return Common::AlignBits(size, Tegra::Texture::GOB_SIZE_SHIFT + block_height + block_depth);
- }
- return size;
-}
-
-std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size,
- bool uncompressed) const {
- const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), GetDefaultBlockWidth())};
- const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), GetDefaultBlockHeight())};
- const u32 depth{is_layered ? 1U : GetMipDepth(level)};
- if (is_tiled) {
- return Tegra::Texture::CalculateSize(!as_host_size, GetBytesPerPixel(), width, height,
- depth, GetMipBlockHeight(level),
- GetMipBlockDepth(level));
- } else if (as_host_size || IsBuffer()) {
- return GetBytesPerPixel() * width * height * depth;
- } else {
- // Linear Texture Case
- return pitch * height * depth;
- }
-}
-
-bool SurfaceParams::operator==(const SurfaceParams& rhs) const {
- return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width,
- height, depth, pitch, num_levels, pixel_format, type, target) ==
- std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth,
- rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch,
- rhs.num_levels, rhs.pixel_format, rhs.type, rhs.target);
-}
-
-std::string SurfaceParams::TargetName() const {
- switch (target) {
- case SurfaceTarget::Texture1D:
- return "1D";
- case SurfaceTarget::TextureBuffer:
- return "TexBuffer";
- case SurfaceTarget::Texture2D:
- return "2D";
- case SurfaceTarget::Texture3D:
- return "3D";
- case SurfaceTarget::Texture1DArray:
- return "1DArray";
- case SurfaceTarget::Texture2DArray:
- return "2DArray";
- case SurfaceTarget::TextureCubemap:
- return "Cube";
- case SurfaceTarget::TextureCubeArray:
- return "CubeArray";
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
- UNREACHABLE();
- return fmt::format("TUK({})", static_cast<u32>(target));
- }
-}
-
-u32 SurfaceParams::GetBlockSize() const {
- const u32 x = 64U << block_width;
- const u32 y = 8U << block_height;
- const u32 z = 1U << block_depth;
- return x * y * z;
-}
-
-std::pair<u32, u32> SurfaceParams::GetBlockXY() const {
- const u32 x_pixels = 64U / GetBytesPerPixel();
- const u32 x = x_pixels << block_width;
- const u32 y = 8U << block_height;
- return {x, y};
-}
-
-std::tuple<u32, u32, u32> SurfaceParams::GetBlockOffsetXYZ(u32 offset) const {
- const auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
- const u32 block_size = GetBlockSize();
- const u32 block_index = offset / block_size;
- const u32 gob_offset = offset % block_size;
- const u32 gob_index = gob_offset / static_cast<u32>(Tegra::Texture::GOB_SIZE);
- const u32 x_gob_pixels = 64U / GetBytesPerPixel();
- const u32 x_block_pixels = x_gob_pixels << block_width;
- const u32 y_block_pixels = 8U << block_height;
- const u32 z_block_pixels = 1U << block_depth;
- const u32 x_blocks = div_ceil(width, x_block_pixels);
- const u32 y_blocks = div_ceil(height, y_block_pixels);
- const u32 z_blocks = div_ceil(depth, z_block_pixels);
- const u32 base_x = block_index % x_blocks;
- const u32 base_y = (block_index / x_blocks) % y_blocks;
- const u32 base_z = (block_index / (x_blocks * y_blocks)) % z_blocks;
- u32 x = base_x * x_block_pixels;
- u32 y = base_y * y_block_pixels;
- u32 z = base_z * z_block_pixels;
- z += gob_index >> block_height;
- y += (gob_index * 8U) % y_block_pixels;
- return {x, y, z};
-}
-
-} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
deleted file mode 100644
index 118aa689e..000000000
--- a/src/video_core/texture_cache/surface_params.h
+++ /dev/null
@@ -1,293 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <utility>
-
-#include "common/alignment.h"
-#include "common/bit_util.h"
-#include "common/cityhash.h"
-#include "common/common_types.h"
-#include "video_core/engines/fermi_2d.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/shader/shader_ir.h"
-#include "video_core/surface.h"
-#include "video_core/textures/decoders.h"
-
-namespace VideoCommon {
-
-class FormatLookupTable;
-
-class SurfaceParams {
-public:
- /// Creates SurfaceCachedParams from a texture configuration.
- static SurfaceParams CreateForTexture(const FormatLookupTable& lookup_table,
- const Tegra::Texture::TICEntry& tic,
- const VideoCommon::Shader::Sampler& entry);
-
- /// Creates SurfaceCachedParams from an image configuration.
- static SurfaceParams CreateForImage(const FormatLookupTable& lookup_table,
- const Tegra::Texture::TICEntry& tic,
- const VideoCommon::Shader::Image& entry);
-
- /// Creates SurfaceCachedParams for a depth buffer configuration.
- static SurfaceParams CreateForDepthBuffer(Core::System& system);
-
- /// Creates SurfaceCachedParams from a framebuffer configuration.
- static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index);
-
- /// Creates SurfaceCachedParams from a Fermi2D surface configuration.
- static SurfaceParams CreateForFermiCopySurface(
- const Tegra::Engines::Fermi2D::Regs::Surface& config);
-
- /// Obtains the texture target from a shader's sampler entry.
- static VideoCore::Surface::SurfaceTarget ExpectedTarget(
- const VideoCommon::Shader::Sampler& entry);
-
- /// Obtains the texture target from a shader's sampler entry.
- static VideoCore::Surface::SurfaceTarget ExpectedTarget(
- const VideoCommon::Shader::Image& entry);
-
- std::size_t Hash() const {
- return static_cast<std::size_t>(
- Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
- }
-
- bool operator==(const SurfaceParams& rhs) const;
-
- bool operator!=(const SurfaceParams& rhs) const {
- return !operator==(rhs);
- }
-
- std::size_t GetGuestSizeInBytes() const {
- return GetInnerMemorySize(false, false, false);
- }
-
- std::size_t GetHostSizeInBytes(bool is_converted) const {
- if (!is_converted) {
- return GetInnerMemorySize(true, false, false);
- }
- // ASTC is uncompressed in software, in emulated as RGBA8
- std::size_t host_size_in_bytes = 0;
- for (u32 level = 0; level < num_levels; ++level) {
- host_size_in_bytes += GetConvertedMipmapSize(level) * GetNumLayers();
- }
- return host_size_in_bytes;
- }
-
- u32 GetBlockAlignedWidth() const {
- return Common::AlignUp(width, 64 / GetBytesPerPixel());
- }
-
- /// Returns the width of a given mipmap level.
- u32 GetMipWidth(u32 level) const {
- return std::max(1U, width >> level);
- }
-
- /// Returns the height of a given mipmap level.
- u32 GetMipHeight(u32 level) const {
- return std::max(1U, height >> level);
- }
-
- /// Returns the depth of a given mipmap level.
- u32 GetMipDepth(u32 level) const {
- return is_layered ? depth : std::max(1U, depth >> level);
- }
-
- /// Returns the block height of a given mipmap level.
- u32 GetMipBlockHeight(u32 level) const;
-
- /// Returns the block depth of a given mipmap level.
- u32 GetMipBlockDepth(u32 level) const;
-
- /// Returns the best possible row/pitch alignment for the surface.
- u32 GetRowAlignment(u32 level, bool is_converted) const {
- const u32 bpp = is_converted ? 4 : GetBytesPerPixel();
- return 1U << Common::CountTrailingZeroes32(GetMipWidth(level) * bpp);
- }
-
- /// Returns the offset in bytes in guest memory of a given mipmap level.
- std::size_t GetGuestMipmapLevelOffset(u32 level) const;
-
- /// Returns the offset in bytes in host memory (linear) of a given mipmap level.
- std::size_t GetHostMipmapLevelOffset(u32 level, bool is_converted) const;
-
- /// Returns the size in bytes in guest memory of a given mipmap level.
- std::size_t GetGuestMipmapSize(u32 level) const {
- return GetInnerMipmapMemorySize(level, false, false);
- }
-
- /// Returns the size in bytes in host memory (linear) of a given mipmap level.
- std::size_t GetHostMipmapSize(u32 level) const {
- return GetInnerMipmapMemorySize(level, true, false) * GetNumLayers();
- }
-
- std::size_t GetConvertedMipmapSize(u32 level) const;
-
- /// Get this texture Tegra Block size in guest memory layout
- u32 GetBlockSize() const;
-
- /// Get X, Y coordinates max sizes of a single block.
- std::pair<u32, u32> GetBlockXY() const;
-
- /// Get the offset in x, y, z coordinates from a memory offset
- std::tuple<u32, u32, u32> GetBlockOffsetXYZ(u32 offset) const;
-
- /// Returns the size of a layer in bytes in guest memory.
- std::size_t GetGuestLayerSize() const {
- return GetLayerSize(false, false);
- }
-
- /// Returns the size of a layer in bytes in host memory for a given mipmap level.
- std::size_t GetHostLayerSize(u32 level) const {
- ASSERT(target != VideoCore::Surface::SurfaceTarget::Texture3D);
- return GetInnerMipmapMemorySize(level, true, false);
- }
-
- /// Returns the max possible mipmap that the texture can have in host gpu
- u32 MaxPossibleMipmap() const {
- const u32 max_mipmap_w = Common::Log2Ceil32(width) + 1U;
- const u32 max_mipmap_h = Common::Log2Ceil32(height) + 1U;
- const u32 max_mipmap = std::max(max_mipmap_w, max_mipmap_h);
- if (target != VideoCore::Surface::SurfaceTarget::Texture3D)
- return max_mipmap;
- return std::max(max_mipmap, Common::Log2Ceil32(depth) + 1U);
- }
-
- /// Returns if the guest surface is a compressed surface.
- bool IsCompressed() const {
- return GetDefaultBlockHeight() > 1 || GetDefaultBlockWidth() > 1;
- }
-
- /// Returns the default block width.
- u32 GetDefaultBlockWidth() const {
- return VideoCore::Surface::GetDefaultBlockWidth(pixel_format);
- }
-
- /// Returns the default block height.
- u32 GetDefaultBlockHeight() const {
- return VideoCore::Surface::GetDefaultBlockHeight(pixel_format);
- }
-
- /// Returns the bits per pixel.
- u32 GetBitsPerPixel() const {
- return VideoCore::Surface::GetFormatBpp(pixel_format);
- }
-
- /// Returns the bytes per pixel.
- u32 GetBytesPerPixel() const {
- return VideoCore::Surface::GetBytesPerPixel(pixel_format);
- }
-
- /// Returns true if the pixel format is a depth and/or stencil format.
- bool IsPixelFormatZeta() const {
- return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat &&
- pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat;
- }
-
- /// Returns is the surface is a TextureBuffer type of surface.
- bool IsBuffer() const {
- return target == VideoCore::Surface::SurfaceTarget::TextureBuffer;
- }
-
- /// Returns the number of layers in the surface.
- std::size_t GetNumLayers() const {
- return is_layered ? depth : 1;
- }
-
- /// Returns the debug name of the texture for use in graphic debuggers.
- std::string TargetName() const;
-
- // Helper used for out of class size calculations
- static std::size_t AlignLayered(const std::size_t out_size, const u32 block_height,
- const u32 block_depth) {
- return Common::AlignBits(out_size,
- Tegra::Texture::GOB_SIZE_SHIFT + block_height + block_depth);
- }
-
- /// Converts a width from a type of surface into another. This helps represent the
- /// equivalent value between compressed/non-compressed textures.
- static u32 ConvertWidth(u32 width, VideoCore::Surface::PixelFormat pixel_format_from,
- VideoCore::Surface::PixelFormat pixel_format_to) {
- const u32 bw1 = VideoCore::Surface::GetDefaultBlockWidth(pixel_format_from);
- const u32 bw2 = VideoCore::Surface::GetDefaultBlockWidth(pixel_format_to);
- return (width * bw2 + bw1 - 1) / bw1;
- }
-
- /// Converts a height from a type of surface into another. This helps represent the
- /// equivalent value between compressed/non-compressed textures.
- static u32 ConvertHeight(u32 height, VideoCore::Surface::PixelFormat pixel_format_from,
- VideoCore::Surface::PixelFormat pixel_format_to) {
- const u32 bh1 = VideoCore::Surface::GetDefaultBlockHeight(pixel_format_from);
- const u32 bh2 = VideoCore::Surface::GetDefaultBlockHeight(pixel_format_to);
- return (height * bh2 + bh1 - 1) / bh1;
- }
-
- // Finds the maximun possible width between 2 2D layers of different formats
- static u32 IntersectWidth(const SurfaceParams& src_params, const SurfaceParams& dst_params,
- const u32 src_level, const u32 dst_level) {
- const u32 bw1 = src_params.GetDefaultBlockWidth();
- const u32 bw2 = dst_params.GetDefaultBlockWidth();
- const u32 t_src_width = (src_params.GetMipWidth(src_level) * bw2 + bw1 - 1) / bw1;
- const u32 t_dst_width = (dst_params.GetMipWidth(dst_level) * bw1 + bw2 - 1) / bw2;
- return std::min(t_src_width, t_dst_width);
- }
-
- // Finds the maximun possible height between 2 2D layers of different formats
- static u32 IntersectHeight(const SurfaceParams& src_params, const SurfaceParams& dst_params,
- const u32 src_level, const u32 dst_level) {
- const u32 bh1 = src_params.GetDefaultBlockHeight();
- const u32 bh2 = dst_params.GetDefaultBlockHeight();
- const u32 t_src_height = (src_params.GetMipHeight(src_level) * bh2 + bh1 - 1) / bh1;
- const u32 t_dst_height = (dst_params.GetMipHeight(dst_level) * bh1 + bh2 - 1) / bh2;
- return std::min(t_src_height, t_dst_height);
- }
-
- bool is_tiled;
- bool srgb_conversion;
- bool is_layered;
- u32 block_width;
- u32 block_height;
- u32 block_depth;
- u32 tile_width_spacing;
- u32 width;
- u32 height;
- u32 depth;
- u32 pitch;
- u32 num_levels;
- u32 emulated_levels;
- VideoCore::Surface::PixelFormat pixel_format;
- VideoCore::Surface::SurfaceType type;
- VideoCore::Surface::SurfaceTarget target;
-
-private:
- /// Returns the size of a given mipmap level inside a layer.
- std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool uncompressed) const;
-
- /// Returns the size of all mipmap levels and aligns as needed.
- std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const {
- return GetLayerSize(as_host_size, uncompressed) *
- (layer_only ? 1U : (is_layered ? depth : 1U));
- }
-
- /// Returns the size of a layer
- std::size_t GetLayerSize(bool as_host_size, bool uncompressed) const;
-
- /// Returns true if these parameters are from a layered surface.
- bool IsLayered() const;
-};
-
-} // namespace VideoCommon
-
-namespace std {
-
-template <>
-struct hash<VideoCommon::SurfaceParams> {
- std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept {
- return k.Hash();
- }
-};
-
-} // namespace std
diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp
deleted file mode 100644
index 6b5f5984b..000000000
--- a/src/video_core/texture_cache/surface_view.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <tuple>
-
-#include "common/common_types.h"
-#include "video_core/texture_cache/surface_view.h"
-
-namespace VideoCommon {
-
-std::size_t ViewParams::Hash() const {
- return static_cast<std::size_t>(base_layer) ^ (static_cast<std::size_t>(num_layers) << 16) ^
- (static_cast<std::size_t>(base_level) << 24) ^
- (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36);
-}
-
-bool ViewParams::operator==(const ViewParams& rhs) const {
- return std::tie(base_layer, num_layers, base_level, num_levels, target) ==
- std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels, rhs.target);
-}
-
-bool ViewParams::operator!=(const ViewParams& rhs) const {
- return !operator==(rhs);
-}
-
-} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h
deleted file mode 100644
index 90a8bb0ae..000000000
--- a/src/video_core/texture_cache/surface_view.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <functional>
-
-#include "common/common_types.h"
-#include "video_core/surface.h"
-#include "video_core/texture_cache/surface_params.h"
-
-namespace VideoCommon {
-
-struct ViewParams {
- constexpr explicit ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer,
- u32 num_layers, u32 base_level, u32 num_levels)
- : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level},
- num_levels{num_levels} {}
-
- std::size_t Hash() const;
-
- bool operator==(const ViewParams& rhs) const;
- bool operator!=(const ViewParams& rhs) const;
-
- bool IsLayered() const {
- switch (target) {
- case VideoCore::Surface::SurfaceTarget::Texture1DArray:
- case VideoCore::Surface::SurfaceTarget::Texture2DArray:
- case VideoCore::Surface::SurfaceTarget::TextureCubemap:
- case VideoCore::Surface::SurfaceTarget::TextureCubeArray:
- return true;
- default:
- return false;
- }
- }
-
- VideoCore::Surface::SurfaceTarget target{};
- u32 base_layer{};
- u32 num_layers{};
- u32 base_level{};
- u32 num_levels{};
-};
-
-class ViewBase {
-public:
- constexpr explicit ViewBase(const ViewParams& params) : params{params} {}
-
- constexpr const ViewParams& GetViewParams() const {
- return params;
- }
-
-protected:
- ViewParams params;
-};
-
-} // namespace VideoCommon
-
-namespace std {
-
-template <>
-struct hash<VideoCommon::ViewParams> {
- std::size_t operator()(const VideoCommon::ViewParams& k) const noexcept {
- return k.Hash();
- }
-};
-
-} // namespace std
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 96c4e4cc2..b1da69971 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -6,1304 +6,1446 @@
#include <algorithm>
#include <array>
-#include <list>
+#include <bit>
#include <memory>
#include <mutex>
-#include <set>
-#include <tuple>
+#include <optional>
+#include <span>
+#include <type_traits>
#include <unordered_map>
+#include <utility>
#include <vector>
#include <boost/container/small_vector.hpp>
-#include <boost/icl/interval_map.hpp>
-#include <boost/range/iterator_range.hpp>
-#include "common/assert.h"
+#include "common/alignment.h"
+#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "common/math_util.h"
-#include "core/core.h"
-#include "core/memory.h"
-#include "core/settings.h"
+#include "common/logging/log.h"
#include "video_core/compatible_formats.h"
+#include "video_core/delayed_destruction_ring.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/fermi_2d.h"
+#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
-#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/surface.h"
-#include "video_core/texture_cache/copy_params.h"
+#include "video_core/texture_cache/descriptor_table.h"
#include "video_core/texture_cache/format_lookup_table.h"
-#include "video_core/texture_cache/surface_base.h"
-#include "video_core/texture_cache/surface_params.h"
-#include "video_core/texture_cache/surface_view.h"
-
-namespace Tegra::Texture {
-struct FullTextureInfo;
-}
-
-namespace VideoCore {
-class RasterizerInterface;
-}
+#include "video_core/texture_cache/formatter.h"
+#include "video_core/texture_cache/image_base.h"
+#include "video_core/texture_cache/image_info.h"
+#include "video_core/texture_cache/image_view_base.h"
+#include "video_core/texture_cache/image_view_info.h"
+#include "video_core/texture_cache/render_targets.h"
+#include "video_core/texture_cache/samples_helper.h"
+#include "video_core/texture_cache/slot_vector.h"
+#include "video_core/texture_cache/types.h"
+#include "video_core/texture_cache/util.h"
+#include "video_core/textures/texture.h"
namespace VideoCommon {
-using VideoCore::Surface::FormatCompatibility;
+using Tegra::Texture::SwizzleSource;
+using Tegra::Texture::TextureType;
+using Tegra::Texture::TICEntry;
+using Tegra::Texture::TSCEntry;
+using VideoCore::Surface::GetFormatType;
+using VideoCore::Surface::IsCopyCompatible;
using VideoCore::Surface::PixelFormat;
-using VideoCore::Surface::SurfaceTarget;
-using RenderTargetConfig = Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig;
+using VideoCore::Surface::PixelFormatFromDepthFormat;
+using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
+using VideoCore::Surface::SurfaceType;
-template <typename TSurface, typename TView>
+template <class P>
class TextureCache {
- using VectorSurface = boost::container::small_vector<TSurface, 1>;
+ /// Address shift for caching images into a hash table
+ static constexpr u64 PAGE_BITS = 20;
+
+ /// Enables debugging features to the texture cache
+ static constexpr bool ENABLE_VALIDATION = P::ENABLE_VALIDATION;
+ /// Implement blits as copies between framebuffers
+ static constexpr bool FRAMEBUFFER_BLITS = P::FRAMEBUFFER_BLITS;
+ /// True when some copies have to be emulated
+ static constexpr bool HAS_EMULATED_COPIES = P::HAS_EMULATED_COPIES;
+
+ /// Image view ID for null descriptors
+ static constexpr ImageViewId NULL_IMAGE_VIEW_ID{0};
+ /// Sampler ID for bugged sampler ids
+ static constexpr SamplerId NULL_SAMPLER_ID{0};
+
+ using Runtime = typename P::Runtime;
+ using Image = typename P::Image;
+ using ImageAlloc = typename P::ImageAlloc;
+ using ImageView = typename P::ImageView;
+ using Sampler = typename P::Sampler;
+ using Framebuffer = typename P::Framebuffer;
+
+ struct BlitImages {
+ ImageId dst_id;
+ ImageId src_id;
+ PixelFormat dst_format;
+ PixelFormat src_format;
+ };
+
+ template <typename T>
+ struct IdentityHash {
+ [[nodiscard]] size_t operator()(T value) const noexcept {
+ return static_cast<size_t>(value);
+ }
+ };
public:
- void InvalidateRegion(VAddr addr, std::size_t size) {
- std::lock_guard lock{mutex};
+ explicit TextureCache(Runtime&, VideoCore::RasterizerInterface&, Tegra::Engines::Maxwell3D&,
+ Tegra::Engines::KeplerCompute&, Tegra::MemoryManager&);
- for (const auto& surface : GetSurfacesInRegion(addr, size)) {
- Unregister(surface);
- }
- }
+ /// Notify the cache that a new frame has been queued
+ void TickFrame();
- void OnCPUWrite(VAddr addr, std::size_t size) {
- std::lock_guard lock{mutex};
+ /// Return a constant reference to the given image view id
+ [[nodiscard]] const ImageView& GetImageView(ImageViewId id) const noexcept;
- for (const auto& surface : GetSurfacesInRegion(addr, size)) {
- if (surface->IsMemoryMarked()) {
- UnmarkMemory(surface);
- surface->SetSyncPending(true);
- marked_for_unregister.emplace_back(surface);
- }
- }
- }
+ /// Return a reference to the given image view id
+ [[nodiscard]] ImageView& GetImageView(ImageViewId id) noexcept;
- void SyncGuestHost() {
- std::lock_guard lock{mutex};
+ /// Fill image_view_ids with the graphics images in indices
+ void FillGraphicsImageViews(std::span<const u32> indices,
+ std::span<ImageViewId> image_view_ids);
- for (const auto& surface : marked_for_unregister) {
- if (surface->IsRegistered()) {
- surface->SetSyncPending(false);
- Unregister(surface);
- }
- }
- marked_for_unregister.clear();
- }
+ /// Fill image_view_ids with the compute images in indices
+ void FillComputeImageViews(std::span<const u32> indices, std::span<ImageViewId> image_view_ids);
- /**
- * Guarantees that rendertargets don't unregister themselves if the
- * collide. Protection is currently only done on 3D slices.
- */
- void GuardRenderTargets(bool new_guard) {
- guard_render_targets = new_guard;
- }
+ /// Get the sampler from the graphics descriptor table in the specified index
+ Sampler* GetGraphicsSampler(u32 index);
- void GuardSamplers(bool new_guard) {
- guard_samplers = new_guard;
- }
+ /// Get the sampler from the compute descriptor table in the specified index
+ Sampler* GetComputeSampler(u32 index);
- void FlushRegion(VAddr addr, std::size_t size) {
- std::lock_guard lock{mutex};
+ /// Refresh the state for graphics image view and sampler descriptors
+ void SynchronizeGraphicsDescriptors();
- auto surfaces = GetSurfacesInRegion(addr, size);
- if (surfaces.empty()) {
- return;
- }
- std::sort(surfaces.begin(), surfaces.end(), [](const TSurface& a, const TSurface& b) {
- return a->GetModificationTick() < b->GetModificationTick();
- });
- for (const auto& surface : surfaces) {
- mutex.unlock();
- FlushSurface(surface);
- mutex.lock();
- }
- }
+ /// Refresh the state for compute image view and sampler descriptors
+ void SynchronizeComputeDescriptors();
- bool MustFlushRegion(VAddr addr, std::size_t size) {
- std::lock_guard lock{mutex};
+ /// Update bound render targets and upload memory if necessary
+ /// @param is_clear True when the render targets are being used for clears
+ void UpdateRenderTargets(bool is_clear);
- const auto surfaces = GetSurfacesInRegion(addr, size);
- return std::any_of(surfaces.cbegin(), surfaces.cend(),
- [](const TSurface& surface) { return surface->IsModified(); });
- }
+ /// Find a framebuffer with the currently bound render targets
+ /// UpdateRenderTargets should be called before this
+ Framebuffer* GetFramebuffer();
- TView GetTextureSurface(const Tegra::Texture::TICEntry& tic,
- const VideoCommon::Shader::Sampler& entry) {
- std::lock_guard lock{mutex};
- const auto gpu_addr{tic.Address()};
- if (!gpu_addr) {
- return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
- }
+ /// Mark images in a range as modified from the CPU
+ void WriteMemory(VAddr cpu_addr, size_t size);
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
- if (!cpu_addr) {
- return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
- }
+ /// Download contents of host images to guest memory in a region
+ void DownloadMemory(VAddr cpu_addr, size_t size);
- if (!IsTypeCompatible(tic.texture_type, entry)) {
- return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
- }
+ /// Remove images in a region
+ void UnmapMemory(VAddr cpu_addr, size_t size);
- const auto params{SurfaceParams::CreateForTexture(format_lookup_table, tic, entry)};
- const auto [surface, view] = GetSurface(gpu_addr, *cpu_addr, params, true, false);
- if (guard_samplers) {
- sampled_textures.push_back(surface);
- }
- return view;
- }
+ /// Blit an image with the given parameters
+ void BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
+ const Tegra::Engines::Fermi2D::Surface& src,
+ const Tegra::Engines::Fermi2D::Config& copy);
- TView GetImageSurface(const Tegra::Texture::TICEntry& tic,
- const VideoCommon::Shader::Image& entry) {
- std::lock_guard lock{mutex};
- const auto gpu_addr{tic.Address()};
- if (!gpu_addr) {
- return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
- }
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
- if (!cpu_addr) {
- return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
- }
- const auto params{SurfaceParams::CreateForImage(format_lookup_table, tic, entry)};
- const auto [surface, view] = GetSurface(gpu_addr, *cpu_addr, params, true, false);
- if (guard_samplers) {
- sampled_textures.push_back(surface);
- }
- return view;
- }
+ /// Invalidate the contents of the color buffer index
+ /// These contents become unspecified, the cache can assume aggressive optimizations.
+ void InvalidateColorBuffer(size_t index);
- bool TextureBarrier() {
- const bool any_rt =
- std::any_of(sampled_textures.begin(), sampled_textures.end(),
- [](const auto& surface) { return surface->IsRenderTarget(); });
- sampled_textures.clear();
- return any_rt;
- }
+ /// Invalidate the contents of the depth buffer
+ /// These contents become unspecified, the cache can assume aggressive optimizations.
+ void InvalidateDepthBuffer();
- TView GetDepthBufferSurface(bool preserve_contents) {
- std::lock_guard lock{mutex};
- auto& maxwell3d = system.GPU().Maxwell3D();
- if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer]) {
- return depth_buffer.view;
- }
- maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false;
+ /// Try to find a cached image view in the given CPU address
+ [[nodiscard]] ImageView* TryFindFramebufferImageView(VAddr cpu_addr);
- const auto& regs{maxwell3d.regs};
- const auto gpu_addr{regs.zeta.Address()};
- if (!gpu_addr || !regs.zeta_enable) {
- SetEmptyDepthBuffer();
- return {};
- }
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
- if (!cpu_addr) {
- SetEmptyDepthBuffer();
- return {};
- }
- const auto depth_params{SurfaceParams::CreateForDepthBuffer(system)};
- auto surface_view = GetSurface(gpu_addr, *cpu_addr, depth_params, preserve_contents, true);
- if (depth_buffer.target)
- depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
- depth_buffer.target = surface_view.first;
- depth_buffer.view = surface_view.second;
- if (depth_buffer.target)
- depth_buffer.target->MarkAsRenderTarget(true, DEPTH_RT);
- return surface_view.second;
- }
-
- TView GetColorBufferSurface(std::size_t index, bool preserve_contents) {
- std::lock_guard lock{mutex};
- ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
- auto& maxwell3d = system.GPU().Maxwell3D();
- if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index]) {
- return render_targets[index].view;
- }
- maxwell3d.dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index] = false;
+ /// Return true when there are uncommitted images to be downloaded
+ [[nodiscard]] bool HasUncommittedFlushes() const noexcept;
- const auto& regs{maxwell3d.regs};
- if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 ||
- regs.rt[index].format == Tegra::RenderTargetFormat::NONE) {
- SetEmptyColorBuffer(index);
- return {};
- }
+ /// Return true when the caller should wait for async downloads
+ [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept;
- const auto& config{regs.rt[index]};
- const auto gpu_addr{config.Address()};
- if (!gpu_addr) {
- SetEmptyColorBuffer(index);
- return {};
- }
+ /// Commit asynchronous downloads
+ void CommitAsyncFlushes();
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
- if (!cpu_addr) {
- SetEmptyColorBuffer(index);
- return {};
- }
+ /// Pop asynchronous downloads
+ void PopAsyncFlushes();
+
+ /// Return true when a CPU region is modified from the GPU
+ [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
- auto surface_view =
- GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
- preserve_contents, true);
- if (render_targets[index].target) {
- auto& surface = render_targets[index].target;
- surface->MarkAsRenderTarget(false, NO_RT);
- const auto& cr_params = surface->GetSurfaceParams();
- if (!cr_params.is_tiled && Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
- AsyncFlushSurface(surface);
+ std::mutex mutex;
+
+private:
+ /// Iterate over all page indices in a range
+ template <typename Func>
+ static void ForEachPage(VAddr addr, size_t size, Func&& func) {
+ static constexpr bool RETURNS_BOOL = std::is_same_v<std::invoke_result<Func, u64>, bool>;
+ const u64 page_end = (addr + size - 1) >> PAGE_BITS;
+ for (u64 page = addr >> PAGE_BITS; page <= page_end; ++page) {
+ if constexpr (RETURNS_BOOL) {
+ if (func(page)) {
+ break;
+ }
+ } else {
+ func(page);
}
}
- render_targets[index].target = surface_view.first;
- render_targets[index].view = surface_view.second;
- if (render_targets[index].target)
- render_targets[index].target->MarkAsRenderTarget(true, static_cast<u32>(index));
- return surface_view.second;
}
- void MarkColorBufferInUse(std::size_t index) {
- if (auto& render_target = render_targets[index].target) {
- render_target->MarkAsModified(true, Tick());
- }
- }
+ /// Fills image_view_ids in the image views in indices
+ void FillImageViews(DescriptorTable<TICEntry>& table,
+ std::span<ImageViewId> cached_image_view_ids, std::span<const u32> indices,
+ std::span<ImageViewId> image_view_ids);
- void MarkDepthBufferInUse() {
- if (depth_buffer.target) {
- depth_buffer.target->MarkAsModified(true, Tick());
- }
- }
+ /// Find or create an image view in the guest descriptor table
+ ImageViewId VisitImageView(DescriptorTable<TICEntry>& table,
+ std::span<ImageViewId> cached_image_view_ids, u32 index);
- void SetEmptyDepthBuffer() {
- if (depth_buffer.target == nullptr) {
- return;
- }
- depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
- depth_buffer.target = nullptr;
- depth_buffer.view = nullptr;
- }
+ /// Find or create a framebuffer with the given render target parameters
+ FramebufferId GetFramebufferId(const RenderTargets& key);
- void SetEmptyColorBuffer(std::size_t index) {
- if (render_targets[index].target == nullptr) {
- return;
- }
- render_targets[index].target->MarkAsRenderTarget(false, NO_RT);
- render_targets[index].target = nullptr;
- render_targets[index].view = nullptr;
- }
-
- void DoFermiCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
- const Tegra::Engines::Fermi2D::Regs::Surface& dst_config,
- const Tegra::Engines::Fermi2D::Config& copy_config) {
- std::lock_guard lock{mutex};
- SurfaceParams src_params = SurfaceParams::CreateForFermiCopySurface(src_config);
- SurfaceParams dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config);
- const GPUVAddr src_gpu_addr = src_config.Address();
- const GPUVAddr dst_gpu_addr = dst_config.Address();
- DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
-
- const auto& memory_manager = system.GPU().MemoryManager();
- const std::optional<VAddr> dst_cpu_addr = memory_manager.GpuToCpuAddress(dst_gpu_addr);
- const std::optional<VAddr> src_cpu_addr = memory_manager.GpuToCpuAddress(src_gpu_addr);
- std::pair dst_surface = GetSurface(dst_gpu_addr, *dst_cpu_addr, dst_params, true, false);
- TView src_surface = GetSurface(src_gpu_addr, *src_cpu_addr, src_params, true, false).second;
- ImageBlit(src_surface, dst_surface.second, copy_config);
- dst_surface.first->MarkAsModified(true, Tick());
- }
-
- TSurface TryFindFramebufferSurface(VAddr addr) const {
- if (!addr) {
- return nullptr;
- }
- const VAddr page = addr >> registry_page_bits;
- const auto it = registry.find(page);
- if (it == registry.end()) {
- return nullptr;
- }
- const auto& list = it->second;
- const auto found = std::find_if(list.begin(), list.end(), [addr](const auto& surface) {
- return surface->GetCpuAddr() == addr;
- });
- return found != list.end() ? *found : nullptr;
- }
+ /// Refresh the contents (pixel data) of an image
+ void RefreshContents(Image& image);
- u64 Tick() {
- return ++ticks;
- }
+ /// Upload data from guest to an image
+ template <typename StagingBuffer>
+ void UploadImageContents(Image& image, StagingBuffer& staging_buffer);
- void CommitAsyncFlushes() {
- committed_flushes.push_back(uncommitted_flushes);
- uncommitted_flushes.reset();
- }
+ /// Find or create an image view from a guest descriptor
+ [[nodiscard]] ImageViewId FindImageView(const TICEntry& config);
- bool HasUncommittedFlushes() const {
- return uncommitted_flushes != nullptr;
- }
+ /// Create a new image view from a guest descriptor
+ [[nodiscard]] ImageViewId CreateImageView(const TICEntry& config);
- bool ShouldWaitAsyncFlushes() const {
- return !committed_flushes.empty() && committed_flushes.front() != nullptr;
- }
+ /// Find or create an image from the given parameters
+ [[nodiscard]] ImageId FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
+ RelaxedOptions options = RelaxedOptions{});
- void PopAsyncFlushes() {
- if (committed_flushes.empty()) {
- return;
- }
- auto& flush_list = committed_flushes.front();
- if (!flush_list) {
- committed_flushes.pop_front();
- return;
- }
- for (TSurface& surface : *flush_list) {
- FlushSurface(surface);
- }
- committed_flushes.pop_front();
- }
+ /// Find an image from the given parameters
+ [[nodiscard]] ImageId FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
+ RelaxedOptions options);
-protected:
- explicit TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
- bool is_astc_supported)
- : system{system}, is_astc_supported{is_astc_supported}, rasterizer{rasterizer} {
- for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
- SetEmptyColorBuffer(i);
- }
+ /// Create an image from the given parameters
+ [[nodiscard]] ImageId InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
+ RelaxedOptions options);
- SetEmptyDepthBuffer();
- staging_cache.SetSize(2);
+ /// Create a new image and join perfectly matching existing images
+ /// Remove joined images from the cache
+ [[nodiscard]] ImageId JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr);
- const auto make_siblings = [this](PixelFormat a, PixelFormat b) {
- siblings_table[static_cast<std::size_t>(a)] = b;
- siblings_table[static_cast<std::size_t>(b)] = a;
- };
- std::fill(siblings_table.begin(), siblings_table.end(), PixelFormat::Invalid);
- make_siblings(PixelFormat::D16_UNORM, PixelFormat::R16_UNORM);
- make_siblings(PixelFormat::D32_FLOAT, PixelFormat::R32_FLOAT);
- make_siblings(PixelFormat::D32_FLOAT_S8_UINT, PixelFormat::R32G32_FLOAT);
+ /// Return a blit image pair from the given guest blit parameters
+ [[nodiscard]] BlitImages GetBlitImages(const Tegra::Engines::Fermi2D::Surface& dst,
+ const Tegra::Engines::Fermi2D::Surface& src);
- sampled_textures.reserve(64);
- }
+ /// Find or create a sampler from a guest descriptor sampler
+ [[nodiscard]] SamplerId FindSampler(const TSCEntry& config);
- ~TextureCache() = default;
+ /// Find or create an image view for the given color buffer index
+ [[nodiscard]] ImageViewId FindColorBuffer(size_t index, bool is_clear);
- virtual TSurface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) = 0;
+ /// Find or create an image view for the depth buffer
+ [[nodiscard]] ImageViewId FindDepthBuffer(bool is_clear);
- virtual void ImageCopy(TSurface& src_surface, TSurface& dst_surface,
- const CopyParams& copy_params) = 0;
+ /// Find or create a view for a render target with the given image parameters
+ [[nodiscard]] ImageViewId FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr,
+ bool is_clear);
- virtual void ImageBlit(TView& src_view, TView& dst_view,
- const Tegra::Engines::Fermi2D::Config& copy_config) = 0;
+ /// Iterates over all the images in a region calling func
+ template <typename Func>
+ void ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func);
- // Depending on the backend, a buffer copy can be slow as it means deoptimizing the texture
- // and reading it from a separate buffer.
- virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0;
+ /// Find or create an image view in the given image with the passed parameters
+ [[nodiscard]] ImageViewId FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info);
- void ManageRenderTargetUnregister(TSurface& surface) {
- auto& dirty = system.GPU().Maxwell3D().dirty;
- const u32 index = surface->GetRenderTarget();
- if (index == DEPTH_RT) {
- dirty.flags[VideoCommon::Dirty::ZetaBuffer] = true;
- } else {
- dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index] = true;
- }
- dirty.flags[VideoCommon::Dirty::RenderTargets] = true;
+ /// Register image in the page table
+ void RegisterImage(ImageId image);
+
+ /// Unregister image from the page table
+ void UnregisterImage(ImageId image);
+
+ /// Track CPU reads and writes for image
+ void TrackImage(ImageBase& image);
+
+ /// Stop tracking CPU reads and writes for image
+ void UntrackImage(ImageBase& image);
+
+ /// Delete image from the cache
+ void DeleteImage(ImageId image);
+
+ /// Remove image views references from the cache
+ void RemoveImageViewReferences(std::span<const ImageViewId> removed_views);
+
+ /// Remove framebuffers using the given image views from the cache
+ void RemoveFramebuffers(std::span<const ImageViewId> removed_views);
+
+ /// Mark an image as modified from the GPU
+ void MarkModification(ImageBase& image) noexcept;
+
+ /// Synchronize image aliases, copying data if needed
+ void SynchronizeAliases(ImageId image_id);
+
+ /// Prepare an image to be used
+ void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
+
+ /// Prepare an image view to be used
+ void PrepareImageView(ImageViewId image_view_id, bool is_modification, bool invalidate);
+
+ /// Execute copies from one image to the other, even if they are incompatible
+ void CopyImage(ImageId dst_id, ImageId src_id, std::span<const ImageCopy> copies);
+
+ /// Bind an image view as render target, downloading resources preemtively if needed
+ void BindRenderTarget(ImageViewId* old_id, ImageViewId new_id);
+
+ /// Create a render target from a given image and image view parameters
+ [[nodiscard]] std::pair<FramebufferId, ImageViewId> RenderTargetFromImage(
+ ImageId, const ImageViewInfo& view_info);
+
+ /// Returns true if the current clear parameters clear the whole image of a given image view
+ [[nodiscard]] bool IsFullClear(ImageViewId id);
+
+ Runtime& runtime;
+ VideoCore::RasterizerInterface& rasterizer;
+ Tegra::Engines::Maxwell3D& maxwell3d;
+ Tegra::Engines::KeplerCompute& kepler_compute;
+ Tegra::MemoryManager& gpu_memory;
+
+ DescriptorTable<TICEntry> graphics_image_table{gpu_memory};
+ DescriptorTable<TSCEntry> graphics_sampler_table{gpu_memory};
+ std::vector<SamplerId> graphics_sampler_ids;
+ std::vector<ImageViewId> graphics_image_view_ids;
+
+ DescriptorTable<TICEntry> compute_image_table{gpu_memory};
+ DescriptorTable<TSCEntry> compute_sampler_table{gpu_memory};
+ std::vector<SamplerId> compute_sampler_ids;
+ std::vector<ImageViewId> compute_image_view_ids;
+
+ RenderTargets render_targets;
+
+ std::unordered_map<TICEntry, ImageViewId> image_views;
+ std::unordered_map<TSCEntry, SamplerId> samplers;
+ std::unordered_map<RenderTargets, FramebufferId> framebuffers;
+
+ std::unordered_map<u64, std::vector<ImageId>, IdentityHash<u64>> page_table;
+
+ bool has_deleted_images = false;
+
+ SlotVector<Image> slot_images;
+ SlotVector<ImageView> slot_image_views;
+ SlotVector<ImageAlloc> slot_image_allocs;
+ SlotVector<Sampler> slot_samplers;
+ SlotVector<Framebuffer> slot_framebuffers;
+
+ // TODO: This data structure is not optimal and it should be reworked
+ std::vector<ImageId> uncommitted_downloads;
+ std::queue<std::vector<ImageId>> committed_downloads;
+
+ static constexpr size_t TICKS_TO_DESTROY = 6;
+ DelayedDestructionRing<Image, TICKS_TO_DESTROY> sentenced_images;
+ DelayedDestructionRing<ImageView, TICKS_TO_DESTROY> sentenced_image_view;
+ DelayedDestructionRing<Framebuffer, TICKS_TO_DESTROY> sentenced_framebuffers;
+
+ std::unordered_map<GPUVAddr, ImageAllocId> image_allocs_table;
+
+ u64 modification_tick = 0;
+ u64 frame_tick = 0;
+};
+
+template <class P>
+TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface& rasterizer_,
+ Tegra::Engines::Maxwell3D& maxwell3d_,
+ Tegra::Engines::KeplerCompute& kepler_compute_,
+ Tegra::MemoryManager& gpu_memory_)
+ : runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
+ kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_} {
+ // Configure null sampler
+ TSCEntry sampler_descriptor{};
+ sampler_descriptor.min_filter.Assign(Tegra::Texture::TextureFilter::Linear);
+ sampler_descriptor.mag_filter.Assign(Tegra::Texture::TextureFilter::Linear);
+ sampler_descriptor.mipmap_filter.Assign(Tegra::Texture::TextureMipmapFilter::Linear);
+ sampler_descriptor.cubemap_anisotropy.Assign(1);
+
+ // Make sure the first index is reserved for the null resources
+ // This way the null resource becomes a compile time constant
+ void(slot_image_views.insert(runtime, NullImageParams{}));
+ void(slot_samplers.insert(runtime, sampler_descriptor));
+}
+
+template <class P>
+void TextureCache<P>::TickFrame() {
+ // Tick sentenced resources in this order to ensure they are destroyed in the right order
+ sentenced_images.Tick();
+ sentenced_framebuffers.Tick();
+ sentenced_image_view.Tick();
+ ++frame_tick;
+}
+
+template <class P>
+const typename P::ImageView& TextureCache<P>::GetImageView(ImageViewId id) const noexcept {
+ return slot_image_views[id];
+}
+
+template <class P>
+typename P::ImageView& TextureCache<P>::GetImageView(ImageViewId id) noexcept {
+ return slot_image_views[id];
+}
+
+template <class P>
+void TextureCache<P>::FillGraphicsImageViews(std::span<const u32> indices,
+ std::span<ImageViewId> image_view_ids) {
+ FillImageViews(graphics_image_table, graphics_image_view_ids, indices, image_view_ids);
+}
+
+template <class P>
+void TextureCache<P>::FillComputeImageViews(std::span<const u32> indices,
+ std::span<ImageViewId> image_view_ids) {
+ FillImageViews(compute_image_table, compute_image_view_ids, indices, image_view_ids);
+}
+
+template <class P>
+typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) {
+ [[unlikely]] if (index > graphics_sampler_table.Limit()) {
+ LOG_ERROR(HW_GPU, "Invalid sampler index={}", index);
+ return &slot_samplers[NULL_SAMPLER_ID];
+ }
+ const auto [descriptor, is_new] = graphics_sampler_table.Read(index);
+ SamplerId& id = graphics_sampler_ids[index];
+ [[unlikely]] if (is_new) {
+ id = FindSampler(descriptor);
}
+ return &slot_samplers[id];
+}
- void Register(TSurface surface) {
- const GPUVAddr gpu_addr = surface->GetGpuAddr();
- const std::size_t size = surface->GetSizeInBytes();
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
- if (!cpu_addr) {
- LOG_CRITICAL(HW_GPU, "Failed to register surface with unmapped gpu_address 0x{:016x}",
- gpu_addr);
- return;
- }
- surface->SetCpuAddr(*cpu_addr);
- RegisterInnerCache(surface);
- surface->MarkAsRegistered(true);
- surface->SetMemoryMarked(true);
- rasterizer.UpdatePagesCachedCount(*cpu_addr, size, 1);
+template <class P>
+typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) {
+ [[unlikely]] if (index > compute_sampler_table.Limit()) {
+ LOG_ERROR(HW_GPU, "Invalid sampler index={}", index);
+ return &slot_samplers[NULL_SAMPLER_ID];
}
+ const auto [descriptor, is_new] = compute_sampler_table.Read(index);
+ SamplerId& id = compute_sampler_ids[index];
+ [[unlikely]] if (is_new) {
+ id = FindSampler(descriptor);
+ }
+ return &slot_samplers[id];
+}
- void UnmarkMemory(TSurface surface) {
- if (!surface->IsMemoryMarked()) {
- return;
- }
- const std::size_t size = surface->GetSizeInBytes();
- const VAddr cpu_addr = surface->GetCpuAddr();
- rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1);
- surface->SetMemoryMarked(false);
+template <class P>
+void TextureCache<P>::SynchronizeGraphicsDescriptors() {
+ using SamplerIndex = Tegra::Engines::Maxwell3D::Regs::SamplerIndex;
+ const bool linked_tsc = maxwell3d.regs.sampler_index == SamplerIndex::ViaHeaderIndex;
+ const u32 tic_limit = maxwell3d.regs.tic.limit;
+ const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d.regs.tsc.limit;
+ if (graphics_sampler_table.Synchornize(maxwell3d.regs.tsc.Address(), tsc_limit)) {
+ graphics_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
+ }
+ if (graphics_image_table.Synchornize(maxwell3d.regs.tic.Address(), tic_limit)) {
+ graphics_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
}
+}
- void Unregister(TSurface surface) {
- if (guard_render_targets && surface->IsProtected()) {
- return;
- }
- if (!guard_render_targets && surface->IsRenderTarget()) {
- ManageRenderTargetUnregister(surface);
- }
- UnmarkMemory(surface);
- if (surface->IsSyncPending()) {
- marked_for_unregister.remove(surface);
- surface->SetSyncPending(false);
- }
- UnregisterInnerCache(surface);
- surface->MarkAsRegistered(false);
- ReserveSurface(surface->GetSurfaceParams(), surface);
+template <class P>
+void TextureCache<P>::SynchronizeComputeDescriptors() {
+ const bool linked_tsc = kepler_compute.launch_description.linked_tsc;
+ const u32 tic_limit = kepler_compute.regs.tic.limit;
+ const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute.regs.tsc.limit;
+ const GPUVAddr tsc_gpu_addr = kepler_compute.regs.tsc.Address();
+ if (compute_sampler_table.Synchornize(tsc_gpu_addr, tsc_limit)) {
+ compute_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID);
+ }
+ if (compute_image_table.Synchornize(kepler_compute.regs.tic.Address(), tic_limit)) {
+ compute_image_view_ids.resize(tic_limit + 1, CORRUPT_ID);
}
+}
- TSurface GetUncachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) {
- if (const auto surface = TryGetReservedSurface(params); surface) {
- surface->SetGpuAddr(gpu_addr);
- return surface;
- }
- // No reserved surface available, create a new one and reserve it
- auto new_surface{CreateSurface(gpu_addr, params)};
- return new_surface;
+template <class P>
+void TextureCache<P>::UpdateRenderTargets(bool is_clear) {
+ using namespace VideoCommon::Dirty;
+ auto& flags = maxwell3d.dirty.flags;
+ if (!flags[Dirty::RenderTargets]) {
+ return;
}
+ flags[Dirty::RenderTargets] = false;
- Core::System& system;
- const bool is_astc_supported;
+ // Render target control is used on all render targets, so force look ups when this one is up
+ const bool force = flags[Dirty::RenderTargetControl];
+ flags[Dirty::RenderTargetControl] = false;
-private:
- enum class RecycleStrategy : u32 {
- Ignore = 0,
- Flush = 1,
- BufferCopy = 3,
- };
+ for (size_t index = 0; index < NUM_RT; ++index) {
+ ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index];
+ if (flags[Dirty::ColorBuffer0 + index] || force) {
+ flags[Dirty::ColorBuffer0 + index] = false;
+ BindRenderTarget(&color_buffer_id, FindColorBuffer(index, is_clear));
+ }
+ PrepareImageView(color_buffer_id, true, is_clear && IsFullClear(color_buffer_id));
+ }
+ if (flags[Dirty::ZetaBuffer] || force) {
+ flags[Dirty::ZetaBuffer] = false;
+ BindRenderTarget(&render_targets.depth_buffer_id, FindDepthBuffer(is_clear));
+ }
+ const ImageViewId depth_buffer_id = render_targets.depth_buffer_id;
+ PrepareImageView(depth_buffer_id, true, is_clear && IsFullClear(depth_buffer_id));
- enum class DeductionType : u32 {
- DeductionComplete,
- DeductionIncomplete,
- DeductionFailed,
+ for (size_t index = 0; index < NUM_RT; ++index) {
+ render_targets.draw_buffers[index] = static_cast<u8>(maxwell3d.regs.rt_control.Map(index));
+ }
+ render_targets.size = Extent2D{
+ maxwell3d.regs.render_area.width,
+ maxwell3d.regs.render_area.height,
};
+}
- struct Deduction {
- DeductionType type{DeductionType::DeductionFailed};
- TSurface surface{};
+template <class P>
+typename P::Framebuffer* TextureCache<P>::GetFramebuffer() {
+ return &slot_framebuffers[GetFramebufferId(render_targets)];
+}
- bool Failed() const {
- return type == DeductionType::DeductionFailed;
- }
+template <class P>
+void TextureCache<P>::FillImageViews(DescriptorTable<TICEntry>& table,
+ std::span<ImageViewId> cached_image_view_ids,
+ std::span<const u32> indices,
+ std::span<ImageViewId> image_view_ids) {
+ ASSERT(indices.size() <= image_view_ids.size());
+ do {
+ has_deleted_images = false;
+ std::ranges::transform(indices, image_view_ids.begin(), [&](u32 index) {
+ return VisitImageView(table, cached_image_view_ids, index);
+ });
+ } while (has_deleted_images);
+}
- bool Incomplete() const {
- return type == DeductionType::DeductionIncomplete;
- }
+template <class P>
+ImageViewId TextureCache<P>::VisitImageView(DescriptorTable<TICEntry>& table,
+ std::span<ImageViewId> cached_image_view_ids,
+ u32 index) {
+ if (index > table.Limit()) {
+ LOG_ERROR(HW_GPU, "Invalid image view index={}", index);
+ return NULL_IMAGE_VIEW_ID;
+ }
+ const auto [descriptor, is_new] = table.Read(index);
+ ImageViewId& image_view_id = cached_image_view_ids[index];
+ if (is_new) {
+ image_view_id = FindImageView(descriptor);
+ }
+ if (image_view_id != NULL_IMAGE_VIEW_ID) {
+ PrepareImageView(image_view_id, false, false);
+ }
+ return image_view_id;
+}
- bool IsDepth() const {
- return surface->GetSurfaceParams().IsPixelFormatZeta();
- }
- };
+template <class P>
+FramebufferId TextureCache<P>::GetFramebufferId(const RenderTargets& key) {
+ const auto [pair, is_new] = framebuffers.try_emplace(key);
+ FramebufferId& framebuffer_id = pair->second;
+ if (!is_new) {
+ return framebuffer_id;
+ }
+ std::array<ImageView*, NUM_RT> color_buffers;
+ std::ranges::transform(key.color_buffer_ids, color_buffers.begin(),
+ [this](ImageViewId id) { return id ? &slot_image_views[id] : nullptr; });
+ ImageView* const depth_buffer =
+ key.depth_buffer_id ? &slot_image_views[key.depth_buffer_id] : nullptr;
+ framebuffer_id = slot_framebuffers.insert(runtime, color_buffers, depth_buffer, key);
+ return framebuffer_id;
+}
- /**
- * Takes care of selecting a proper strategy to deal with a texture recycle.
- *
- * @param overlaps The overlapping surfaces registered in the cache.
- * @param params The parameters on the new surface.
- * @param gpu_addr The starting address of the new surface.
- * @param untopological Indicates to the recycler that the texture has no way
- * to match the overlaps due to topological reasons.
- **/
- RecycleStrategy PickStrategy(VectorSurface& overlaps, const SurfaceParams& params,
- const GPUVAddr gpu_addr, const MatchTopologyResult untopological) {
- if (Settings::IsGPULevelExtreme()) {
- return RecycleStrategy::Flush;
- }
- // 3D Textures decision
- if (params.target == SurfaceTarget::Texture3D) {
- return RecycleStrategy::Flush;
- }
- for (const auto& s : overlaps) {
- const auto& s_params = s->GetSurfaceParams();
- if (s_params.target == SurfaceTarget::Texture3D) {
- return RecycleStrategy::Flush;
- }
- }
- // Untopological decision
- if (untopological == MatchTopologyResult::CompressUnmatch) {
- return RecycleStrategy::Flush;
- }
- if (untopological == MatchTopologyResult::FullMatch && !params.is_tiled) {
- return RecycleStrategy::Flush;
- }
- return RecycleStrategy::Ignore;
- }
-
- /**
- * Used to decide what to do with textures we can't resolve in the cache It has 2 implemented
- * strategies: Ignore and Flush.
- *
- * - Ignore: Just unregisters all the overlaps and loads the new texture.
- * - Flush: Flushes all the overlaps into memory and loads the new surface from that data.
- *
- * @param overlaps The overlapping surfaces registered in the cache.
- * @param params The parameters for the new surface.
- * @param gpu_addr The starting address of the new surface.
- * @param preserve_contents Indicates that the new surface should be loaded from memory or left
- * blank.
- * @param untopological Indicates to the recycler that the texture has no way to match the
- * overlaps due to topological reasons.
- **/
- std::pair<TSurface, TView> RecycleSurface(VectorSurface& overlaps, const SurfaceParams& params,
- const GPUVAddr gpu_addr, const bool preserve_contents,
- const MatchTopologyResult untopological) {
- const bool do_load = preserve_contents && Settings::IsGPULevelExtreme();
- for (auto& surface : overlaps) {
- Unregister(surface);
- }
- switch (PickStrategy(overlaps, params, gpu_addr, untopological)) {
- case RecycleStrategy::Ignore: {
- return InitializeSurface(gpu_addr, params, do_load);
- }
- case RecycleStrategy::Flush: {
- std::sort(overlaps.begin(), overlaps.end(),
- [](const TSurface& a, const TSurface& b) -> bool {
- return a->GetModificationTick() < b->GetModificationTick();
- });
- for (auto& surface : overlaps) {
- FlushSurface(surface);
- }
- return InitializeSurface(gpu_addr, params, preserve_contents);
+template <class P>
+void TextureCache<P>::WriteMemory(VAddr cpu_addr, size_t size) {
+ ForEachImageInRegion(cpu_addr, size, [this](ImageId image_id, Image& image) {
+ if (True(image.flags & ImageFlagBits::CpuModified)) {
+ return;
}
- case RecycleStrategy::BufferCopy: {
- auto new_surface = GetUncachedSurface(gpu_addr, params);
- BufferCopy(overlaps[0], new_surface);
- return {new_surface, new_surface->GetMainView()};
+ image.flags |= ImageFlagBits::CpuModified;
+ UntrackImage(image);
+ });
+}
+
+template <class P>
+void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) {
+ std::vector<ImageId> images;
+ ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) {
+ // Skip images that were not modified from the GPU
+ if (False(image.flags & ImageFlagBits::GpuModified)) {
+ return;
}
- default: {
- UNIMPLEMENTED_MSG("Unimplemented Texture Cache Recycling Strategy!");
- return InitializeSurface(gpu_addr, params, do_load);
+ // Skip images that .are. modified from the CPU
+ // We don't want to write sensitive data from the guest
+ if (True(image.flags & ImageFlagBits::CpuModified)) {
+ return;
}
+ if (image.info.num_samples > 1) {
+ LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
+ return;
}
+ image.flags &= ~ImageFlagBits::GpuModified;
+ images.push_back(image_id);
+ });
+ if (images.empty()) {
+ return;
}
+ std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) {
+ return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick;
+ });
+ for (const ImageId image_id : images) {
+ Image& image = slot_images[image_id];
+ auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
+ const auto copies = FullDownloadCopies(image.info);
+ image.DownloadMemory(map, copies);
+ runtime.Finish();
+ SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span);
+ }
+}
- /**
- * Takes a single surface and recreates into another that may differ in
- * format, target or width alignment.
- *
- * @param current_surface The registered surface in the cache which we want to convert.
- * @param params The new surface params which we'll use to recreate the surface.
- * @param is_render Whether or not the surface is a render target.
- **/
- std::pair<TSurface, TView> RebuildSurface(TSurface current_surface, const SurfaceParams& params,
- bool is_render) {
- const auto gpu_addr = current_surface->GetGpuAddr();
- const auto& cr_params = current_surface->GetSurfaceParams();
- TSurface new_surface;
- if (cr_params.pixel_format != params.pixel_format && !is_render &&
- GetSiblingFormat(cr_params.pixel_format) == params.pixel_format) {
- SurfaceParams new_params = params;
- new_params.pixel_format = cr_params.pixel_format;
- new_params.type = cr_params.type;
- new_surface = GetUncachedSurface(gpu_addr, new_params);
- } else {
- new_surface = GetUncachedSurface(gpu_addr, params);
- }
- const SurfaceParams& final_params = new_surface->GetSurfaceParams();
- if (cr_params.type != final_params.type) {
- if (Settings::IsGPULevelExtreme()) {
- BufferCopy(current_surface, new_surface);
- }
- } else {
- std::vector<CopyParams> bricks = current_surface->BreakDown(final_params);
- for (auto& brick : bricks) {
- TryCopyImage(current_surface, new_surface, brick);
- }
- }
- Unregister(current_surface);
- Register(new_surface);
- new_surface->MarkAsModified(current_surface->IsModified(), Tick());
- return {new_surface, new_surface->GetMainView()};
- }
-
- /**
- * Takes a single surface and checks with the new surface's params if it's an exact
- * match, we return the main view of the registered surface. If its formats don't
- * match, we rebuild the surface. We call this last method a `Mirage`. If formats
- * match but the targets don't, we create an overview View of the registered surface.
- *
- * @param current_surface The registered surface in the cache which we want to convert.
- * @param params The new surface params which we want to check.
- * @param is_render Whether or not the surface is a render target.
- **/
- std::pair<TSurface, TView> ManageStructuralMatch(TSurface current_surface,
- const SurfaceParams& params, bool is_render) {
- const bool is_mirage = !current_surface->MatchFormat(params.pixel_format);
- const bool matches_target = current_surface->MatchTarget(params.target);
- const auto match_check = [&]() -> std::pair<TSurface, TView> {
- if (matches_target) {
- return {current_surface, current_surface->GetMainView()};
- }
- return {current_surface, current_surface->EmplaceOverview(params)};
- };
- if (!is_mirage) {
- return match_check();
- }
- if (!is_render && GetSiblingFormat(current_surface->GetFormat()) == params.pixel_format) {
- return match_check();
- }
- return RebuildSurface(current_surface, params, is_render);
- }
-
- /**
- * Unlike RebuildSurface where we know whether or not registered surfaces match the candidate
- * in some way, we have no guarantees here. We try to see if the overlaps are sublayers/mipmaps
- * of the new surface, if they all match we end up recreating a surface for them,
- * else we return nothing.
- *
- * @param overlaps The overlapping surfaces registered in the cache.
- * @param params The parameters on the new surface.
- * @param gpu_addr The starting address of the new surface.
- **/
- std::optional<std::pair<TSurface, TView>> TryReconstructSurface(VectorSurface& overlaps,
- const SurfaceParams& params,
- GPUVAddr gpu_addr) {
- if (params.target == SurfaceTarget::Texture3D) {
- return std::nullopt;
- }
- const auto test_modified = [](TSurface& surface) { return surface->IsModified(); };
- TSurface new_surface = GetUncachedSurface(gpu_addr, params);
+template <class P>
+void TextureCache<P>::UnmapMemory(VAddr cpu_addr, size_t size) {
+ std::vector<ImageId> deleted_images;
+ ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); });
+ for (const ImageId id : deleted_images) {
+ Image& image = slot_images[id];
+ if (True(image.flags & ImageFlagBits::Tracked)) {
+ UntrackImage(image);
+ }
+ UnregisterImage(id);
+ DeleteImage(id);
+ }
+}
- if (std::none_of(overlaps.begin(), overlaps.end(), test_modified)) {
- LoadSurface(new_surface);
- for (const auto& surface : overlaps) {
- Unregister(surface);
- }
- Register(new_surface);
- return {{new_surface, new_surface->GetMainView()}};
- }
+template <class P>
+void TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
+ const Tegra::Engines::Fermi2D::Surface& src,
+ const Tegra::Engines::Fermi2D::Config& copy) {
+ const BlitImages images = GetBlitImages(dst, src);
+ const ImageId dst_id = images.dst_id;
+ const ImageId src_id = images.src_id;
+ PrepareImage(src_id, false, false);
+ PrepareImage(dst_id, true, false);
+
+ ImageBase& dst_image = slot_images[dst_id];
+ const ImageBase& src_image = slot_images[src_id];
+
+ // TODO: Deduplicate
+ const std::optional dst_base = dst_image.TryFindBase(dst.Address());
+ const SubresourceRange dst_range{.base = dst_base.value(), .extent = {1, 1}};
+ const ImageViewInfo dst_view_info(ImageViewType::e2D, images.dst_format, dst_range);
+ const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info);
+ const auto [src_samples_x, src_samples_y] = SamplesLog2(src_image.info.num_samples);
+ const std::array src_region{
+ Offset2D{.x = copy.src_x0 >> src_samples_x, .y = copy.src_y0 >> src_samples_y},
+ Offset2D{.x = copy.src_x1 >> src_samples_x, .y = copy.src_y1 >> src_samples_y},
+ };
- std::size_t passed_tests = 0;
- for (auto& surface : overlaps) {
- const SurfaceParams& src_params = surface->GetSurfaceParams();
- const auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())};
- if (!mipmap_layer) {
- continue;
- }
- const auto [base_layer, base_mipmap] = *mipmap_layer;
- if (new_surface->GetMipmapSize(base_mipmap) != surface->GetMipmapSize(0)) {
- continue;
- }
- ++passed_tests;
-
- // Copy all mipmaps and layers
- const u32 block_width = params.GetDefaultBlockWidth();
- const u32 block_height = params.GetDefaultBlockHeight();
- for (u32 mipmap = base_mipmap; mipmap < base_mipmap + src_params.num_levels; ++mipmap) {
- const u32 width = SurfaceParams::IntersectWidth(src_params, params, 0, mipmap);
- const u32 height = SurfaceParams::IntersectHeight(src_params, params, 0, mipmap);
- if (width < block_width || height < block_height) {
- // Current APIs forbid copying small compressed textures, avoid errors
- break;
- }
- const CopyParams copy_params(0, 0, 0, 0, 0, base_layer, 0, mipmap, width, height,
- src_params.depth);
- TryCopyImage(surface, new_surface, copy_params);
- }
- }
- if (passed_tests == 0) {
- return std::nullopt;
- }
- if (Settings::IsGPULevelExtreme() && passed_tests != overlaps.size()) {
- // In Accurate GPU all tests should pass, else we recycle
- return std::nullopt;
- }
+ const std::optional src_base = src_image.TryFindBase(src.Address());
+ const SubresourceRange src_range{.base = src_base.value(), .extent = {1, 1}};
+ const ImageViewInfo src_view_info(ImageViewType::e2D, images.src_format, src_range);
+ const auto [src_framebuffer_id, src_view_id] = RenderTargetFromImage(src_id, src_view_info);
+ const auto [dst_samples_x, dst_samples_y] = SamplesLog2(dst_image.info.num_samples);
+ const std::array dst_region{
+ Offset2D{.x = copy.dst_x0 >> dst_samples_x, .y = copy.dst_y0 >> dst_samples_y},
+ Offset2D{.x = copy.dst_x1 >> dst_samples_x, .y = copy.dst_y1 >> dst_samples_y},
+ };
- const bool modified = std::any_of(overlaps.begin(), overlaps.end(), test_modified);
- for (const auto& surface : overlaps) {
- Unregister(surface);
- }
+ // Always call this after src_framebuffer_id was queried, as the address might be invalidated.
+ Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_framebuffer_id];
+ if constexpr (FRAMEBUFFER_BLITS) {
+ // OpenGL blits from framebuffers, not images
+ Framebuffer* const src_framebuffer = &slot_framebuffers[src_framebuffer_id];
+ runtime.BlitFramebuffer(dst_framebuffer, src_framebuffer, dst_region, src_region,
+ copy.filter, copy.operation);
+ } else {
+ // Vulkan can blit images, but it lacks format reinterpretations
+ // Provide a framebuffer in case it's necessary
+ ImageView& dst_view = slot_image_views[dst_view_id];
+ ImageView& src_view = slot_image_views[src_view_id];
+ runtime.BlitImage(dst_framebuffer, dst_view, src_view, dst_region, src_region, copy.filter,
+ copy.operation);
+ }
+}
- new_surface->MarkAsModified(modified, Tick());
- Register(new_surface);
- return {{new_surface, new_surface->GetMainView()}};
- }
-
- /**
- * Takes care of managing 3D textures and its slices. Does HLE methods for reconstructing the 3D
- * textures within the GPU if possible. Falls back to LLE when it isn't possible to use any of
- * the HLE methods.
- *
- * @param overlaps The overlapping surfaces registered in the cache.
- * @param params The parameters on the new surface.
- * @param gpu_addr The starting address of the new surface.
- * @param cpu_addr The starting address of the new surface on physical memory.
- * @param preserve_contents Indicates that the new surface should be loaded from memory or
- * left blank.
- */
- std::optional<std::pair<TSurface, TView>> Manage3DSurfaces(VectorSurface& overlaps,
- const SurfaceParams& params,
- GPUVAddr gpu_addr, VAddr cpu_addr,
- bool preserve_contents) {
- if (params.target != SurfaceTarget::Texture3D) {
- for (const auto& surface : overlaps) {
- if (!surface->MatchTarget(params.target)) {
- if (overlaps.size() == 1 && surface->GetCpuAddr() == cpu_addr) {
- if (Settings::IsGPULevelExtreme()) {
- return std::nullopt;
- }
- Unregister(surface);
- return InitializeSurface(gpu_addr, params, preserve_contents);
- }
- return std::nullopt;
- }
- if (surface->GetCpuAddr() != cpu_addr) {
- continue;
- }
- if (surface->MatchesStructure(params) == MatchStructureResult::FullMatch) {
- return std::make_pair(surface, surface->GetMainView());
- }
- }
- return InitializeSurface(gpu_addr, params, preserve_contents);
- }
+template <class P>
+void TextureCache<P>::InvalidateColorBuffer(size_t index) {
+ ImageViewId& color_buffer_id = render_targets.color_buffer_ids[index];
+ color_buffer_id = FindColorBuffer(index, false);
+ if (!color_buffer_id) {
+ LOG_ERROR(HW_GPU, "Invalidating invalid color buffer in index={}", index);
+ return;
+ }
+ // When invalidating a color buffer, the old contents are no longer relevant
+ ImageView& color_buffer = slot_image_views[color_buffer_id];
+ Image& image = slot_images[color_buffer.image_id];
+ image.flags &= ~ImageFlagBits::CpuModified;
+ image.flags &= ~ImageFlagBits::GpuModified;
- if (params.num_levels > 1) {
- // We can't handle mipmaps in 3D textures yet, better fallback to LLE approach
- return std::nullopt;
- }
+ runtime.InvalidateColorBuffer(color_buffer, index);
+}
- if (overlaps.size() == 1) {
- const auto& surface = overlaps[0];
- const SurfaceParams& overlap_params = surface->GetSurfaceParams();
- // Don't attempt to render to textures with more than one level for now
- // The texture has to be to the right or the sample address if we want to render to it
- if (overlap_params.num_levels == 1 && cpu_addr >= surface->GetCpuAddr()) {
- const u32 offset = static_cast<u32>(cpu_addr - surface->GetCpuAddr());
- const u32 slice = std::get<2>(params.GetBlockOffsetXYZ(offset));
- if (slice < overlap_params.depth) {
- auto view = surface->Emplace3DView(slice, params.depth, 0, 1);
- return std::make_pair(std::move(surface), std::move(view));
- }
- }
- }
+template <class P>
+void TextureCache<P>::InvalidateDepthBuffer() {
+ ImageViewId& depth_buffer_id = render_targets.depth_buffer_id;
+ depth_buffer_id = FindDepthBuffer(false);
+ if (!depth_buffer_id) {
+ LOG_ERROR(HW_GPU, "Invalidating invalid depth buffer");
+ return;
+ }
+ // When invalidating the depth buffer, the old contents are no longer relevant
+ ImageBase& image = slot_images[slot_image_views[depth_buffer_id].image_id];
+ image.flags &= ~ImageFlagBits::CpuModified;
+ image.flags &= ~ImageFlagBits::GpuModified;
- TSurface new_surface = GetUncachedSurface(gpu_addr, params);
- bool modified = false;
+ ImageView& depth_buffer = slot_image_views[depth_buffer_id];
+ runtime.InvalidateDepthBuffer(depth_buffer);
+}
- for (auto& surface : overlaps) {
- const SurfaceParams& src_params = surface->GetSurfaceParams();
- if (src_params.target != SurfaceTarget::Texture2D ||
- src_params.height != params.height ||
- src_params.block_depth != params.block_depth ||
- src_params.block_height != params.block_height) {
- return std::nullopt;
- }
- modified |= surface->IsModified();
-
- const u32 offset = static_cast<u32>(surface->GetCpuAddr() - cpu_addr);
- const u32 slice = std::get<2>(params.GetBlockOffsetXYZ(offset));
- const u32 width = params.width;
- const u32 height = params.height;
- const CopyParams copy_params(0, 0, 0, 0, 0, slice, 0, 0, width, height, 1);
- TryCopyImage(surface, new_surface, copy_params);
+template <class P>
+typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_addr) {
+ // TODO: Properly implement this
+ const auto it = page_table.find(cpu_addr >> PAGE_BITS);
+ if (it == page_table.end()) {
+ return nullptr;
+ }
+ const auto& image_ids = it->second;
+ for (const ImageId image_id : image_ids) {
+ const ImageBase& image = slot_images[image_id];
+ if (image.cpu_addr != cpu_addr) {
+ continue;
}
- for (const auto& surface : overlaps) {
- Unregister(surface);
+ if (image.image_view_ids.empty()) {
+ continue;
}
- new_surface->MarkAsModified(modified, Tick());
- Register(new_surface);
-
- TView view = new_surface->GetMainView();
- return std::make_pair(std::move(new_surface), std::move(view));
- }
-
- /**
- * Gets the starting address and parameters of a candidate surface and tries
- * to find a matching surface within the cache. This is done in 3 big steps:
- *
- * 1. Check the 1st Level Cache in order to find an exact match, if we fail, we move to step 2.
- *
- * 2. Check if there are any overlaps at all, if there are none, we just load the texture from
- * memory else we move to step 3.
- *
- * 3. Consists of figuring out the relationship between the candidate texture and the
- * overlaps. We divide the scenarios depending if there's 1 or many overlaps. If
- * there's many, we just try to reconstruct a new surface out of them based on the
- * candidate's parameters, if we fail, we recycle. When there's only 1 overlap then we
- * have to check if the candidate is a view (layer/mipmap) of the overlap or if the
- * registered surface is a mipmap/layer of the candidate. In this last case we reconstruct
- * a new surface.
- *
- * @param gpu_addr The starting address of the candidate surface.
- * @param params The parameters on the candidate surface.
- * @param preserve_contents Indicates that the new surface should be loaded from memory or
- * left blank.
- * @param is_render Whether or not the surface is a render target.
- **/
- std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const VAddr cpu_addr,
- const SurfaceParams& params, bool preserve_contents,
- bool is_render) {
- // Step 1
- // Check Level 1 Cache for a fast structural match. If candidate surface
- // matches at certain level we are pretty much done.
- if (const auto iter = l1_cache.find(cpu_addr); iter != l1_cache.end()) {
- TSurface& current_surface = iter->second;
- const auto topological_result = current_surface->MatchesTopology(params);
- if (topological_result != MatchTopologyResult::FullMatch) {
- VectorSurface overlaps{current_surface};
- return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
- topological_result);
- }
+ return &slot_image_views[image.image_view_ids.at(0)];
+ }
+ return nullptr;
+}
- const auto struct_result = current_surface->MatchesStructure(params);
- if (struct_result != MatchStructureResult::None) {
- const auto& old_params = current_surface->GetSurfaceParams();
- const bool not_3d = params.target != SurfaceTarget::Texture3D &&
- old_params.target != SurfaceTarget::Texture3D;
- if (not_3d || current_surface->MatchTarget(params.target)) {
- if (struct_result == MatchStructureResult::FullMatch) {
- return ManageStructuralMatch(current_surface, params, is_render);
- } else {
- return RebuildSurface(current_surface, params, is_render);
- }
- }
- }
- }
+template <class P>
+bool TextureCache<P>::HasUncommittedFlushes() const noexcept {
+ return !uncommitted_downloads.empty();
+}
- // Step 2
- // Obtain all possible overlaps in the memory region
- const std::size_t candidate_size = params.GetGuestSizeInBytes();
- auto overlaps{GetSurfacesInRegion(cpu_addr, candidate_size)};
+template <class P>
+bool TextureCache<P>::ShouldWaitAsyncFlushes() const noexcept {
+ return !committed_downloads.empty() && !committed_downloads.front().empty();
+}
- // If none are found, we are done. we just load the surface and create it.
- if (overlaps.empty()) {
- return InitializeSurface(gpu_addr, params, preserve_contents);
- }
+template <class P>
+void TextureCache<P>::CommitAsyncFlushes() {
+ // This is intentionally passing the value by copy
+ committed_downloads.push(uncommitted_downloads);
+ uncommitted_downloads.clear();
+}
- // Step 3
- // Now we need to figure the relationship between the texture and its overlaps
- // we do a topological test to ensure we can find some relationship. If it fails
- // immediately recycle the texture
- for (const auto& surface : overlaps) {
- const auto topological_result = surface->MatchesTopology(params);
- if (topological_result != MatchTopologyResult::FullMatch) {
- return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
- topological_result);
- }
- }
+template <class P>
+void TextureCache<P>::PopAsyncFlushes() {
+ if (committed_downloads.empty()) {
+ return;
+ }
+ const std::span<const ImageId> download_ids = committed_downloads.front();
+ if (download_ids.empty()) {
+ committed_downloads.pop();
+ return;
+ }
+ size_t total_size_bytes = 0;
+ for (const ImageId image_id : download_ids) {
+ total_size_bytes += slot_images[image_id].unswizzled_size_bytes;
+ }
+ auto download_map = runtime.DownloadStagingBuffer(total_size_bytes);
+ const size_t original_offset = download_map.offset;
+ for (const ImageId image_id : download_ids) {
+ Image& image = slot_images[image_id];
+ const auto copies = FullDownloadCopies(image.info);
+ image.DownloadMemory(download_map, copies);
+ download_map.offset += image.unswizzled_size_bytes;
+ }
+ // Wait for downloads to finish
+ runtime.Finish();
+
+ download_map.offset = original_offset;
+ std::span<u8> download_span = download_map.mapped_span;
+ for (const ImageId image_id : download_ids) {
+ const ImageBase& image = slot_images[image_id];
+ const auto copies = FullDownloadCopies(image.info);
+ SwizzleImage(gpu_memory, image.gpu_addr, image.info, copies, download_span);
+ download_map.offset += image.unswizzled_size_bytes;
+ download_span = download_span.subspan(image.unswizzled_size_bytes);
+ }
+ committed_downloads.pop();
+}
- // Manage 3D textures
- if (params.block_depth > 0) {
- auto surface =
- Manage3DSurfaces(overlaps, params, gpu_addr, cpu_addr, preserve_contents);
- if (surface) {
- return *surface;
- }
+template <class P>
+bool TextureCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
+ bool is_modified = false;
+ ForEachImageInRegion(addr, size, [&is_modified](ImageId, ImageBase& image) {
+ if (False(image.flags & ImageFlagBits::GpuModified)) {
+ return false;
}
+ is_modified = true;
+ return true;
+ });
+ return is_modified;
+}
- // Split cases between 1 overlap or many.
- if (overlaps.size() == 1) {
- TSurface current_surface = overlaps[0];
- // First check if the surface is within the overlap. If not, it means
- // two things either the candidate surface is a supertexture of the overlap
- // or they don't match in any known way.
- if (!current_surface->IsInside(gpu_addr, gpu_addr + candidate_size)) {
- const std::optional view = TryReconstructSurface(overlaps, params, gpu_addr);
- if (view) {
- return *view;
- }
- return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
- MatchTopologyResult::FullMatch);
- }
- // Now we check if the candidate is a mipmap/layer of the overlap
- std::optional<TView> view =
- current_surface->EmplaceView(params, gpu_addr, candidate_size);
- if (view) {
- const bool is_mirage = !current_surface->MatchFormat(params.pixel_format);
- if (is_mirage) {
- // On a mirage view, we need to recreate the surface under this new view
- // and then obtain a view again.
- SurfaceParams new_params = current_surface->GetSurfaceParams();
- const u32 wh = SurfaceParams::ConvertWidth(
- new_params.width, new_params.pixel_format, params.pixel_format);
- const u32 hh = SurfaceParams::ConvertHeight(
- new_params.height, new_params.pixel_format, params.pixel_format);
- new_params.width = wh;
- new_params.height = hh;
- new_params.pixel_format = params.pixel_format;
- std::pair<TSurface, TView> pair =
- RebuildSurface(current_surface, new_params, is_render);
- std::optional<TView> mirage_view =
- pair.first->EmplaceView(params, gpu_addr, candidate_size);
- if (mirage_view)
- return {pair.first, *mirage_view};
- return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
- MatchTopologyResult::FullMatch);
- }
- return {current_surface, *view};
- }
- } else {
- // If there are many overlaps, odds are they are subtextures of the candidate
- // surface. We try to construct a new surface based on the candidate parameters,
- // using the overlaps. If a single overlap fails, this will fail.
- std::optional<std::pair<TSurface, TView>> view =
- TryReconstructSurface(overlaps, params, gpu_addr);
- if (view) {
- return *view;
- }
- }
- // We failed all the tests, recycle the overlaps into a new texture.
- return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
- MatchTopologyResult::FullMatch);
- }
-
- /**
- * Gets the starting address and parameters of a candidate surface and tries to find a
- * matching surface within the cache that's similar to it. If there are many textures
- * or the texture found if entirely incompatible, it will fail. If no texture is found, the
- * blit will be unsuccessful.
- *
- * @param gpu_addr The starting address of the candidate surface.
- * @param params The parameters on the candidate surface.
- **/
- Deduction DeduceSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) {
- const std::optional<VAddr> cpu_addr =
- system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
-
- if (!cpu_addr) {
- Deduction result{};
- result.type = DeductionType::DeductionFailed;
- return result;
- }
+template <class P>
+void TextureCache<P>::RefreshContents(Image& image) {
+ if (False(image.flags & ImageFlagBits::CpuModified)) {
+ // Only upload modified images
+ return;
+ }
+ image.flags &= ~ImageFlagBits::CpuModified;
+ TrackImage(image);
- if (const auto iter = l1_cache.find(*cpu_addr); iter != l1_cache.end()) {
- TSurface& current_surface = iter->second;
- const auto topological_result = current_surface->MatchesTopology(params);
- if (topological_result != MatchTopologyResult::FullMatch) {
- Deduction result{};
- result.type = DeductionType::DeductionFailed;
- return result;
- }
- const auto struct_result = current_surface->MatchesStructure(params);
- if (struct_result != MatchStructureResult::None &&
- current_surface->MatchTarget(params.target)) {
- Deduction result{};
- result.type = DeductionType::DeductionComplete;
- result.surface = current_surface;
- return result;
- }
- }
+ if (image.info.num_samples > 1) {
+ LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
+ return;
+ }
+ auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image));
+ UploadImageContents(image, staging);
+ runtime.InsertUploadMemoryBarrier();
+}
- const std::size_t candidate_size = params.GetGuestSizeInBytes();
- auto overlaps{GetSurfacesInRegion(*cpu_addr, candidate_size)};
+template <class P>
+template <typename StagingBuffer>
+void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging) {
+ const std::span<u8> mapped_span = staging.mapped_span;
+ const GPUVAddr gpu_addr = image.gpu_addr;
+
+ if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
+ gpu_memory.ReadBlockUnsafe(gpu_addr, mapped_span.data(), mapped_span.size_bytes());
+ const auto uploads = FullUploadSwizzles(image.info);
+ runtime.AccelerateImageUpload(image, staging, uploads);
+ } else if (True(image.flags & ImageFlagBits::Converted)) {
+ std::vector<u8> unswizzled_data(image.unswizzled_size_bytes);
+ auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, unswizzled_data);
+ ConvertImage(unswizzled_data, image.info, mapped_span, copies);
+ image.UploadMemory(staging, copies);
+ } else if (image.info.type == ImageType::Buffer) {
+ const std::array copies{UploadBufferCopy(gpu_memory, gpu_addr, image, mapped_span)};
+ image.UploadMemory(staging, copies);
+ } else {
+ const auto copies = UnswizzleImage(gpu_memory, gpu_addr, image.info, mapped_span);
+ image.UploadMemory(staging, copies);
+ }
+}
- if (overlaps.empty()) {
- Deduction result{};
- result.type = DeductionType::DeductionIncomplete;
- return result;
- }
+template <class P>
+ImageViewId TextureCache<P>::FindImageView(const TICEntry& config) {
+ if (!IsValidAddress(gpu_memory, config)) {
+ return NULL_IMAGE_VIEW_ID;
+ }
+ const auto [pair, is_new] = image_views.try_emplace(config);
+ ImageViewId& image_view_id = pair->second;
+ if (is_new) {
+ image_view_id = CreateImageView(config);
+ }
+ return image_view_id;
+}
- if (overlaps.size() > 1) {
- Deduction result{};
- result.type = DeductionType::DeductionFailed;
- return result;
- } else {
- Deduction result{};
- result.type = DeductionType::DeductionComplete;
- result.surface = overlaps[0];
- return result;
- }
+template <class P>
+ImageViewId TextureCache<P>::CreateImageView(const TICEntry& config) {
+ const ImageInfo info(config);
+ const GPUVAddr image_gpu_addr = config.Address() - config.BaseLayer() * info.layer_stride;
+ const ImageId image_id = FindOrInsertImage(info, image_gpu_addr);
+ if (!image_id) {
+ return NULL_IMAGE_VIEW_ID;
}
+ ImageBase& image = slot_images[image_id];
+ const SubresourceBase base = image.TryFindBase(config.Address()).value();
+ ASSERT(base.level == 0);
+ const ImageViewInfo view_info(config, base.layer);
+ const ImageViewId image_view_id = FindOrEmplaceImageView(image_id, view_info);
+ ImageViewBase& image_view = slot_image_views[image_view_id];
+ image_view.flags |= ImageViewFlagBits::Strong;
+ image.flags |= ImageFlagBits::Strong;
+ return image_view_id;
+}
- /**
- * Gets a null surface based on a target texture.
- * @param target The target of the null surface.
- */
- TView GetNullSurface(SurfaceTarget target) {
- const u32 i_target = static_cast<u32>(target);
- if (const auto it = invalid_cache.find(i_target); it != invalid_cache.end()) {
- return it->second->GetMainView();
- }
- SurfaceParams params{};
- params.target = target;
- params.is_tiled = false;
- params.srgb_conversion = false;
- params.is_layered =
- target == SurfaceTarget::Texture1DArray || target == SurfaceTarget::Texture2DArray ||
- target == SurfaceTarget::TextureCubemap || target == SurfaceTarget::TextureCubeArray;
- params.block_width = 0;
- params.block_height = 0;
- params.block_depth = 0;
- params.tile_width_spacing = 1;
- params.width = 1;
- params.height = 1;
- params.depth = 1;
- if (target == SurfaceTarget::TextureCubemap || target == SurfaceTarget::TextureCubeArray) {
- params.depth = 6;
- }
- params.pitch = 4;
- params.num_levels = 1;
- params.emulated_levels = 1;
- params.pixel_format = VideoCore::Surface::PixelFormat::R8_UNORM;
- params.type = VideoCore::Surface::SurfaceType::ColorTexture;
- auto surface = CreateSurface(0ULL, params);
- invalid_memory.resize(surface->GetHostSizeInBytes(), 0U);
- surface->UploadTexture(invalid_memory);
- surface->MarkAsModified(false, Tick());
- invalid_cache.emplace(i_target, surface);
- return surface->GetMainView();
- }
-
- /**
- * Gets the a source and destination starting address and parameters,
- * and tries to deduce if they are supposed to be depth textures. If so, their
- * parameters are modified and fixed into so.
- *
- * @param src_params The parameters of the candidate surface.
- * @param dst_params The parameters of the destination surface.
- * @param src_gpu_addr The starting address of the candidate surface.
- * @param dst_gpu_addr The starting address of the destination surface.
- **/
- void DeduceBestBlit(SurfaceParams& src_params, SurfaceParams& dst_params,
- const GPUVAddr src_gpu_addr, const GPUVAddr dst_gpu_addr) {
- auto deduced_src = DeduceSurface(src_gpu_addr, src_params);
- auto deduced_dst = DeduceSurface(dst_gpu_addr, dst_params);
- if (deduced_src.Failed() || deduced_dst.Failed()) {
- return;
+template <class P>
+ImageId TextureCache<P>::FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
+ RelaxedOptions options) {
+ if (const ImageId image_id = FindImage(info, gpu_addr, options); image_id) {
+ return image_id;
+ }
+ return InsertImage(info, gpu_addr, options);
+}
+
+template <class P>
+ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
+ RelaxedOptions options) {
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ if (!cpu_addr) {
+ return ImageId{};
+ }
+ const bool broken_views = runtime.HasBrokenTextureViewFormats();
+ ImageId image_id;
+ const auto lambda = [&](ImageId existing_image_id, ImageBase& existing_image) {
+ if (info.type == ImageType::Linear || existing_image.info.type == ImageType::Linear) {
+ const bool strict_size = False(options & RelaxedOptions::Size) &&
+ True(existing_image.flags & ImageFlagBits::Strong);
+ const ImageInfo& existing = existing_image.info;
+ if (existing_image.gpu_addr == gpu_addr && existing.type == info.type &&
+ existing.pitch == info.pitch &&
+ IsPitchLinearSameSize(existing, info, strict_size) &&
+ IsViewCompatible(existing.format, info.format, broken_views)) {
+ image_id = existing_image_id;
+ return true;
+ }
+ } else if (IsSubresource(info, existing_image, gpu_addr, options, broken_views)) {
+ image_id = existing_image_id;
+ return true;
}
+ return false;
+ };
+ ForEachImageInRegion(*cpu_addr, CalculateGuestSizeInBytes(info), lambda);
+ return image_id;
+}
- const bool incomplete_src = deduced_src.Incomplete();
- const bool incomplete_dst = deduced_dst.Incomplete();
+template <class P>
+ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
+ RelaxedOptions options) {
+ const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
+ ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr);
+ const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr);
+ const Image& image = slot_images[image_id];
+ // Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different
+ const auto [it, is_new] = image_allocs_table.try_emplace(image.gpu_addr);
+ if (is_new) {
+ it->second = slot_image_allocs.insert();
+ }
+ slot_image_allocs[it->second].images.push_back(image_id);
+ return image_id;
+}
- if (incomplete_src && incomplete_dst) {
+template <class P>
+ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr) {
+ ImageInfo new_info = info;
+ const size_t size_bytes = CalculateGuestSizeInBytes(new_info);
+ const bool broken_views = runtime.HasBrokenTextureViewFormats();
+ std::vector<ImageId> overlap_ids;
+ std::vector<ImageId> left_aliased_ids;
+ std::vector<ImageId> right_aliased_ids;
+ ForEachImageInRegion(cpu_addr, size_bytes, [&](ImageId overlap_id, ImageBase& overlap) {
+ if (info.type != overlap.info.type) {
return;
}
-
- const bool any_incomplete = incomplete_src || incomplete_dst;
-
- if (!any_incomplete) {
- if (!(deduced_src.IsDepth() && deduced_dst.IsDepth())) {
- return;
- }
- } else {
- if (incomplete_src && !(deduced_dst.IsDepth())) {
- return;
- }
-
- if (incomplete_dst && !(deduced_src.IsDepth())) {
- return;
+ if (info.type == ImageType::Linear) {
+ if (info.pitch == overlap.info.pitch && gpu_addr == overlap.gpu_addr) {
+ // Alias linear images with the same pitch
+ left_aliased_ids.push_back(overlap_id);
}
+ return;
}
-
- const auto inherit_format = [](SurfaceParams& to, TSurface from) {
- const SurfaceParams& params = from->GetSurfaceParams();
- to.pixel_format = params.pixel_format;
- to.type = params.type;
- };
- // Now we got the cases where one or both is Depth and the other is not known
- if (!incomplete_src) {
- inherit_format(src_params, deduced_src.surface);
- } else {
- inherit_format(src_params, deduced_dst.surface);
+ static constexpr bool strict_size = true;
+ const std::optional<OverlapResult> solution =
+ ResolveOverlap(new_info, gpu_addr, cpu_addr, overlap, strict_size, broken_views);
+ if (solution) {
+ gpu_addr = solution->gpu_addr;
+ cpu_addr = solution->cpu_addr;
+ new_info.resources = solution->resources;
+ overlap_ids.push_back(overlap_id);
+ return;
}
- if (!incomplete_dst) {
- inherit_format(dst_params, deduced_dst.surface);
+ static constexpr auto options = RelaxedOptions::Size | RelaxedOptions::Format;
+ const ImageBase new_image_base(new_info, gpu_addr, cpu_addr);
+ if (IsSubresource(new_info, overlap, gpu_addr, options, broken_views)) {
+ left_aliased_ids.push_back(overlap_id);
+ } else if (IsSubresource(overlap.info, new_image_base, overlap.gpu_addr, options,
+ broken_views)) {
+ right_aliased_ids.push_back(overlap_id);
+ }
+ });
+ const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr);
+ Image& new_image = slot_images[new_image_id];
+
+ // TODO: Only upload what we need
+ RefreshContents(new_image);
+
+ for (const ImageId overlap_id : overlap_ids) {
+ Image& overlap = slot_images[overlap_id];
+ if (overlap.info.num_samples != new_image.info.num_samples) {
+ LOG_WARNING(HW_GPU, "Copying between images with different samples is not implemented");
} else {
- inherit_format(dst_params, deduced_src.surface);
+ const SubresourceBase base = new_image.TryFindBase(overlap.gpu_addr).value();
+ const auto copies = MakeShrinkImageCopies(new_info, overlap.info, base);
+ runtime.CopyImage(new_image, overlap, copies);
+ }
+ if (True(overlap.flags & ImageFlagBits::Tracked)) {
+ UntrackImage(overlap);
}
+ UnregisterImage(overlap_id);
+ DeleteImage(overlap_id);
+ }
+ ImageBase& new_image_base = new_image;
+ for (const ImageId aliased_id : right_aliased_ids) {
+ ImageBase& aliased = slot_images[aliased_id];
+ AddImageAlias(new_image_base, aliased, new_image_id, aliased_id);
}
+ for (const ImageId aliased_id : left_aliased_ids) {
+ ImageBase& aliased = slot_images[aliased_id];
+ AddImageAlias(aliased, new_image_base, aliased_id, new_image_id);
+ }
+ RegisterImage(new_image_id);
+ return new_image_id;
+}
- std::pair<TSurface, TView> InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params,
- bool preserve_contents) {
- auto new_surface{GetUncachedSurface(gpu_addr, params)};
- Register(new_surface);
- if (preserve_contents) {
- LoadSurface(new_surface);
- }
- return {new_surface, new_surface->GetMainView()};
+template <class P>
+typename TextureCache<P>::BlitImages TextureCache<P>::GetBlitImages(
+ const Tegra::Engines::Fermi2D::Surface& dst, const Tegra::Engines::Fermi2D::Surface& src) {
+ static constexpr auto FIND_OPTIONS = RelaxedOptions::Format | RelaxedOptions::Samples;
+ const GPUVAddr dst_addr = dst.Address();
+ const GPUVAddr src_addr = src.Address();
+ ImageInfo dst_info(dst);
+ ImageInfo src_info(src);
+ ImageId dst_id;
+ ImageId src_id;
+ do {
+ has_deleted_images = false;
+ dst_id = FindImage(dst_info, dst_addr, FIND_OPTIONS);
+ src_id = FindImage(src_info, src_addr, FIND_OPTIONS);
+ const ImageBase* const dst_image = dst_id ? &slot_images[dst_id] : nullptr;
+ const ImageBase* const src_image = src_id ? &slot_images[src_id] : nullptr;
+ DeduceBlitImages(dst_info, src_info, dst_image, src_image);
+ if (GetFormatType(dst_info.format) != GetFormatType(src_info.format)) {
+ continue;
+ }
+ if (!dst_id) {
+ dst_id = InsertImage(dst_info, dst_addr, RelaxedOptions{});
+ }
+ if (!src_id) {
+ src_id = InsertImage(src_info, src_addr, RelaxedOptions{});
+ }
+ } while (has_deleted_images);
+ return BlitImages{
+ .dst_id = dst_id,
+ .src_id = src_id,
+ .dst_format = dst_info.format,
+ .src_format = src_info.format,
+ };
+}
+
+template <class P>
+SamplerId TextureCache<P>::FindSampler(const TSCEntry& config) {
+ if (std::ranges::all_of(config.raw, [](u64 value) { return value == 0; })) {
+ return NULL_SAMPLER_ID;
}
+ const auto [pair, is_new] = samplers.try_emplace(config);
+ if (is_new) {
+ pair->second = slot_samplers.insert(runtime, config);
+ }
+ return pair->second;
+}
- void LoadSurface(const TSurface& surface) {
- staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes());
- surface->LoadBuffer(system.GPU().MemoryManager(), staging_cache);
- surface->UploadTexture(staging_cache.GetBuffer(0));
- surface->MarkAsModified(false, Tick());
+template <class P>
+ImageViewId TextureCache<P>::FindColorBuffer(size_t index, bool is_clear) {
+ const auto& regs = maxwell3d.regs;
+ if (index >= regs.rt_control.count) {
+ return ImageViewId{};
+ }
+ const auto& rt = regs.rt[index];
+ const GPUVAddr gpu_addr = rt.Address();
+ if (gpu_addr == 0) {
+ return ImageViewId{};
}
+ if (rt.format == Tegra::RenderTargetFormat::NONE) {
+ return ImageViewId{};
+ }
+ const ImageInfo info(regs, index);
+ return FindRenderTargetView(info, gpu_addr, is_clear);
+}
- void FlushSurface(const TSurface& surface) {
- if (!surface->IsModified()) {
- return;
- }
- staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes());
- surface->DownloadTexture(staging_cache.GetBuffer(0));
- surface->FlushBuffer(system.GPU().MemoryManager(), staging_cache);
- surface->MarkAsModified(false, Tick());
- }
-
- void RegisterInnerCache(TSurface& surface) {
- const VAddr cpu_addr = surface->GetCpuAddr();
- VAddr start = cpu_addr >> registry_page_bits;
- const VAddr end = (surface->GetCpuAddrEnd() - 1) >> registry_page_bits;
- l1_cache[cpu_addr] = surface;
- while (start <= end) {
- registry[start].push_back(surface);
- start++;
- }
+template <class P>
+ImageViewId TextureCache<P>::FindDepthBuffer(bool is_clear) {
+ const auto& regs = maxwell3d.regs;
+ if (!regs.zeta_enable) {
+ return ImageViewId{};
+ }
+ const GPUVAddr gpu_addr = regs.zeta.Address();
+ if (gpu_addr == 0) {
+ return ImageViewId{};
}
+ const ImageInfo info(regs);
+ return FindRenderTargetView(info, gpu_addr, is_clear);
+}
- void UnregisterInnerCache(TSurface& surface) {
- const VAddr cpu_addr = surface->GetCpuAddr();
- VAddr start = cpu_addr >> registry_page_bits;
- const VAddr end = (surface->GetCpuAddrEnd() - 1) >> registry_page_bits;
- l1_cache.erase(cpu_addr);
- while (start <= end) {
- auto& reg{registry[start]};
- reg.erase(std::find(reg.begin(), reg.end(), surface));
- start++;
- }
+template <class P>
+ImageViewId TextureCache<P>::FindRenderTargetView(const ImageInfo& info, GPUVAddr gpu_addr,
+ bool is_clear) {
+ const auto options = is_clear ? RelaxedOptions::Samples : RelaxedOptions{};
+ const ImageId image_id = FindOrInsertImage(info, gpu_addr, options);
+ if (!image_id) {
+ return NULL_IMAGE_VIEW_ID;
+ }
+ Image& image = slot_images[image_id];
+ const ImageViewType view_type = RenderTargetImageViewType(info);
+ SubresourceBase base;
+ if (image.info.type == ImageType::Linear) {
+ base = SubresourceBase{.level = 0, .layer = 0};
+ } else {
+ base = image.TryFindBase(gpu_addr).value();
}
+ const s32 layers = image.info.type == ImageType::e3D ? info.size.depth : info.resources.layers;
+ const SubresourceRange range{
+ .base = base,
+ .extent = {.levels = 1, .layers = layers},
+ };
+ return FindOrEmplaceImageView(image_id, ImageViewInfo(view_type, info.format, range));
+}
- VectorSurface GetSurfacesInRegion(const VAddr cpu_addr, const std::size_t size) {
- if (size == 0) {
- return {};
+template <class P>
+template <typename Func>
+void TextureCache<P>::ForEachImageInRegion(VAddr cpu_addr, size_t size, Func&& func) {
+ using FuncReturn = typename std::invoke_result<Func, ImageId, Image&>::type;
+ static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
+ boost::container::small_vector<ImageId, 32> images;
+ ForEachPage(cpu_addr, size, [this, &images, cpu_addr, size, func](u64 page) {
+ const auto it = page_table.find(page);
+ if (it == page_table.end()) {
+ if constexpr (BOOL_BREAK) {
+ return false;
+ } else {
+ return;
+ }
}
- const VAddr cpu_addr_end = cpu_addr + size;
- const VAddr end = (cpu_addr_end - 1) >> registry_page_bits;
- VectorSurface surfaces;
- for (VAddr start = cpu_addr >> registry_page_bits; start <= end; ++start) {
- const auto it = registry.find(start);
- if (it == registry.end()) {
+ for (const ImageId image_id : it->second) {
+ Image& image = slot_images[image_id];
+ if (True(image.flags & ImageFlagBits::Picked)) {
+ continue;
+ }
+ if (!image.Overlaps(cpu_addr, size)) {
continue;
}
- for (auto& surface : it->second) {
- if (surface->IsPicked() || !surface->Overlaps(cpu_addr, cpu_addr_end)) {
- continue;
+ image.flags |= ImageFlagBits::Picked;
+ images.push_back(image_id);
+ if constexpr (BOOL_BREAK) {
+ if (func(image_id, image)) {
+ return true;
}
- surface->MarkAsPicked(true);
- surfaces.push_back(surface);
+ } else {
+ func(image_id, image);
}
}
- for (auto& surface : surfaces) {
- surface->MarkAsPicked(false);
+ if constexpr (BOOL_BREAK) {
+ return false;
}
- return surfaces;
+ });
+ for (const ImageId image_id : images) {
+ slot_images[image_id].flags &= ~ImageFlagBits::Picked;
}
+}
- void ReserveSurface(const SurfaceParams& params, TSurface surface) {
- surface_reserve[params].push_back(std::move(surface));
+template <class P>
+ImageViewId TextureCache<P>::FindOrEmplaceImageView(ImageId image_id, const ImageViewInfo& info) {
+ Image& image = slot_images[image_id];
+ if (const ImageViewId image_view_id = image.FindView(info); image_view_id) {
+ return image_view_id;
}
+ const ImageViewId image_view_id = slot_image_views.insert(runtime, info, image_id, image);
+ image.InsertView(info, image_view_id);
+ return image_view_id;
+}
+
+template <class P>
+void TextureCache<P>::RegisterImage(ImageId image_id) {
+ ImageBase& image = slot_images[image_id];
+ ASSERT_MSG(False(image.flags & ImageFlagBits::Registered),
+ "Trying to register an already registered image");
+ image.flags |= ImageFlagBits::Registered;
+ ForEachPage(image.cpu_addr, image.guest_size_bytes,
+ [this, image_id](u64 page) { page_table[page].push_back(image_id); });
+}
- TSurface TryGetReservedSurface(const SurfaceParams& params) {
- auto search{surface_reserve.find(params)};
- if (search == surface_reserve.end()) {
- return {};
+template <class P>
+void TextureCache<P>::UnregisterImage(ImageId image_id) {
+ Image& image = slot_images[image_id];
+ ASSERT_MSG(True(image.flags & ImageFlagBits::Registered),
+ "Trying to unregister an already registered image");
+ image.flags &= ~ImageFlagBits::Registered;
+ ForEachPage(image.cpu_addr, image.guest_size_bytes, [this, image_id](u64 page) {
+ const auto page_it = page_table.find(page);
+ if (page_it == page_table.end()) {
+ UNREACHABLE_MSG("Unregistering unregistered page=0x{:x}", page << PAGE_BITS);
+ return;
}
- for (auto& surface : search->second) {
- if (!surface->IsRegistered()) {
- return surface;
- }
+ std::vector<ImageId>& image_ids = page_it->second;
+ const auto vector_it = std::ranges::find(image_ids, image_id);
+ if (vector_it == image_ids.end()) {
+ UNREACHABLE_MSG("Unregistering unregistered image in page=0x{:x}", page << PAGE_BITS);
+ return;
}
- return {};
- }
+ image_ids.erase(vector_it);
+ });
+}
- /// Try to do an image copy logging when formats are incompatible.
- void TryCopyImage(TSurface& src, TSurface& dst, const CopyParams& copy) {
- const SurfaceParams& src_params = src->GetSurfaceParams();
- const SurfaceParams& dst_params = dst->GetSurfaceParams();
- if (!format_compatibility.TestCopy(src_params.pixel_format, dst_params.pixel_format)) {
- LOG_ERROR(HW_GPU, "Illegal copy between formats={{{}, {}}}",
- static_cast<int>(dst_params.pixel_format),
- static_cast<int>(src_params.pixel_format));
- return;
+template <class P>
+void TextureCache<P>::TrackImage(ImageBase& image) {
+ ASSERT(False(image.flags & ImageFlagBits::Tracked));
+ image.flags |= ImageFlagBits::Tracked;
+ rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1);
+}
+
+template <class P>
+void TextureCache<P>::UntrackImage(ImageBase& image) {
+ ASSERT(True(image.flags & ImageFlagBits::Tracked));
+ image.flags &= ~ImageFlagBits::Tracked;
+ rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1);
+}
+
+template <class P>
+void TextureCache<P>::DeleteImage(ImageId image_id) {
+ ImageBase& image = slot_images[image_id];
+ const GPUVAddr gpu_addr = image.gpu_addr;
+ const auto alloc_it = image_allocs_table.find(gpu_addr);
+ if (alloc_it == image_allocs_table.end()) {
+ UNREACHABLE_MSG("Trying to delete an image alloc that does not exist in address 0x{:x}",
+ gpu_addr);
+ return;
+ }
+ const ImageAllocId alloc_id = alloc_it->second;
+ std::vector<ImageId>& alloc_images = slot_image_allocs[alloc_id].images;
+ const auto alloc_image_it = std::ranges::find(alloc_images, image_id);
+ if (alloc_image_it == alloc_images.end()) {
+ UNREACHABLE_MSG("Trying to delete an image that does not exist");
+ return;
+ }
+ ASSERT_MSG(False(image.flags & ImageFlagBits::Tracked), "Image was not untracked");
+ ASSERT_MSG(False(image.flags & ImageFlagBits::Registered), "Image was not unregistered");
+
+ // Mark render targets as dirty
+ auto& dirty = maxwell3d.dirty.flags;
+ dirty[Dirty::RenderTargets] = true;
+ dirty[Dirty::ZetaBuffer] = true;
+ for (size_t rt = 0; rt < NUM_RT; ++rt) {
+ dirty[Dirty::ColorBuffer0 + rt] = true;
+ }
+ const std::span<const ImageViewId> image_view_ids = image.image_view_ids;
+ for (const ImageViewId image_view_id : image_view_ids) {
+ std::ranges::replace(render_targets.color_buffer_ids, image_view_id, ImageViewId{});
+ if (render_targets.depth_buffer_id == image_view_id) {
+ render_targets.depth_buffer_id = ImageViewId{};
}
- ImageCopy(src, dst, copy);
}
+ RemoveImageViewReferences(image_view_ids);
+ RemoveFramebuffers(image_view_ids);
+
+ for (const AliasedImage& alias : image.aliased_images) {
+ ImageBase& other_image = slot_images[alias.id];
+ [[maybe_unused]] const size_t num_removed_aliases =
+ std::erase_if(other_image.aliased_images, [image_id](const AliasedImage& other_alias) {
+ return other_alias.id == image_id;
+ });
+ ASSERT_MSG(num_removed_aliases == 1, "Invalid number of removed aliases: {}",
+ num_removed_aliases);
+ }
+ for (const ImageViewId image_view_id : image_view_ids) {
+ sentenced_image_view.Push(std::move(slot_image_views[image_view_id]));
+ slot_image_views.erase(image_view_id);
+ }
+ sentenced_images.Push(std::move(slot_images[image_id]));
+ slot_images.erase(image_id);
- constexpr PixelFormat GetSiblingFormat(PixelFormat format) const {
- return siblings_table[static_cast<std::size_t>(format)];
+ alloc_images.erase(alloc_image_it);
+ if (alloc_images.empty()) {
+ image_allocs_table.erase(alloc_it);
+ }
+ if constexpr (ENABLE_VALIDATION) {
+ std::ranges::fill(graphics_image_view_ids, CORRUPT_ID);
+ std::ranges::fill(compute_image_view_ids, CORRUPT_ID);
}
+ graphics_image_table.Invalidate();
+ compute_image_table.Invalidate();
+ has_deleted_images = true;
+}
- /// Returns true the shader sampler entry is compatible with the TIC texture type.
- static bool IsTypeCompatible(Tegra::Texture::TextureType tic_type,
- const VideoCommon::Shader::Sampler& entry) {
- const auto shader_type = entry.type;
- switch (tic_type) {
- case Tegra::Texture::TextureType::Texture1D:
- case Tegra::Texture::TextureType::Texture1DArray:
- return shader_type == Tegra::Shader::TextureType::Texture1D;
- case Tegra::Texture::TextureType::Texture1DBuffer:
- // TODO(Rodrigo): Assume as valid for now
- return true;
- case Tegra::Texture::TextureType::Texture2D:
- case Tegra::Texture::TextureType::Texture2DNoMipmap:
- return shader_type == Tegra::Shader::TextureType::Texture2D;
- case Tegra::Texture::TextureType::Texture2DArray:
- return shader_type == Tegra::Shader::TextureType::Texture2D ||
- shader_type == Tegra::Shader::TextureType::TextureCube;
- case Tegra::Texture::TextureType::Texture3D:
- return shader_type == Tegra::Shader::TextureType::Texture3D;
- case Tegra::Texture::TextureType::TextureCubeArray:
- case Tegra::Texture::TextureType::TextureCubemap:
- if (shader_type == Tegra::Shader::TextureType::TextureCube) {
- return true;
- }
- return shader_type == Tegra::Shader::TextureType::Texture2D && entry.is_array;
+template <class P>
+void TextureCache<P>::RemoveImageViewReferences(std::span<const ImageViewId> removed_views) {
+ auto it = image_views.begin();
+ while (it != image_views.end()) {
+ const auto found = std::ranges::find(removed_views, it->second);
+ if (found != removed_views.end()) {
+ it = image_views.erase(it);
+ } else {
+ ++it;
}
- UNREACHABLE();
- return true;
}
+}
- struct FramebufferTargetInfo {
- TSurface target;
- TView view;
- };
-
- void AsyncFlushSurface(TSurface& surface) {
- if (!uncommitted_flushes) {
- uncommitted_flushes = std::make_shared<std::list<TSurface>>();
+template <class P>
+void TextureCache<P>::RemoveFramebuffers(std::span<const ImageViewId> removed_views) {
+ auto it = framebuffers.begin();
+ while (it != framebuffers.end()) {
+ if (it->first.Contains(removed_views)) {
+ it = framebuffers.erase(it);
+ } else {
+ ++it;
}
- uncommitted_flushes->push_back(surface);
}
+}
- VideoCore::RasterizerInterface& rasterizer;
-
- FormatLookupTable format_lookup_table;
- FormatCompatibility format_compatibility;
-
- u64 ticks{};
-
- // Guards the cache for protection conflicts.
- bool guard_render_targets{};
- bool guard_samplers{};
-
- // The siblings table is for formats that can inter exchange with one another
- // without causing issues. This is only valid when a conflict occurs on a non
- // rendering use.
- std::array<PixelFormat, static_cast<std::size_t>(PixelFormat::Max)> siblings_table;
-
- // The internal Cache is different for the Texture Cache. It's based on buckets
- // of 1MB. This fits better for the purpose of this cache as textures are normaly
- // large in size.
- static constexpr u64 registry_page_bits{20};
- static constexpr u64 registry_page_size{1 << registry_page_bits};
- std::unordered_map<VAddr, std::vector<TSurface>> registry;
+template <class P>
+void TextureCache<P>::MarkModification(ImageBase& image) noexcept {
+ image.flags |= ImageFlagBits::GpuModified;
+ image.modification_tick = ++modification_tick;
+}
- static constexpr u32 DEPTH_RT = 8;
- static constexpr u32 NO_RT = 0xFFFFFFFF;
+template <class P>
+void TextureCache<P>::SynchronizeAliases(ImageId image_id) {
+ boost::container::small_vector<const AliasedImage*, 1> aliased_images;
+ ImageBase& image = slot_images[image_id];
+ u64 most_recent_tick = image.modification_tick;
+ for (const AliasedImage& aliased : image.aliased_images) {
+ ImageBase& aliased_image = slot_images[aliased.id];
+ if (image.modification_tick < aliased_image.modification_tick) {
+ most_recent_tick = std::max(most_recent_tick, aliased_image.modification_tick);
+ aliased_images.push_back(&aliased);
+ }
+ }
+ if (aliased_images.empty()) {
+ return;
+ }
+ image.modification_tick = most_recent_tick;
+ std::ranges::sort(aliased_images, [this](const AliasedImage* lhs, const AliasedImage* rhs) {
+ const ImageBase& lhs_image = slot_images[lhs->id];
+ const ImageBase& rhs_image = slot_images[rhs->id];
+ return lhs_image.modification_tick < rhs_image.modification_tick;
+ });
+ for (const AliasedImage* const aliased : aliased_images) {
+ CopyImage(image_id, aliased->id, aliased->copies);
+ }
+}
- // The L1 Cache is used for fast texture lookup before checking the overlaps
- // This avoids calculating size and other stuffs.
- std::unordered_map<VAddr, TSurface> l1_cache;
+template <class P>
+void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool invalidate) {
+ Image& image = slot_images[image_id];
+ if (invalidate) {
+ image.flags &= ~(ImageFlagBits::CpuModified | ImageFlagBits::GpuModified);
+ if (False(image.flags & ImageFlagBits::Tracked)) {
+ TrackImage(image);
+ }
+ } else {
+ RefreshContents(image);
+ SynchronizeAliases(image_id);
+ }
+ if (is_modification) {
+ MarkModification(image);
+ }
+ image.frame_tick = frame_tick;
+}
- /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
- /// previously been used. This is to prevent surfaces from being constantly created and
- /// destroyed when used with different surface parameters.
- std::unordered_map<SurfaceParams, std::vector<TSurface>> surface_reserve;
- std::array<FramebufferTargetInfo, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets>
- render_targets;
- FramebufferTargetInfo depth_buffer;
+template <class P>
+void TextureCache<P>::PrepareImageView(ImageViewId image_view_id, bool is_modification,
+ bool invalidate) {
+ if (!image_view_id) {
+ return;
+ }
+ const ImageViewBase& image_view = slot_image_views[image_view_id];
+ PrepareImage(image_view.image_id, is_modification, invalidate);
+}
- std::vector<TSurface> sampled_textures;
+template <class P>
+void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::span<const ImageCopy> copies) {
+ Image& dst = slot_images[dst_id];
+ Image& src = slot_images[src_id];
+ const auto dst_format_type = GetFormatType(dst.info.format);
+ const auto src_format_type = GetFormatType(src.info.format);
+ if (src_format_type == dst_format_type) {
+ if constexpr (HAS_EMULATED_COPIES) {
+ if (!runtime.CanImageBeCopied(dst, src)) {
+ return runtime.EmulateCopyImage(dst, src, copies);
+ }
+ }
+ return runtime.CopyImage(dst, src, copies);
+ }
+ UNIMPLEMENTED_IF(dst.info.type != ImageType::e2D);
+ UNIMPLEMENTED_IF(src.info.type != ImageType::e2D);
+ for (const ImageCopy& copy : copies) {
+ UNIMPLEMENTED_IF(copy.dst_subresource.num_layers != 1);
+ UNIMPLEMENTED_IF(copy.src_subresource.num_layers != 1);
+ UNIMPLEMENTED_IF(copy.src_offset != Offset3D{});
+ UNIMPLEMENTED_IF(copy.dst_offset != Offset3D{});
+
+ const SubresourceBase dst_base{
+ .level = copy.dst_subresource.base_level,
+ .layer = copy.dst_subresource.base_layer,
+ };
+ const SubresourceBase src_base{
+ .level = copy.src_subresource.base_level,
+ .layer = copy.src_subresource.base_layer,
+ };
+ const SubresourceExtent dst_extent{.levels = 1, .layers = 1};
+ const SubresourceExtent src_extent{.levels = 1, .layers = 1};
+ const SubresourceRange dst_range{.base = dst_base, .extent = dst_extent};
+ const SubresourceRange src_range{.base = src_base, .extent = src_extent};
+ const ImageViewInfo dst_view_info(ImageViewType::e2D, dst.info.format, dst_range);
+ const ImageViewInfo src_view_info(ImageViewType::e2D, src.info.format, src_range);
+ const auto [dst_framebuffer_id, dst_view_id] = RenderTargetFromImage(dst_id, dst_view_info);
+ Framebuffer* const dst_framebuffer = &slot_framebuffers[dst_framebuffer_id];
+ const ImageViewId src_view_id = FindOrEmplaceImageView(src_id, src_view_info);
+ ImageView& dst_view = slot_image_views[dst_view_id];
+ ImageView& src_view = slot_image_views[src_view_id];
+ [[maybe_unused]] const Extent3D expected_size{
+ .width = std::min(dst_view.size.width, src_view.size.width),
+ .height = std::min(dst_view.size.height, src_view.size.height),
+ .depth = std::min(dst_view.size.depth, src_view.size.depth),
+ };
+ UNIMPLEMENTED_IF(copy.extent != expected_size);
- /// This cache stores null surfaces in order to be used as a placeholder
- /// for invalid texture calls.
- std::unordered_map<u32, TSurface> invalid_cache;
- std::vector<u8> invalid_memory;
+ runtime.ConvertImage(dst_framebuffer, dst_view, src_view);
+ }
+}
- std::list<TSurface> marked_for_unregister;
+template <class P>
+void TextureCache<P>::BindRenderTarget(ImageViewId* old_id, ImageViewId new_id) {
+ if (*old_id == new_id) {
+ return;
+ }
+ if (*old_id) {
+ const ImageViewBase& old_view = slot_image_views[*old_id];
+ if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload)) {
+ uncommitted_downloads.push_back(old_view.image_id);
+ }
+ }
+ *old_id = new_id;
+}
- std::shared_ptr<std::list<TSurface>> uncommitted_flushes{};
- std::list<std::shared_ptr<std::list<TSurface>>> committed_flushes;
+template <class P>
+std::pair<FramebufferId, ImageViewId> TextureCache<P>::RenderTargetFromImage(
+ ImageId image_id, const ImageViewInfo& view_info) {
+ const ImageViewId view_id = FindOrEmplaceImageView(image_id, view_info);
+ const ImageBase& image = slot_images[image_id];
+ const bool is_color = GetFormatType(image.info.format) == SurfaceType::ColorTexture;
+ const ImageViewId color_view_id = is_color ? view_id : ImageViewId{};
+ const ImageViewId depth_view_id = is_color ? ImageViewId{} : view_id;
+ const Extent3D extent = MipSize(image.info.size, view_info.range.base.level);
+ const u32 num_samples = image.info.num_samples;
+ const auto [samples_x, samples_y] = SamplesLog2(num_samples);
+ const FramebufferId framebuffer_id = GetFramebufferId(RenderTargets{
+ .color_buffer_ids = {color_view_id},
+ .depth_buffer_id = depth_view_id,
+ .size = {extent.width >> samples_x, extent.height >> samples_y},
+ });
+ return {framebuffer_id, view_id};
+}
- StagingCache staging_cache;
- std::recursive_mutex mutex;
-};
+template <class P>
+bool TextureCache<P>::IsFullClear(ImageViewId id) {
+ if (!id) {
+ return true;
+ }
+ const ImageViewBase& image_view = slot_image_views[id];
+ const ImageBase& image = slot_images[image_view.image_id];
+ const Extent3D size = image_view.size;
+ const auto& regs = maxwell3d.regs;
+ const auto& scissor = regs.scissor_test[0];
+ if (image.info.resources.levels > 1 || image.info.resources.layers > 1) {
+ // Images with multiple resources can't be cleared in a single call
+ return false;
+ }
+ if (regs.clear_flags.scissor == 0) {
+ // If scissor testing is disabled, the clear is always full
+ return true;
+ }
+ // Make sure the clear covers all texels in the subresource
+ return scissor.min_x == 0 && scissor.min_y == 0 && scissor.max_x >= size.width &&
+ scissor.max_y >= size.height;
+}
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/types.h b/src/video_core/texture_cache/types.h
new file mode 100644
index 000000000..2ad2d72a6
--- /dev/null
+++ b/src/video_core/texture_cache/types.h
@@ -0,0 +1,140 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "video_core/texture_cache/slot_vector.h"
+
+namespace VideoCommon {
+
+constexpr size_t NUM_RT = 8;
+constexpr size_t MAX_MIP_LEVELS = 14;
+
+constexpr SlotId CORRUPT_ID{0xfffffffe};
+
+using ImageId = SlotId;
+using ImageViewId = SlotId;
+using ImageAllocId = SlotId;
+using SamplerId = SlotId;
+using FramebufferId = SlotId;
+
+enum class ImageType : u32 {
+ e1D,
+ e2D,
+ e3D,
+ Linear,
+ Buffer,
+};
+
+enum class ImageViewType : u32 {
+ e1D,
+ e2D,
+ Cube,
+ e3D,
+ e1DArray,
+ e2DArray,
+ CubeArray,
+ Rect,
+ Buffer,
+};
+constexpr size_t NUM_IMAGE_VIEW_TYPES = 9;
+
+enum class RelaxedOptions : u32 {
+ Size = 1 << 0,
+ Format = 1 << 1,
+ Samples = 1 << 2,
+};
+DECLARE_ENUM_FLAG_OPERATORS(RelaxedOptions)
+
+struct Offset2D {
+ constexpr auto operator<=>(const Offset2D&) const noexcept = default;
+
+ s32 x;
+ s32 y;
+};
+
+struct Offset3D {
+ constexpr auto operator<=>(const Offset3D&) const noexcept = default;
+
+ s32 x;
+ s32 y;
+ s32 z;
+};
+
+struct Extent2D {
+ constexpr auto operator<=>(const Extent2D&) const noexcept = default;
+
+ u32 width;
+ u32 height;
+};
+
+struct Extent3D {
+ constexpr auto operator<=>(const Extent3D&) const noexcept = default;
+
+ u32 width;
+ u32 height;
+ u32 depth;
+};
+
+struct SubresourceLayers {
+ s32 base_level = 0;
+ s32 base_layer = 0;
+ s32 num_layers = 1;
+};
+
+struct SubresourceBase {
+ constexpr auto operator<=>(const SubresourceBase&) const noexcept = default;
+
+ s32 level = 0;
+ s32 layer = 0;
+};
+
+struct SubresourceExtent {
+ constexpr auto operator<=>(const SubresourceExtent&) const noexcept = default;
+
+ s32 levels = 1;
+ s32 layers = 1;
+};
+
+struct SubresourceRange {
+ constexpr auto operator<=>(const SubresourceRange&) const noexcept = default;
+
+ SubresourceBase base;
+ SubresourceExtent extent;
+};
+
+struct ImageCopy {
+ SubresourceLayers src_subresource;
+ SubresourceLayers dst_subresource;
+ Offset3D src_offset;
+ Offset3D dst_offset;
+ Extent3D extent;
+};
+
+struct BufferImageCopy {
+ size_t buffer_offset;
+ size_t buffer_size;
+ u32 buffer_row_length;
+ u32 buffer_image_height;
+ SubresourceLayers image_subresource;
+ Offset3D image_offset;
+ Extent3D image_extent;
+};
+
+struct BufferCopy {
+ size_t src_offset;
+ size_t dst_offset;
+ size_t size;
+};
+
+struct SwizzleParameters {
+ Extent3D num_tiles;
+ Extent3D block;
+ size_t buffer_offset;
+ s32 level;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
new file mode 100644
index 000000000..a0bc1f7b6
--- /dev/null
+++ b/src/video_core/texture_cache/util.cpp
@@ -0,0 +1,1210 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// This files contains code from Ryujinx
+// A copy of the code can be obtained from https://github.com/Ryujinx/Ryujinx
+// The sections using code from Ryujinx are marked with a link to the original version
+
+// MIT License
+//
+// Copyright (c) Ryujinx Team and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+// associated documentation files (the "Software"), to deal in the Software without restriction,
+// including without limitation the rights to use, copy, modify, merge, publish, distribute,
+// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#include <algorithm>
+#include <array>
+#include <numeric>
+#include <optional>
+#include <span>
+#include <vector>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/bit_util.h"
+#include "common/common_types.h"
+#include "common/div_ceil.h"
+#include "video_core/compatible_formats.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/decode_bc4.h"
+#include "video_core/texture_cache/format_lookup_table.h"
+#include "video_core/texture_cache/formatter.h"
+#include "video_core/texture_cache/samples_helper.h"
+#include "video_core/texture_cache/util.h"
+#include "video_core/textures/astc.h"
+#include "video_core/textures/decoders.h"
+
+namespace VideoCommon {
+
+namespace {
+
+using Tegra::Texture::GOB_SIZE;
+using Tegra::Texture::GOB_SIZE_SHIFT;
+using Tegra::Texture::GOB_SIZE_X;
+using Tegra::Texture::GOB_SIZE_X_SHIFT;
+using Tegra::Texture::GOB_SIZE_Y;
+using Tegra::Texture::GOB_SIZE_Y_SHIFT;
+using Tegra::Texture::GOB_SIZE_Z;
+using Tegra::Texture::GOB_SIZE_Z_SHIFT;
+using Tegra::Texture::MsaaMode;
+using Tegra::Texture::SwizzleTexture;
+using Tegra::Texture::TextureFormat;
+using Tegra::Texture::TextureType;
+using Tegra::Texture::TICEntry;
+using Tegra::Texture::UnswizzleTexture;
+using VideoCore::Surface::BytesPerBlock;
+using VideoCore::Surface::DefaultBlockHeight;
+using VideoCore::Surface::DefaultBlockWidth;
+using VideoCore::Surface::IsCopyCompatible;
+using VideoCore::Surface::IsPixelFormatASTC;
+using VideoCore::Surface::IsViewCompatible;
+using VideoCore::Surface::PixelFormatFromDepthFormat;
+using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
+using VideoCore::Surface::SurfaceType;
+
+constexpr u32 CONVERTED_BYTES_PER_BLOCK = BytesPerBlock(PixelFormat::A8B8G8R8_UNORM);
+
+struct LevelInfo {
+ Extent3D size;
+ Extent3D block;
+ Extent2D tile_size;
+ u32 bpp_log2;
+ u32 tile_width_spacing;
+};
+
+[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) {
+ if (shift == 0) {
+ return 0;
+ }
+ u32 x = unit_factor << (shift - 1);
+ if (x >= dimension) {
+ while (--shift) {
+ x >>= 1;
+ if (x < dimension) {
+ break;
+ }
+ }
+ }
+ return shift;
+}
+
+[[nodiscard]] constexpr u32 AdjustMipSize(u32 size, u32 level) {
+ return std::max<u32>(size >> level, 1);
+}
+
+[[nodiscard]] constexpr Extent3D AdjustMipSize(Extent3D size, s32 level) {
+ return Extent3D{
+ .width = AdjustMipSize(size.width, level),
+ .height = AdjustMipSize(size.height, level),
+ .depth = AdjustMipSize(size.depth, level),
+ };
+}
+
+[[nodiscard]] Extent3D AdjustSamplesSize(Extent3D size, s32 num_samples) {
+ const auto [samples_x, samples_y] = SamplesLog2(num_samples);
+ return Extent3D{
+ .width = size.width >> samples_x,
+ .height = size.height >> samples_y,
+ .depth = size.depth,
+ };
+}
+
+template <u32 GOB_EXTENT>
+[[nodiscard]] constexpr u32 AdjustMipBlockSize(u32 num_tiles, u32 block_size, u32 level) {
+ do {
+ while (block_size > 0 && num_tiles <= (1U << (block_size - 1)) * GOB_EXTENT) {
+ --block_size;
+ }
+ } while (level--);
+ return block_size;
+}
+
+[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size,
+ u32 level) {
+ return {
+ .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level),
+ .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level),
+ .depth = AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level),
+ };
+}
+
+[[nodiscard]] constexpr Extent3D AdjustTileSize(Extent3D size, Extent2D tile_size) {
+ return {
+ .width = Common::DivCeil(size.width, tile_size.width),
+ .height = Common::DivCeil(size.height, tile_size.height),
+ .depth = size.depth,
+ };
+}
+
+[[nodiscard]] constexpr u32 BytesPerBlockLog2(u32 bytes_per_block) {
+ return std::countl_zero(bytes_per_block) ^ 0x1F;
+}
+
+[[nodiscard]] constexpr u32 BytesPerBlockLog2(PixelFormat format) {
+ return BytesPerBlockLog2(BytesPerBlock(format));
+}
+
+[[nodiscard]] constexpr u32 NumBlocks(Extent3D size, Extent2D tile_size) {
+ const Extent3D num_blocks = AdjustTileSize(size, tile_size);
+ return num_blocks.width * num_blocks.height * num_blocks.depth;
+}
+
+[[nodiscard]] constexpr u32 AdjustSize(u32 size, u32 level, u32 block_size) {
+ return Common::DivCeil(AdjustMipSize(size, level), block_size);
+}
+
+[[nodiscard]] constexpr std::pair<int, int> Samples(int num_samples) {
+ switch (num_samples) {
+ case 1:
+ return {1, 1};
+ case 2:
+ return {2, 1};
+ case 4:
+ return {2, 2};
+ case 8:
+ return {4, 2};
+ case 16:
+ return {4, 4};
+ }
+ UNREACHABLE_MSG("Invalid number of samples={}", num_samples);
+ return {1, 1};
+}
+
+[[nodiscard]] constexpr Extent2D DefaultBlockSize(PixelFormat format) {
+ return {DefaultBlockWidth(format), DefaultBlockHeight(format)};
+}
+
+[[nodiscard]] constexpr Extent3D NumLevelBlocks(const LevelInfo& info, u32 level) {
+ return Extent3D{
+ .width = AdjustSize(info.size.width, level, info.tile_size.width) << info.bpp_log2,
+ .height = AdjustSize(info.size.height, level, info.tile_size.height),
+ .depth = AdjustMipSize(info.size.depth, level),
+ };
+}
+
+[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) {
+ const Extent3D blocks = NumLevelBlocks(info, level);
+ return Extent3D{
+ .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width),
+ .height = AdjustTileSize(info.block.height, GOB_SIZE_Y, blocks.height),
+ .depth = AdjustTileSize(info.block.depth, GOB_SIZE_Z, blocks.depth),
+ };
+}
+
+[[nodiscard]] constexpr Extent2D GobSize(u32 bpp_log2, u32 block_height, u32 tile_width_spacing) {
+ return Extent2D{
+ .width = GOB_SIZE_X_SHIFT - bpp_log2 + tile_width_spacing,
+ .height = GOB_SIZE_Y_SHIFT + block_height,
+ };
+}
+
+[[nodiscard]] constexpr bool IsSmallerThanGobSize(Extent3D num_tiles, Extent2D gob,
+ u32 block_depth) {
+ return num_tiles.width <= (1U << gob.width) || num_tiles.height <= (1U << gob.height) ||
+ num_tiles.depth < (1U << block_depth);
+}
+
+[[nodiscard]] constexpr u32 StrideAlignment(Extent3D num_tiles, Extent3D block, Extent2D gob,
+ u32 bpp_log2) {
+ if (IsSmallerThanGobSize(num_tiles, gob, block.depth)) {
+ return GOB_SIZE_X_SHIFT - bpp_log2;
+ } else {
+ return gob.width;
+ }
+}
+
+[[nodiscard]] constexpr u32 StrideAlignment(Extent3D num_tiles, Extent3D block, u32 bpp_log2,
+ u32 tile_width_spacing) {
+ const Extent2D gob = GobSize(bpp_log2, block.height, tile_width_spacing);
+ return StrideAlignment(num_tiles, block, gob, bpp_log2);
+}
+
+[[nodiscard]] constexpr Extent2D NumGobs(const LevelInfo& info, u32 level) {
+ const Extent3D blocks = NumLevelBlocks(info, level);
+ const Extent2D gobs{
+ .width = Common::DivCeilLog2(blocks.width, GOB_SIZE_X_SHIFT),
+ .height = Common::DivCeilLog2(blocks.height, GOB_SIZE_Y_SHIFT),
+ };
+ const Extent2D gob = GobSize(info.bpp_log2, info.block.height, info.tile_width_spacing);
+ const bool is_small = IsSmallerThanGobSize(blocks, gob, info.block.depth);
+ const u32 alignment = is_small ? 0 : info.tile_width_spacing;
+ return Extent2D{
+ .width = Common::AlignUpLog2(gobs.width, alignment),
+ .height = gobs.height,
+ };
+}
+
+[[nodiscard]] constexpr Extent3D LevelTiles(const LevelInfo& info, u32 level) {
+ const Extent3D blocks = NumLevelBlocks(info, level);
+ const Extent3D tile_shift = TileShift(info, level);
+ const Extent2D gobs = NumGobs(info, level);
+ return Extent3D{
+ .width = Common::DivCeilLog2(gobs.width, tile_shift.width),
+ .height = Common::DivCeilLog2(gobs.height, tile_shift.height),
+ .depth = Common::DivCeilLog2(blocks.depth, tile_shift.depth),
+ };
+}
+
+[[nodiscard]] constexpr u32 CalculateLevelSize(const LevelInfo& info, u32 level) {
+ const Extent3D tile_shift = TileShift(info, level);
+ const Extent3D tiles = LevelTiles(info, level);
+ const u32 num_tiles = tiles.width * tiles.height * tiles.depth;
+ const u32 shift = GOB_SIZE_SHIFT + tile_shift.width + tile_shift.height + tile_shift.depth;
+ return num_tiles << shift;
+}
+
+[[nodiscard]] constexpr std::array<u32, MAX_MIP_LEVELS> CalculateLevelSizes(const LevelInfo& info,
+ u32 num_levels) {
+ ASSERT(num_levels <= MAX_MIP_LEVELS);
+ std::array<u32, MAX_MIP_LEVELS> sizes{};
+ for (u32 level = 0; level < num_levels; ++level) {
+ sizes[level] = CalculateLevelSize(info, level);
+ }
+ return sizes;
+}
+
+[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block,
+ u32 num_samples, u32 tile_width_spacing) {
+ const auto [samples_x, samples_y] = Samples(num_samples);
+ const u32 bytes_per_block = BytesPerBlock(format);
+ return {
+ .size =
+ {
+ .width = size.width * samples_x,
+ .height = size.height * samples_y,
+ .depth = size.depth,
+ },
+ .block = block,
+ .tile_size = DefaultBlockSize(format),
+ .bpp_log2 = BytesPerBlockLog2(bytes_per_block),
+ .tile_width_spacing = tile_width_spacing,
+ };
+}
+
+[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) {
+ return MakeLevelInfo(info.format, info.size, info.block, info.num_samples,
+ info.tile_width_spacing);
+}
+
+[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block,
+ u32 num_samples, u32 tile_width_spacing,
+ u32 level) {
+ const LevelInfo info = MakeLevelInfo(format, size, block, num_samples, tile_width_spacing);
+ u32 offset = 0;
+ for (u32 current_level = 0; current_level < level; ++current_level) {
+ offset += CalculateLevelSize(info, current_level);
+ }
+ return offset;
+}
+
+[[nodiscard]] constexpr u32 AlignLayerSize(u32 size_bytes, Extent3D size, Extent3D block,
+ u32 tile_size_y, u32 tile_width_spacing) {
+ // https://github.com/Ryujinx/Ryujinx/blob/1c9aba6de1520aea5480c032e0ff5664ac1bb36f/Ryujinx.Graphics.Texture/SizeCalculator.cs#L134
+ if (tile_width_spacing > 0) {
+ const u32 alignment_log2 = GOB_SIZE_SHIFT + tile_width_spacing + block.height + block.depth;
+ return Common::AlignUpLog2(size_bytes, alignment_log2);
+ }
+ const u32 aligned_height = Common::AlignUp(size.height, tile_size_y);
+ while (block.height != 0 && aligned_height <= (1U << (block.height - 1)) * GOB_SIZE_Y) {
+ --block.height;
+ }
+ while (block.depth != 0 && size.depth <= (1U << (block.depth - 1))) {
+ --block.depth;
+ }
+ const u32 block_shift = GOB_SIZE_SHIFT + block.height + block.depth;
+ const u32 num_blocks = size_bytes >> block_shift;
+ if (size_bytes != num_blocks << block_shift) {
+ return (num_blocks + 1) << block_shift;
+ }
+ return size_bytes;
+}
+
+[[nodiscard]] std::optional<SubresourceExtent> ResolveOverlapEqualAddress(const ImageInfo& new_info,
+ const ImageBase& overlap,
+ bool strict_size) {
+ const ImageInfo& info = overlap.info;
+ if (!IsBlockLinearSizeCompatible(new_info, info, 0, 0, strict_size)) {
+ return std::nullopt;
+ }
+ if (new_info.block != info.block) {
+ return std::nullopt;
+ }
+ const SubresourceExtent resources = new_info.resources;
+ return SubresourceExtent{
+ .levels = std::max(resources.levels, info.resources.levels),
+ .layers = std::max(resources.layers, info.resources.layers),
+ };
+}
+
+[[nodiscard]] std::optional<SubresourceExtent> ResolveOverlapRightAddress3D(
+ const ImageInfo& new_info, GPUVAddr gpu_addr, const ImageBase& overlap, bool strict_size) {
+ const std::vector<u32> slice_offsets = CalculateSliceOffsets(new_info);
+ const u32 diff = static_cast<u32>(overlap.gpu_addr - gpu_addr);
+ const auto it = std::ranges::find(slice_offsets, diff);
+ if (it == slice_offsets.end()) {
+ return std::nullopt;
+ }
+ const std::vector subresources = CalculateSliceSubresources(new_info);
+ const SubresourceBase base = subresources[std::distance(slice_offsets.begin(), it)];
+ const ImageInfo& info = overlap.info;
+ if (!IsBlockLinearSizeCompatible(new_info, info, base.level, 0, strict_size)) {
+ return std::nullopt;
+ }
+ const u32 mip_depth = std::max(1U, new_info.size.depth << base.level);
+ if (mip_depth < info.size.depth + base.layer) {
+ return std::nullopt;
+ }
+ if (MipBlockSize(new_info, base.level) != info.block) {
+ return std::nullopt;
+ }
+ return SubresourceExtent{
+ .levels = std::max(new_info.resources.levels, info.resources.levels + base.level),
+ .layers = 1,
+ };
+}
+
+[[nodiscard]] std::optional<SubresourceExtent> ResolveOverlapRightAddress2D(
+ const ImageInfo& new_info, GPUVAddr gpu_addr, const ImageBase& overlap, bool strict_size) {
+ const u32 layer_stride = new_info.layer_stride;
+ const s32 new_size = layer_stride * new_info.resources.layers;
+ const s32 diff = static_cast<s32>(overlap.gpu_addr - gpu_addr);
+ if (diff > new_size) {
+ return std::nullopt;
+ }
+ const s32 base_layer = diff / layer_stride;
+ const s32 mip_offset = diff % layer_stride;
+ const std::array offsets = CalculateMipLevelOffsets(new_info);
+ const auto end = offsets.begin() + new_info.resources.levels;
+ const auto it = std::find(offsets.begin(), end, mip_offset);
+ if (it == end) {
+ // Mipmap is not aligned to any valid size
+ return std::nullopt;
+ }
+ const SubresourceBase base{
+ .level = static_cast<s32>(std::distance(offsets.begin(), it)),
+ .layer = base_layer,
+ };
+ const ImageInfo& info = overlap.info;
+ if (!IsBlockLinearSizeCompatible(new_info, info, base.level, 0, strict_size)) {
+ return std::nullopt;
+ }
+ if (MipBlockSize(new_info, base.level) != info.block) {
+ return std::nullopt;
+ }
+ return SubresourceExtent{
+ .levels = std::max(new_info.resources.levels, info.resources.levels + base.level),
+ .layers = std::max(new_info.resources.layers, info.resources.layers + base.layer),
+ };
+}
+
+[[nodiscard]] std::optional<OverlapResult> ResolveOverlapRightAddress(const ImageInfo& new_info,
+ GPUVAddr gpu_addr,
+ VAddr cpu_addr,
+ const ImageBase& overlap,
+ bool strict_size) {
+ std::optional<SubresourceExtent> resources;
+ if (new_info.type != ImageType::e3D) {
+ resources = ResolveOverlapRightAddress2D(new_info, gpu_addr, overlap, strict_size);
+ } else {
+ resources = ResolveOverlapRightAddress3D(new_info, gpu_addr, overlap, strict_size);
+ }
+ if (!resources) {
+ return std::nullopt;
+ }
+ return OverlapResult{
+ .gpu_addr = gpu_addr,
+ .cpu_addr = cpu_addr,
+ .resources = *resources,
+ };
+}
+
+[[nodiscard]] std::optional<OverlapResult> ResolveOverlapLeftAddress(const ImageInfo& new_info,
+ GPUVAddr gpu_addr,
+ VAddr cpu_addr,
+ const ImageBase& overlap,
+ bool strict_size) {
+ const std::optional<SubresourceBase> base = overlap.TryFindBase(gpu_addr);
+ if (!base) {
+ return std::nullopt;
+ }
+ const ImageInfo& info = overlap.info;
+ if (!IsBlockLinearSizeCompatible(new_info, info, base->level, 0, strict_size)) {
+ return std::nullopt;
+ }
+ if (new_info.block != MipBlockSize(info, base->level)) {
+ return std::nullopt;
+ }
+ const SubresourceExtent resources = new_info.resources;
+ s32 layers = 1;
+ if (info.type != ImageType::e3D) {
+ layers = std::max(resources.layers, info.resources.layers + base->layer);
+ }
+ return OverlapResult{
+ .gpu_addr = overlap.gpu_addr,
+ .cpu_addr = overlap.cpu_addr,
+ .resources =
+ {
+ .levels = std::max(resources.levels + base->level, info.resources.levels),
+ .layers = layers,
+ },
+ };
+}
+
+[[nodiscard]] Extent2D PitchLinearAlignedSize(const ImageInfo& info) {
+ // https://github.com/Ryujinx/Ryujinx/blob/1c9aba6de1520aea5480c032e0ff5664ac1bb36f/Ryujinx.Graphics.Texture/SizeCalculator.cs#L212
+ static constexpr u32 STRIDE_ALIGNMENT = 32;
+ ASSERT(info.type == ImageType::Linear);
+ const Extent2D num_tiles{
+ .width = Common::DivCeil(info.size.width, DefaultBlockWidth(info.format)),
+ .height = Common::DivCeil(info.size.height, DefaultBlockHeight(info.format)),
+ };
+ const u32 width_alignment = STRIDE_ALIGNMENT / BytesPerBlock(info.format);
+ return Extent2D{
+ .width = Common::AlignUp(num_tiles.width, width_alignment),
+ .height = num_tiles.height,
+ };
+}
+
+[[nodiscard]] Extent3D BlockLinearAlignedSize(const ImageInfo& info, u32 level) {
+ // https://github.com/Ryujinx/Ryujinx/blob/1c9aba6de1520aea5480c032e0ff5664ac1bb36f/Ryujinx.Graphics.Texture/SizeCalculator.cs#L176
+ ASSERT(info.type != ImageType::Linear);
+ const Extent3D size = AdjustMipSize(info.size, level);
+ const Extent3D num_tiles{
+ .width = Common::DivCeil(size.width, DefaultBlockWidth(info.format)),
+ .height = Common::DivCeil(size.height, DefaultBlockHeight(info.format)),
+ .depth = size.depth,
+ };
+ const u32 bpp_log2 = BytesPerBlockLog2(info.format);
+ const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing);
+ const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0);
+ return Extent3D{
+ .width = Common::AlignUpLog2(num_tiles.width, alignment),
+ .height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height),
+ .depth = Common::AlignUpLog2(num_tiles.depth, GOB_SIZE_Z_SHIFT + mip_block.depth),
+ };
+}
+
+[[nodiscard]] constexpr u32 NumBlocksPerLayer(const ImageInfo& info, Extent2D tile_size) noexcept {
+ u32 num_blocks = 0;
+ for (s32 level = 0; level < info.resources.levels; ++level) {
+ const Extent3D mip_size = AdjustMipSize(info.size, level);
+ num_blocks += NumBlocks(mip_size, tile_size);
+ }
+ return num_blocks;
+}
+
+[[nodiscard]] u32 NumSlices(const ImageInfo& info) noexcept {
+ ASSERT(info.type == ImageType::e3D);
+ u32 num_slices = 0;
+ for (s32 level = 0; level < info.resources.levels; ++level) {
+ num_slices += AdjustMipSize(info.size.depth, level);
+ }
+ return num_slices;
+}
+
+void SwizzlePitchLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
+ const ImageInfo& info, const BufferImageCopy& copy,
+ std::span<const u8> memory) {
+ ASSERT(copy.image_offset.z == 0);
+ ASSERT(copy.image_extent.depth == 1);
+ ASSERT(copy.image_subresource.base_level == 0);
+ ASSERT(copy.image_subresource.base_layer == 0);
+ ASSERT(copy.image_subresource.num_layers == 1);
+
+ const u32 bytes_per_block = BytesPerBlock(info.format);
+ const u32 row_length = copy.image_extent.width * bytes_per_block;
+ const u32 guest_offset_x = copy.image_offset.x * bytes_per_block;
+
+ for (u32 line = 0; line < copy.image_extent.height; ++line) {
+ const u32 host_offset_y = line * info.pitch;
+ const u32 guest_offset_y = (copy.image_offset.y + line) * info.pitch;
+ const u32 guest_offset = guest_offset_x + guest_offset_y;
+ gpu_memory.WriteBlockUnsafe(gpu_addr + guest_offset, memory.data() + host_offset_y,
+ row_length);
+ }
+}
+
+void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
+ const ImageInfo& info, const BufferImageCopy& copy,
+ std::span<const u8> input) {
+ const Extent3D size = info.size;
+ const LevelInfo level_info = MakeLevelInfo(info);
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+ const u32 bytes_per_block = BytesPerBlock(info.format);
+
+ const s32 level = copy.image_subresource.base_level;
+ const Extent3D level_size = AdjustMipSize(size, level);
+ const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size);
+ const u32 host_bytes_per_layer = num_blocks_per_layer * bytes_per_block;
+
+ UNIMPLEMENTED_IF(info.tile_width_spacing > 0);
+
+ UNIMPLEMENTED_IF(copy.image_offset.x != 0);
+ UNIMPLEMENTED_IF(copy.image_offset.y != 0);
+ UNIMPLEMENTED_IF(copy.image_offset.z != 0);
+ UNIMPLEMENTED_IF(copy.image_extent != level_size);
+
+ const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
+ const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+
+ size_t host_offset = copy.buffer_offset;
+
+ const u32 num_levels = info.resources.levels;
+ const std::array sizes = CalculateLevelSizes(level_info, num_levels);
+ size_t guest_offset = std::reduce(sizes.begin(), sizes.begin() + level, 0);
+ const size_t layer_stride =
+ AlignLayerSize(std::reduce(sizes.begin(), sizes.begin() + num_levels, 0), size,
+ level_info.block, tile_size.height, info.tile_width_spacing);
+ const size_t subresource_size = sizes[level];
+
+ const auto dst_data = std::make_unique<u8[]>(subresource_size);
+ const std::span<u8> dst(dst_data.get(), subresource_size);
+
+ for (s32 layer = 0; layer < info.resources.layers; ++layer) {
+ const std::span<const u8> src = input.subspan(host_offset);
+ SwizzleTexture(dst, src, bytes_per_block, num_tiles.width, num_tiles.height,
+ num_tiles.depth, block.height, block.depth);
+
+ gpu_memory.WriteBlockUnsafe(gpu_addr + guest_offset, dst.data(), dst.size_bytes());
+
+ host_offset += host_bytes_per_layer;
+ guest_offset += layer_stride;
+ }
+ ASSERT(host_offset - copy.buffer_offset == copy.buffer_size);
+}
+
+} // Anonymous namespace
+
+u32 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept {
+ if (info.type == ImageType::Buffer) {
+ return info.size.width * BytesPerBlock(info.format);
+ }
+ if (info.type == ImageType::Linear) {
+ return info.pitch * Common::DivCeil(info.size.height, DefaultBlockHeight(info.format));
+ }
+ if (info.resources.layers > 1) {
+ ASSERT(info.layer_stride != 0);
+ return info.layer_stride * info.resources.layers;
+ } else {
+ return CalculateLayerSize(info);
+ }
+}
+
+u32 CalculateUnswizzledSizeBytes(const ImageInfo& info) noexcept {
+ if (info.type == ImageType::Buffer) {
+ return info.size.width * BytesPerBlock(info.format);
+ }
+ if (info.num_samples > 1) {
+ // Multisample images can't be uploaded or downloaded to the host
+ return 0;
+ }
+ if (info.type == ImageType::Linear) {
+ return info.pitch * Common::DivCeil(info.size.height, DefaultBlockHeight(info.format));
+ }
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+ return NumBlocksPerLayer(info, tile_size) * info.resources.layers * BytesPerBlock(info.format);
+}
+
+u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept {
+ if (info.type == ImageType::Buffer) {
+ return info.size.width * BytesPerBlock(info.format);
+ }
+ static constexpr Extent2D TILE_SIZE{1, 1};
+ return NumBlocksPerLayer(info, TILE_SIZE) * info.resources.layers * CONVERTED_BYTES_PER_BLOCK;
+}
+
+u32 CalculateLayerStride(const ImageInfo& info) noexcept {
+ ASSERT(info.type != ImageType::Linear);
+ const u32 layer_size = CalculateLayerSize(info);
+ const Extent3D size = info.size;
+ const Extent3D block = info.block;
+ const u32 tile_size_y = DefaultBlockHeight(info.format);
+ return AlignLayerSize(layer_size, size, block, tile_size_y, info.tile_width_spacing);
+}
+
+u32 CalculateLayerSize(const ImageInfo& info) noexcept {
+ ASSERT(info.type != ImageType::Linear);
+ return CalculateLevelOffset(info.format, info.size, info.block, info.num_samples,
+ info.tile_width_spacing, info.resources.levels);
+}
+
+std::array<u32, MAX_MIP_LEVELS> CalculateMipLevelOffsets(const ImageInfo& info) noexcept {
+ ASSERT(info.resources.levels <= static_cast<s32>(MAX_MIP_LEVELS));
+ const LevelInfo level_info = MakeLevelInfo(info);
+ std::array<u32, MAX_MIP_LEVELS> offsets{};
+ u32 offset = 0;
+ for (s32 level = 0; level < info.resources.levels; ++level) {
+ offsets[level] = offset;
+ offset += CalculateLevelSize(level_info, level);
+ }
+ return offsets;
+}
+
+std::vector<u32> CalculateSliceOffsets(const ImageInfo& info) {
+ ASSERT(info.type == ImageType::e3D);
+ std::vector<u32> offsets;
+ offsets.reserve(NumSlices(info));
+
+ const LevelInfo level_info = MakeLevelInfo(info);
+ u32 mip_offset = 0;
+ for (s32 level = 0; level < info.resources.levels; ++level) {
+ const Extent3D tile_shift = TileShift(level_info, level);
+ const Extent3D tiles = LevelTiles(level_info, level);
+ const u32 gob_size_shift = tile_shift.height + GOB_SIZE_SHIFT;
+ const u32 slice_size = (tiles.width * tiles.height) << gob_size_shift;
+ const u32 z_mask = (1U << tile_shift.depth) - 1;
+ const u32 depth = AdjustMipSize(info.size.depth, level);
+ for (u32 slice = 0; slice < depth; ++slice) {
+ const u32 z_low = slice & z_mask;
+ const u32 z_high = slice & ~z_mask;
+ offsets.push_back(mip_offset + (z_low << gob_size_shift) + (z_high * slice_size));
+ }
+ mip_offset += CalculateLevelSize(level_info, level);
+ }
+ return offsets;
+}
+
+std::vector<SubresourceBase> CalculateSliceSubresources(const ImageInfo& info) {
+ ASSERT(info.type == ImageType::e3D);
+ std::vector<SubresourceBase> subresources;
+ subresources.reserve(NumSlices(info));
+ for (s32 level = 0; level < info.resources.levels; ++level) {
+ const s32 depth = AdjustMipSize(info.size.depth, level);
+ for (s32 slice = 0; slice < depth; ++slice) {
+ subresources.emplace_back(SubresourceBase{
+ .level = level,
+ .layer = slice,
+ });
+ }
+ }
+ return subresources;
+}
+
+u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level) {
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+ const Extent3D level_size = AdjustMipSize(info.size, level);
+ const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
+ const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level);
+ const u32 bpp_log2 = BytesPerBlockLog2(info.format);
+ return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing);
+}
+
+PixelFormat PixelFormatFromTIC(const TICEntry& config) noexcept {
+ return PixelFormatFromTextureInfo(config.format, config.r_type, config.g_type, config.b_type,
+ config.a_type, config.srgb_conversion);
+}
+
+ImageViewType RenderTargetImageViewType(const ImageInfo& info) noexcept {
+ switch (info.type) {
+ case ImageType::e2D:
+ return info.resources.layers > 1 ? ImageViewType::e2DArray : ImageViewType::e2D;
+ case ImageType::e3D:
+ return ImageViewType::e2DArray;
+ case ImageType::Linear:
+ return ImageViewType::e2D;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented image type={}", static_cast<int>(info.type));
+ return ImageViewType{};
+ }
+}
+
+std::vector<ImageCopy> MakeShrinkImageCopies(const ImageInfo& dst, const ImageInfo& src,
+ SubresourceBase base) {
+ ASSERT(dst.resources.levels >= src.resources.levels);
+ ASSERT(dst.num_samples == src.num_samples);
+
+ const bool is_dst_3d = dst.type == ImageType::e3D;
+ if (is_dst_3d) {
+ ASSERT(src.type == ImageType::e3D);
+ ASSERT(src.resources.levels == 1);
+ }
+
+ std::vector<ImageCopy> copies;
+ copies.reserve(src.resources.levels);
+ for (s32 level = 0; level < src.resources.levels; ++level) {
+ ImageCopy& copy = copies.emplace_back();
+ copy.src_subresource = SubresourceLayers{
+ .base_level = level,
+ .base_layer = 0,
+ .num_layers = src.resources.layers,
+ };
+ copy.dst_subresource = SubresourceLayers{
+ .base_level = base.level + level,
+ .base_layer = is_dst_3d ? 0 : base.layer,
+ .num_layers = is_dst_3d ? 1 : src.resources.layers,
+ };
+ copy.src_offset = Offset3D{
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ };
+ copy.dst_offset = Offset3D{
+ .x = 0,
+ .y = 0,
+ .z = is_dst_3d ? base.layer : 0,
+ };
+ const Extent3D mip_size = AdjustMipSize(dst.size, base.level + level);
+ copy.extent = AdjustSamplesSize(mip_size, dst.num_samples);
+ if (is_dst_3d) {
+ copy.extent.depth = src.size.depth;
+ }
+ }
+ return copies;
+}
+
+bool IsValidAddress(const Tegra::MemoryManager& gpu_memory, const TICEntry& config) {
+ if (config.Address() == 0) {
+ return false;
+ }
+ if (config.Address() > (u64(1) << 48)) {
+ return false;
+ }
+ return gpu_memory.GpuToCpuAddress(config.Address()).has_value();
+}
+
+std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
+ const ImageInfo& info, std::span<u8> output) {
+ const size_t guest_size_bytes = CalculateGuestSizeInBytes(info);
+ const u32 bpp_log2 = BytesPerBlockLog2(info.format);
+ const Extent3D size = info.size;
+
+ if (info.type == ImageType::Linear) {
+ gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), guest_size_bytes);
+
+ ASSERT((info.pitch >> bpp_log2) << bpp_log2 == info.pitch);
+ return {{
+ .buffer_offset = 0,
+ .buffer_size = guest_size_bytes,
+ .buffer_row_length = info.pitch >> bpp_log2,
+ .buffer_image_height = size.height,
+ .image_subresource =
+ {
+ .base_level = 0,
+ .base_layer = 0,
+ .num_layers = 1,
+ },
+ .image_offset = {0, 0, 0},
+ .image_extent = size,
+ }};
+ }
+ const auto input_data = std::make_unique<u8[]>(guest_size_bytes);
+ gpu_memory.ReadBlockUnsafe(gpu_addr, input_data.get(), guest_size_bytes);
+ const std::span<const u8> input(input_data.get(), guest_size_bytes);
+
+ const LevelInfo level_info = MakeLevelInfo(info);
+ const s32 num_layers = info.resources.layers;
+ const s32 num_levels = info.resources.levels;
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+ const std::array level_sizes = CalculateLevelSizes(level_info, num_levels);
+ const Extent2D gob = GobSize(bpp_log2, info.block.height, info.tile_width_spacing);
+ const u32 layer_size = std::reduce(level_sizes.begin(), level_sizes.begin() + num_levels, 0);
+ const u32 layer_stride = AlignLayerSize(layer_size, size, level_info.block, tile_size.height,
+ info.tile_width_spacing);
+ size_t guest_offset = 0;
+ u32 host_offset = 0;
+ std::vector<BufferImageCopy> copies(num_levels);
+
+ for (s32 level = 0; level < num_levels; ++level) {
+ const Extent3D level_size = AdjustMipSize(size, level);
+ const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size);
+ const u32 host_bytes_per_layer = num_blocks_per_layer << bpp_log2;
+ copies[level] = BufferImageCopy{
+ .buffer_offset = host_offset,
+ .buffer_size = static_cast<size_t>(host_bytes_per_layer) * num_layers,
+ .buffer_row_length = Common::AlignUp(level_size.width, tile_size.width),
+ .buffer_image_height = Common::AlignUp(level_size.height, tile_size.height),
+ .image_subresource =
+ {
+ .base_level = level,
+ .base_layer = 0,
+ .num_layers = info.resources.layers,
+ },
+ .image_offset = {0, 0, 0},
+ .image_extent = level_size,
+ };
+ const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
+ const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+ const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
+ size_t guest_layer_offset = 0;
+
+ for (s32 layer = 0; layer < info.resources.layers; ++layer) {
+ const std::span<u8> dst = output.subspan(host_offset);
+ const std::span<const u8> src = input.subspan(guest_offset + guest_layer_offset);
+ UnswizzleTexture(dst, src, 1U << bpp_log2, num_tiles.width, num_tiles.height,
+ num_tiles.depth, block.height, block.depth, stride_alignment);
+ guest_layer_offset += layer_stride;
+ host_offset += host_bytes_per_layer;
+ }
+ guest_offset += level_sizes[level];
+ }
+ return copies;
+}
+
+BufferCopy UploadBufferCopy(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
+ const ImageBase& image, std::span<u8> output) {
+ gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), image.guest_size_bytes);
+ return BufferCopy{
+ .src_offset = 0,
+ .dst_offset = 0,
+ .size = image.guest_size_bytes,
+ };
+}
+
+void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output,
+ std::span<BufferImageCopy> copies) {
+ u32 output_offset = 0;
+
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+ for (BufferImageCopy& copy : copies) {
+ const u32 level = copy.image_subresource.base_level;
+ const Extent3D mip_size = AdjustMipSize(info.size, level);
+ ASSERT(copy.image_offset == Offset3D{});
+ ASSERT(copy.image_subresource.base_layer == 0);
+ ASSERT(copy.image_extent == mip_size);
+ ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width));
+ ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height));
+
+ if (IsPixelFormatASTC(info.format)) {
+ ASSERT(copy.image_extent.depth == 1);
+ Tegra::Texture::ASTC::Decompress(input.subspan(copy.buffer_offset),
+ copy.image_extent.width, copy.image_extent.height,
+ copy.image_subresource.num_layers, tile_size.width,
+ tile_size.height, output.subspan(output_offset));
+ } else {
+ DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent,
+ output.subspan(output_offset));
+ }
+ copy.buffer_offset = output_offset;
+ copy.buffer_row_length = mip_size.width;
+ copy.buffer_image_height = mip_size.height;
+
+ output_offset += copy.image_extent.width * copy.image_extent.height *
+ copy.image_subresource.num_layers * CONVERTED_BYTES_PER_BLOCK;
+ }
+}
+
+std::vector<BufferImageCopy> FullDownloadCopies(const ImageInfo& info) {
+ const Extent3D size = info.size;
+ const u32 bytes_per_block = BytesPerBlock(info.format);
+ if (info.type == ImageType::Linear) {
+ ASSERT(info.pitch % bytes_per_block == 0);
+ return {{
+ .buffer_offset = 0,
+ .buffer_size = static_cast<size_t>(info.pitch) * size.height,
+ .buffer_row_length = info.pitch / bytes_per_block,
+ .buffer_image_height = size.height,
+ .image_subresource =
+ {
+ .base_level = 0,
+ .base_layer = 0,
+ .num_layers = 1,
+ },
+ .image_offset = {0, 0, 0},
+ .image_extent = size,
+ }};
+ }
+ UNIMPLEMENTED_IF(info.tile_width_spacing > 0);
+
+ const s32 num_layers = info.resources.layers;
+ const s32 num_levels = info.resources.levels;
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+
+ u32 host_offset = 0;
+
+ std::vector<BufferImageCopy> copies(num_levels);
+ for (s32 level = 0; level < num_levels; ++level) {
+ const Extent3D level_size = AdjustMipSize(size, level);
+ const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size);
+ const u32 host_bytes_per_level = num_blocks_per_layer * bytes_per_block * num_layers;
+ copies[level] = BufferImageCopy{
+ .buffer_offset = host_offset,
+ .buffer_size = host_bytes_per_level,
+ .buffer_row_length = level_size.width,
+ .buffer_image_height = level_size.height,
+ .image_subresource =
+ {
+ .base_level = level,
+ .base_layer = 0,
+ .num_layers = info.resources.layers,
+ },
+ .image_offset = {0, 0, 0},
+ .image_extent = level_size,
+ };
+ host_offset += host_bytes_per_level;
+ }
+ return copies;
+}
+
+Extent3D MipSize(Extent3D size, u32 level) {
+ return AdjustMipSize(size, level);
+}
+
+Extent3D MipBlockSize(const ImageInfo& info, u32 level) {
+ const LevelInfo level_info = MakeLevelInfo(info);
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+ const Extent3D level_size = AdjustMipSize(info.size, level);
+ const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
+ return AdjustMipBlockSize(num_tiles, level_info.block, level);
+}
+
+std::vector<SwizzleParameters> FullUploadSwizzles(const ImageInfo& info) {
+ const Extent2D tile_size = DefaultBlockSize(info.format);
+ if (info.type == ImageType::Linear) {
+ return std::vector{SwizzleParameters{
+ .num_tiles = AdjustTileSize(info.size, tile_size),
+ .block = {},
+ .buffer_offset = 0,
+ .level = 0,
+ }};
+ }
+ const LevelInfo level_info = MakeLevelInfo(info);
+ const Extent3D size = info.size;
+ const s32 num_levels = info.resources.levels;
+
+ u32 guest_offset = 0;
+ std::vector<SwizzleParameters> params(num_levels);
+ for (s32 level = 0; level < num_levels; ++level) {
+ const Extent3D level_size = AdjustMipSize(size, level);
+ const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
+ const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+ params[level] = SwizzleParameters{
+ .num_tiles = num_tiles,
+ .block = block,
+ .buffer_offset = guest_offset,
+ .level = level,
+ };
+ guest_offset += CalculateLevelSize(level_info, level);
+ }
+ return params;
+}
+
+void SwizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
+ std::span<const BufferImageCopy> copies, std::span<const u8> memory) {
+ const bool is_pitch_linear = info.type == ImageType::Linear;
+ for (const BufferImageCopy& copy : copies) {
+ if (is_pitch_linear) {
+ SwizzlePitchLinearImage(gpu_memory, gpu_addr, info, copy, memory);
+ } else {
+ SwizzleBlockLinearImage(gpu_memory, gpu_addr, info, copy, memory);
+ }
+ }
+}
+
+bool IsBlockLinearSizeCompatible(const ImageInfo& lhs, const ImageInfo& rhs, u32 lhs_level,
+ u32 rhs_level, bool strict_size) noexcept {
+ ASSERT(lhs.type != ImageType::Linear);
+ ASSERT(rhs.type != ImageType::Linear);
+ if (strict_size) {
+ const Extent3D lhs_size = AdjustMipSize(lhs.size, lhs_level);
+ const Extent3D rhs_size = AdjustMipSize(rhs.size, rhs_level);
+ return lhs_size.width == rhs_size.width && lhs_size.height == rhs_size.height;
+ } else {
+ const Extent3D lhs_size = BlockLinearAlignedSize(lhs, lhs_level);
+ const Extent3D rhs_size = BlockLinearAlignedSize(rhs, rhs_level);
+ return lhs_size.width == rhs_size.width && lhs_size.height == rhs_size.height;
+ }
+}
+
+bool IsPitchLinearSameSize(const ImageInfo& lhs, const ImageInfo& rhs, bool strict_size) noexcept {
+ ASSERT(lhs.type == ImageType::Linear);
+ ASSERT(rhs.type == ImageType::Linear);
+ if (strict_size) {
+ return lhs.size.width == rhs.size.width && lhs.size.height == rhs.size.height;
+ } else {
+ const Extent2D lhs_size = PitchLinearAlignedSize(lhs);
+ const Extent2D rhs_size = PitchLinearAlignedSize(rhs);
+ return lhs_size == rhs_size;
+ }
+}
+
+std::optional<OverlapResult> ResolveOverlap(const ImageInfo& new_info, GPUVAddr gpu_addr,
+ VAddr cpu_addr, const ImageBase& overlap,
+ bool strict_size, bool broken_views) {
+ ASSERT(new_info.type != ImageType::Linear);
+ ASSERT(overlap.info.type != ImageType::Linear);
+ if (!IsLayerStrideCompatible(new_info, overlap.info)) {
+ return std::nullopt;
+ }
+ if (!IsViewCompatible(overlap.info.format, new_info.format, broken_views)) {
+ return std::nullopt;
+ }
+ if (gpu_addr == overlap.gpu_addr) {
+ const std::optional solution = ResolveOverlapEqualAddress(new_info, overlap, strict_size);
+ if (!solution) {
+ return std::nullopt;
+ }
+ return OverlapResult{
+ .gpu_addr = gpu_addr,
+ .cpu_addr = cpu_addr,
+ .resources = *solution,
+ };
+ }
+ if (overlap.gpu_addr > gpu_addr) {
+ return ResolveOverlapRightAddress(new_info, gpu_addr, cpu_addr, overlap, strict_size);
+ }
+ // if overlap.gpu_addr < gpu_addr
+ return ResolveOverlapLeftAddress(new_info, gpu_addr, cpu_addr, overlap, strict_size);
+}
+
+bool IsLayerStrideCompatible(const ImageInfo& lhs, const ImageInfo& rhs) {
+ // If either of the layer strides is zero, we can assume they are compatible
+ // These images generally come from rendertargets
+ if (lhs.layer_stride == 0) {
+ return true;
+ }
+ if (rhs.layer_stride == 0) {
+ return true;
+ }
+ // It's definitely compatible if the layer stride matches
+ if (lhs.layer_stride == rhs.layer_stride) {
+ return true;
+ }
+ // Although we also have to compare for cases where it can be unaligned
+ // This can happen if the image doesn't have layers, so the stride is not aligned
+ if (lhs.maybe_unaligned_layer_stride == rhs.maybe_unaligned_layer_stride) {
+ return true;
+ }
+ return false;
+}
+
+std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const ImageBase& image,
+ GPUVAddr candidate_addr, RelaxedOptions options,
+ bool broken_views) {
+ const std::optional<SubresourceBase> base = image.TryFindBase(candidate_addr);
+ if (!base) {
+ return std::nullopt;
+ }
+ const ImageInfo& existing = image.info;
+ if (False(options & RelaxedOptions::Format)) {
+ if (!IsViewCompatible(existing.format, candidate.format, broken_views)) {
+ return std::nullopt;
+ }
+ }
+ if (!IsLayerStrideCompatible(existing, candidate)) {
+ return std::nullopt;
+ }
+ if (existing.type != candidate.type) {
+ return std::nullopt;
+ }
+ if (False(options & RelaxedOptions::Samples)) {
+ if (existing.num_samples != candidate.num_samples) {
+ return std::nullopt;
+ }
+ }
+ if (existing.resources.levels < candidate.resources.levels + base->level) {
+ return std::nullopt;
+ }
+ if (existing.type == ImageType::e3D) {
+ const u32 mip_depth = std::max(1U, existing.size.depth << base->level);
+ if (mip_depth < candidate.size.depth + base->layer) {
+ return std::nullopt;
+ }
+ } else {
+ if (existing.resources.layers < candidate.resources.layers + base->layer) {
+ return std::nullopt;
+ }
+ }
+ const bool strict_size = False(options & RelaxedOptions::Size);
+ if (!IsBlockLinearSizeCompatible(existing, candidate, base->level, 0, strict_size)) {
+ return std::nullopt;
+ }
+ // TODO: compare block sizes
+ return base;
+}
+
+bool IsSubresource(const ImageInfo& candidate, const ImageBase& image, GPUVAddr candidate_addr,
+ RelaxedOptions options, bool broken_views) {
+ return FindSubresource(candidate, image, candidate_addr, options, broken_views).has_value();
+}
+
+void DeduceBlitImages(ImageInfo& dst_info, ImageInfo& src_info, const ImageBase* dst,
+ const ImageBase* src) {
+ if (src && GetFormatType(src->info.format) != SurfaceType::ColorTexture) {
+ src_info.format = src->info.format;
+ }
+ if (dst && GetFormatType(dst->info.format) != SurfaceType::ColorTexture) {
+ dst_info.format = dst->info.format;
+ }
+ if (!dst && src && GetFormatType(src->info.format) != SurfaceType::ColorTexture) {
+ dst_info.format = src->info.format;
+ }
+ if (!src && dst && GetFormatType(dst->info.format) != SurfaceType::ColorTexture) {
+ src_info.format = src->info.format;
+ }
+}
+
+u32 MapSizeBytes(const ImageBase& image) {
+ if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
+ return image.guest_size_bytes;
+ } else if (True(image.flags & ImageFlagBits::Converted)) {
+ return image.converted_size_bytes;
+ } else {
+ return image.unswizzled_size_bytes;
+ }
+}
+
+static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) ==
+ 0x7f8000);
+static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x4000);
+
+static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 1, 0, 7) ==
+ 0x2afc00);
+static_assert(CalculateLevelOffset(PixelFormat::ASTC_2D_12X12_UNORM, {8192, 4096, 1}, {0, 2, 0}, 1,
+ 0, 12) == 0x50d200);
+
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 0) == 0);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 1) == 0x400000);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 2) == 0x500000);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 3) == 0x540000);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 4) == 0x550000);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 5) == 0x554000);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 6) == 0x555000);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 7) == 0x555400);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 8) == 0x555600);
+static_assert(CalculateLevelOffset(PixelFormat::A8B8G8R8_UNORM, {1024, 1024, 1}, {0, 4, 0}, 1, 0,
+ 9) == 0x555800);
+
+constexpr u32 ValidateLayerSize(PixelFormat format, u32 width, u32 height, u32 block_height,
+ u32 tile_width_spacing, u32 level) {
+ const Extent3D size{width, height, 1};
+ const Extent3D block{0, block_height, 0};
+ const u32 offset = CalculateLevelOffset(format, size, block, 1, tile_width_spacing, level);
+ return AlignLayerSize(offset, size, block, DefaultBlockHeight(format), tile_width_spacing);
+}
+
+static_assert(ValidateLayerSize(PixelFormat::ASTC_2D_12X12_UNORM, 8192, 4096, 2, 0, 12) ==
+ 0x50d800);
+static_assert(ValidateLayerSize(PixelFormat::A8B8G8R8_UNORM, 1024, 1024, 2, 0, 10) == 0x556000);
+static_assert(ValidateLayerSize(PixelFormat::BC3_UNORM, 128, 128, 2, 0, 8) == 0x6000);
+
+static_assert(ValidateLayerSize(PixelFormat::A8B8G8R8_UNORM, 518, 572, 4, 3, 1) == 0x190000,
+ "Tile width spacing is not working");
+static_assert(ValidateLayerSize(PixelFormat::BC5_UNORM, 1024, 1024, 3, 4, 11) == 0x160000,
+ "Compressed tile width spacing is not working");
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h
new file mode 100644
index 000000000..52a9207d6
--- /dev/null
+++ b/src/video_core/texture_cache/util.h
@@ -0,0 +1,109 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <span>
+
+#include "common/common_types.h"
+
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/image_base.h"
+#include "video_core/texture_cache/image_view_base.h"
+#include "video_core/texture_cache/types.h"
+#include "video_core/textures/texture.h"
+
+namespace VideoCommon {
+
+using Tegra::Texture::TICEntry;
+
+struct OverlapResult {
+ GPUVAddr gpu_addr;
+ VAddr cpu_addr;
+ SubresourceExtent resources;
+};
+
+[[nodiscard]] u32 CalculateGuestSizeInBytes(const ImageInfo& info) noexcept;
+
+[[nodiscard]] u32 CalculateUnswizzledSizeBytes(const ImageInfo& info) noexcept;
+
+[[nodiscard]] u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept;
+
+[[nodiscard]] u32 CalculateLayerStride(const ImageInfo& info) noexcept;
+
+[[nodiscard]] u32 CalculateLayerSize(const ImageInfo& info) noexcept;
+
+[[nodiscard]] std::array<u32, MAX_MIP_LEVELS> CalculateMipLevelOffsets(
+ const ImageInfo& info) noexcept;
+
+[[nodiscard]] std::vector<u32> CalculateSliceOffsets(const ImageInfo& info);
+
+[[nodiscard]] std::vector<SubresourceBase> CalculateSliceSubresources(const ImageInfo& info);
+
+[[nodiscard]] u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level);
+
+[[nodiscard]] VideoCore::Surface::PixelFormat PixelFormatFromTIC(
+ const Tegra::Texture::TICEntry& config) noexcept;
+
+[[nodiscard]] ImageViewType RenderTargetImageViewType(const ImageInfo& info) noexcept;
+
+[[nodiscard]] std::vector<ImageCopy> MakeShrinkImageCopies(const ImageInfo& dst,
+ const ImageInfo& src,
+ SubresourceBase base);
+
+[[nodiscard]] bool IsValidAddress(const Tegra::MemoryManager& gpu_memory, const TICEntry& config);
+
+[[nodiscard]] std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory,
+ GPUVAddr gpu_addr, const ImageInfo& info,
+ std::span<u8> output);
+
+[[nodiscard]] BufferCopy UploadBufferCopy(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
+ const ImageBase& image, std::span<u8> output);
+
+void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output,
+ std::span<BufferImageCopy> copies);
+
+[[nodiscard]] std::vector<BufferImageCopy> FullDownloadCopies(const ImageInfo& info);
+
+[[nodiscard]] Extent3D MipSize(Extent3D size, u32 level);
+
+[[nodiscard]] Extent3D MipBlockSize(const ImageInfo& info, u32 level);
+
+[[nodiscard]] std::vector<SwizzleParameters> FullUploadSwizzles(const ImageInfo& info);
+
+void SwizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
+ std::span<const BufferImageCopy> copies, std::span<const u8> memory);
+
+[[nodiscard]] bool IsBlockLinearSizeCompatible(const ImageInfo& new_info,
+ const ImageInfo& overlap_info, u32 new_level,
+ u32 overlap_level, bool strict_size) noexcept;
+
+[[nodiscard]] bool IsPitchLinearSameSize(const ImageInfo& lhs, const ImageInfo& rhs,
+ bool strict_size) noexcept;
+
+[[nodiscard]] std::optional<OverlapResult> ResolveOverlap(const ImageInfo& new_info,
+ GPUVAddr gpu_addr, VAddr cpu_addr,
+ const ImageBase& overlap,
+ bool strict_size, bool broken_views);
+
+[[nodiscard]] bool IsLayerStrideCompatible(const ImageInfo& lhs, const ImageInfo& rhs);
+
+[[nodiscard]] std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate,
+ const ImageBase& image,
+ GPUVAddr candidate_addr,
+ RelaxedOptions options,
+ bool broken_views);
+
+[[nodiscard]] bool IsSubresource(const ImageInfo& candidate, const ImageBase& image,
+ GPUVAddr candidate_addr, RelaxedOptions options,
+ bool broken_views);
+
+void DeduceBlitImages(ImageInfo& dst_info, ImageInfo& src_info, const ImageBase* dst,
+ const ImageBase* src);
+
+[[nodiscard]] u32 MapSizeBytes(const ImageBase& image);
+
+} // namespace VideoCommon
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index 365bde2f1..3625b666c 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -18,6 +18,7 @@
#include <algorithm>
#include <cassert>
#include <cstring>
+#include <span>
#include <vector>
#include <boost/container/static_vector.hpp>
@@ -41,21 +42,24 @@ constexpr u32 Popcnt(u32 n) {
class InputBitStream {
public:
- constexpr explicit InputBitStream(const u8* ptr, std::size_t start_offset = 0)
- : cur_byte{ptr}, next_bit{start_offset % 8} {}
+ constexpr explicit InputBitStream(std::span<const u8> data, size_t start_offset = 0)
+ : cur_byte{data.data()}, total_bits{data.size()}, next_bit{start_offset % 8} {}
- constexpr std::size_t GetBitsRead() const {
+ constexpr size_t GetBitsRead() const {
return bits_read;
}
constexpr bool ReadBit() {
- const bool bit = (*cur_byte >> next_bit++) & 1;
+ if (bits_read >= total_bits * 8) {
+ return 0;
+ }
+ const bool bit = ((*cur_byte >> next_bit) & 1) != 0;
+ ++next_bit;
while (next_bit >= 8) {
next_bit -= 8;
- cur_byte++;
+ ++cur_byte;
}
-
- bits_read++;
+ ++bits_read;
return bit;
}
@@ -78,8 +82,9 @@ public:
private:
const u8* cur_byte;
- std::size_t next_bit = 0;
- std::size_t bits_read = 0;
+ size_t total_bits = 0;
+ size_t next_bit = 0;
+ size_t bits_read = 0;
};
class OutputBitStream {
@@ -192,15 +197,15 @@ struct IntegerEncodedValue {
};
};
using IntegerEncodedVector = boost::container::static_vector<
- IntegerEncodedValue, 64,
+ IntegerEncodedValue, 256,
boost::container::static_vector_options<
boost::container::inplace_alignment<alignof(IntegerEncodedValue)>,
boost::container::throw_on_overflow<false>>::type>;
static void DecodeTritBlock(InputBitStream& bits, IntegerEncodedVector& result, u32 nBitsPerValue) {
// Implement the algorithm in section C.2.12
- u32 m[5];
- u32 t[5];
+ std::array<u32, 5> m;
+ std::array<u32, 5> t;
u32 T;
// Read the trit encoded block according to
@@ -600,7 +605,7 @@ static TexelWeightParams DecodeBlockInfo(InputBitStream& strm) {
return params;
}
-static void FillVoidExtentLDR(InputBitStream& strm, u32* const outBuf, u32 blockWidth,
+static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 blockWidth,
u32 blockHeight) {
// Don't actually care about the void extent, just read the bits...
for (s32 i = 0; i < 4; ++i) {
@@ -623,7 +628,7 @@ static void FillVoidExtentLDR(InputBitStream& strm, u32* const outBuf, u32 block
}
}
-static void FillError(u32* outBuf, u32 blockWidth, u32 blockHeight) {
+static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
for (u32 j = 0; j < blockHeight; j++) {
for (u32 i = 0; i < blockWidth; i++) {
outBuf[j * blockWidth + i] = 0xFFFF00FF;
@@ -865,7 +870,7 @@ public:
}
};
-static void DecodeColorValues(u32* out, u8* data, const u32* modes, const u32 nPartitions,
+static void DecodeColorValues(u32* out, std::span<u8> data, const u32* modes, const u32 nPartitions,
const u32 nBitsForColorData) {
// First figure out how many color values we have
u32 nValues = 0;
@@ -897,7 +902,7 @@ static void DecodeColorValues(u32* out, u8* data, const u32* modes, const u32 nP
// We now have enough to decode our integer sequence.
IntegerEncodedVector decodedColorValues;
- InputBitStream colorStream(data);
+ InputBitStream colorStream(data, 0);
DecodeIntegerSequence(decodedColorValues, colorStream, range, nValues);
// Once we have the decoded values, we need to dequantize them to the 0-255 range
@@ -1438,8 +1443,8 @@ static void ComputeEndpos32s(Pixel& ep1, Pixel& ep2, const u32*& colorValues,
#undef READ_INT_VALUES
}
-static void DecompressBlock(const u8 inBuf[16], const u32 blockWidth, const u32 blockHeight,
- u32* outBuf) {
+static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth,
+ const u32 blockHeight, std::span<u32, 12 * 12> outBuf) {
InputBitStream strm(inBuf);
TexelWeightParams weightParams = DecodeBlockInfo(strm);
@@ -1601,8 +1606,8 @@ static void DecompressBlock(const u8 inBuf[16], const u32 blockWidth, const u32
}
// Read the texel weight data..
- u8 texelWeightData[16];
- memcpy(texelWeightData, inBuf, sizeof(texelWeightData));
+ std::array<u8, 16> texelWeightData;
+ std::ranges::copy(inBuf, texelWeightData.begin());
// Reverse everything
for (u32 i = 0; i < 8; i++) {
@@ -1618,10 +1623,12 @@ static void DecompressBlock(const u8 inBuf[16], const u32 blockWidth, const u32
// Make sure that higher non-texel bits are set to zero
const u32 clearByteStart = (weightParams.GetPackedBitSize() >> 3) + 1;
- texelWeightData[clearByteStart - 1] =
- texelWeightData[clearByteStart - 1] &
- static_cast<u8>((1 << (weightParams.GetPackedBitSize() % 8)) - 1);
- memset(texelWeightData + clearByteStart, 0, 16 - clearByteStart);
+ if (clearByteStart > 0 && clearByteStart <= texelWeightData.size()) {
+ texelWeightData[clearByteStart - 1] &=
+ static_cast<u8>((1 << (weightParams.GetPackedBitSize() % 8)) - 1);
+ std::memset(texelWeightData.data() + clearByteStart, 0,
+ std::min(16U - clearByteStart, 16U));
+ }
IntegerEncodedVector texelWeightValues;
@@ -1672,36 +1679,32 @@ static void DecompressBlock(const u8 inBuf[16], const u32 blockWidth, const u32
namespace Tegra::Texture::ASTC {
-std::vector<u8> Decompress(const u8* data, u32 width, u32 height, u32 depth, u32 block_width,
- u32 block_height) {
- u32 blockIdx = 0;
+void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
+ uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) {
+ u32 block_index = 0;
std::size_t depth_offset = 0;
- std::vector<u8> outData(height * width * depth * 4);
- for (u32 k = 0; k < depth; k++) {
- for (u32 j = 0; j < height; j += block_height) {
- for (u32 i = 0; i < width; i += block_width) {
-
- const u8* blockPtr = data + blockIdx * 16;
+ for (u32 z = 0; z < depth; z++) {
+ for (u32 y = 0; y < height; y += block_height) {
+ for (u32 x = 0; x < width; x += block_width) {
+ const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)};
// Blocks can be at most 12x12
- u32 uncompData[144];
+ std::array<u32, 12 * 12> uncompData;
ASTCC::DecompressBlock(blockPtr, block_width, block_height, uncompData);
- u32 decompWidth = std::min(block_width, width - i);
- u32 decompHeight = std::min(block_height, height - j);
+ u32 decompWidth = std::min(block_width, width - x);
+ u32 decompHeight = std::min(block_height, height - y);
- u8* outRow = depth_offset + outData.data() + (j * width + i) * 4;
+ const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4);
for (u32 jj = 0; jj < decompHeight; jj++) {
- memcpy(outRow + jj * width * 4, uncompData + jj * block_width, decompWidth * 4);
+ std::memcpy(outRow.data() + jj * width * 4,
+ uncompData.data() + jj * block_width, decompWidth * 4);
}
-
- blockIdx++;
+ ++block_index;
}
}
depth_offset += height * width * 4;
}
-
- return outData;
}
} // namespace Tegra::Texture::ASTC
diff --git a/src/video_core/textures/astc.h b/src/video_core/textures/astc.h
index 991cdba72..9105119bc 100644
--- a/src/video_core/textures/astc.h
+++ b/src/video_core/textures/astc.h
@@ -5,11 +5,10 @@
#pragma once
#include <cstdint>
-#include <vector>
namespace Tegra::Texture::ASTC {
-std::vector<uint8_t> Decompress(const uint8_t* data, uint32_t width, uint32_t height,
- uint32_t depth, uint32_t block_width, uint32_t block_height);
+void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
+ uint32_t block_width, uint32_t block_height, std::span<uint8_t> output);
} // namespace Tegra::Texture::ASTC
diff --git a/src/video_core/textures/convert.cpp b/src/video_core/textures/convert.cpp
deleted file mode 100644
index 962921483..000000000
--- a/src/video_core/textures/convert.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cstring>
-#include <tuple>
-#include <vector>
-
-#include "common/assert.h"
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "video_core/surface.h"
-#include "video_core/textures/astc.h"
-#include "video_core/textures/convert.h"
-
-namespace Tegra::Texture {
-
-using VideoCore::Surface::PixelFormat;
-
-template <bool reverse>
-void SwapS8Z24ToZ24S8(u8* data, u32 width, u32 height) {
- union S8Z24 {
- BitField<0, 24, u32> z24;
- BitField<24, 8, u32> s8;
- };
- static_assert(sizeof(S8Z24) == 4, "S8Z24 is incorrect size");
-
- union Z24S8 {
- BitField<0, 8, u32> s8;
- BitField<8, 24, u32> z24;
- };
- static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size");
-
- S8Z24 s8z24_pixel{};
- Z24S8 z24s8_pixel{};
- constexpr auto bpp{
- VideoCore::Surface::GetBytesPerPixel(VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM)};
- for (std::size_t y = 0; y < height; ++y) {
- for (std::size_t x = 0; x < width; ++x) {
- const std::size_t offset{bpp * (y * width + x)};
- if constexpr (reverse) {
- std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8));
- s8z24_pixel.s8.Assign(z24s8_pixel.s8);
- s8z24_pixel.z24.Assign(z24s8_pixel.z24);
- std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24));
- } else {
- std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24));
- z24s8_pixel.s8.Assign(s8z24_pixel.s8);
- z24s8_pixel.z24.Assign(s8z24_pixel.z24);
- std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8));
- }
- }
- }
-}
-
-static void ConvertS8Z24ToZ24S8(u8* data, u32 width, u32 height) {
- SwapS8Z24ToZ24S8<false>(data, width, height);
-}
-
-static void ConvertZ24S8ToS8Z24(u8* data, u32 width, u32 height) {
- SwapS8Z24ToZ24S8<true>(data, width, height);
-}
-
-void ConvertFromGuestToHost(u8* in_data, u8* out_data, PixelFormat pixel_format, u32 width,
- u32 height, u32 depth, bool convert_astc, bool convert_s8z24) {
- if (convert_astc && IsPixelFormatASTC(pixel_format)) {
- // Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
- u32 block_width{};
- u32 block_height{};
- std::tie(block_width, block_height) = GetASTCBlockSize(pixel_format);
- const std::vector<u8> rgba8_data = Tegra::Texture::ASTC::Decompress(
- in_data, width, height, depth, block_width, block_height);
- std::copy(rgba8_data.begin(), rgba8_data.end(), out_data);
-
- } else if (convert_s8z24 && pixel_format == PixelFormat::S8_UINT_D24_UNORM) {
- Tegra::Texture::ConvertS8Z24ToZ24S8(in_data, width, height);
- }
-}
-
-void ConvertFromHostToGuest(u8* data, PixelFormat pixel_format, u32 width, u32 height, u32 depth,
- bool convert_astc, bool convert_s8z24) {
- if (convert_astc && IsPixelFormatASTC(pixel_format)) {
- LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented",
- static_cast<u32>(pixel_format));
- UNREACHABLE();
-
- } else if (convert_s8z24 && pixel_format == PixelFormat::S8_UINT_D24_UNORM) {
- Tegra::Texture::ConvertZ24S8ToS8Z24(data, width, height);
- }
-}
-
-} // namespace Tegra::Texture
diff --git a/src/video_core/textures/convert.h b/src/video_core/textures/convert.h
deleted file mode 100644
index d5d6c77bb..000000000
--- a/src/video_core/textures/convert.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_types.h"
-
-namespace VideoCore::Surface {
-enum class PixelFormat;
-}
-
-namespace Tegra::Texture {
-
-void ConvertFromGuestToHost(u8* in_data, u8* out_data, VideoCore::Surface::PixelFormat pixel_format,
- u32 width, u32 height, u32 depth, bool convert_astc,
- bool convert_s8z24);
-
-void ConvertFromHostToGuest(u8* data, VideoCore::Surface::PixelFormat pixel_format, u32 width,
- u32 height, u32 depth, bool convert_astc, bool convert_s8z24);
-
-} // namespace Tegra::Texture
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 16d46a018..62685a183 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -2,204 +2,111 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
#include <cmath>
#include <cstring>
+#include <span>
+#include <utility>
+
#include "common/alignment.h"
#include "common/assert.h"
#include "common/bit_util.h"
+#include "common/div_ceil.h"
#include "video_core/gpu.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace Tegra::Texture {
-namespace {
+namespace {
/**
- * This table represents the internal swizzle of a gob,
- * in format 16 bytes x 2 sector packing.
+ * This table represents the internal swizzle of a gob, in format 16 bytes x 2 sector packing.
* Calculates the offset of an (x, y) position within a swizzled texture.
* Taken from the Tegra X1 Technical Reference Manual. pages 1187-1188
*/
-template <std::size_t N, std::size_t M, u32 Align>
-struct alignas(64) SwizzleTable {
- static_assert(M * Align == 64, "Swizzle Table does not align to GOB");
- constexpr SwizzleTable() {
- for (u32 y = 0; y < N; ++y) {
- for (u32 x = 0; x < M; ++x) {
- const u32 x2 = x * Align;
- values[y][x] = static_cast<u16>(((x2 % 64) / 32) * 256 + ((y % 8) / 2) * 64 +
- ((x2 % 32) / 16) * 32 + (y % 2) * 16 + (x2 % 16));
- }
+constexpr SwizzleTable MakeSwizzleTableConst() {
+ SwizzleTable table{};
+ for (u32 y = 0; y < table.size(); ++y) {
+ for (u32 x = 0; x < table[0].size(); ++x) {
+ table[y][x] = ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
+ (y % 2) * 16 + (x % 16);
}
}
- const std::array<u16, M>& operator[](std::size_t index) const {
- return values[index];
- }
- std::array<std::array<u16, M>, N> values{};
-};
+ return table;
+}
-constexpr u32 FAST_SWIZZLE_ALIGN = 16;
+constexpr SwizzleTable SWIZZLE_TABLE = MakeSwizzleTableConst();
-constexpr auto LEGACY_SWIZZLE_TABLE = SwizzleTable<GOB_SIZE_X, GOB_SIZE_X, GOB_SIZE_Z>();
-constexpr auto FAST_SWIZZLE_TABLE = SwizzleTable<GOB_SIZE_Y, 4, FAST_SWIZZLE_ALIGN>();
+template <bool TO_LINEAR>
+void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
+ u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) {
+ // The origin of the transformation can be configured here, leave it as zero as the current API
+ // doesn't expose it.
+ static constexpr u32 origin_x = 0;
+ static constexpr u32 origin_y = 0;
+ static constexpr u32 origin_z = 0;
-/**
- * This function manages ALL the GOBs(Group of Bytes) Inside a single block.
- * Instead of going gob by gob, we map the coordinates inside a block and manage from
- * those. Block_Width is assumed to be 1.
- */
-void PreciseProcessBlock(u8* const swizzled_data, u8* const unswizzled_data, const bool unswizzle,
- const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end,
- const u32 y_end, const u32 z_end, const u32 tile_offset,
- const u32 xy_block_size, const u32 layer_z, const u32 stride_x,
- const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) {
- std::array<u8*, 2> data_ptrs;
- u32 z_address = tile_offset;
-
- for (u32 z = z_start; z < z_end; z++) {
- u32 y_address = z_address;
- u32 pixel_base = layer_z * z + y_start * stride_x;
- for (u32 y = y_start; y < y_end; y++) {
- const auto& table = LEGACY_SWIZZLE_TABLE[y % GOB_SIZE_Y];
- for (u32 x = x_start; x < x_end; x++) {
- const u32 swizzle_offset{y_address + table[x * bytes_per_pixel % GOB_SIZE_X]};
- const u32 pixel_index{x * out_bytes_per_pixel + pixel_base};
- data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
- data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
- std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
- }
- pixel_base += stride_x;
- if ((y + 1) % GOB_SIZE_Y == 0)
- y_address += GOB_SIZE;
- }
- z_address += xy_block_size;
- }
-}
+ // We can configure here a custom pitch
+ // As it's not exposed 'width * bpp' will be the expected pitch.
+ const u32 pitch = width * bytes_per_pixel;
+ const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel;
-/**
- * This function manages ALL the GOBs(Group of Bytes) Inside a single block.
- * Instead of going gob by gob, we map the coordinates inside a block and manage from
- * those. Block_Width is assumed to be 1.
- */
-void FastProcessBlock(u8* const swizzled_data, u8* const unswizzled_data, const bool unswizzle,
- const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end,
- const u32 y_end, const u32 z_end, const u32 tile_offset,
- const u32 xy_block_size, const u32 layer_z, const u32 stride_x,
- const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) {
- std::array<u8*, 2> data_ptrs;
- u32 z_address = tile_offset;
- const u32 x_startb = x_start * bytes_per_pixel;
- const u32 x_endb = x_end * bytes_per_pixel;
-
- for (u32 z = z_start; z < z_end; z++) {
- u32 y_address = z_address;
- u32 pixel_base = layer_z * z + y_start * stride_x;
- for (u32 y = y_start; y < y_end; y++) {
- const auto& table = FAST_SWIZZLE_TABLE[y % GOB_SIZE_Y];
- for (u32 xb = x_startb; xb < x_endb; xb += FAST_SWIZZLE_ALIGN) {
- const u32 swizzle_offset{y_address + table[(xb / FAST_SWIZZLE_ALIGN) % 4]};
- const u32 out_x = xb * out_bytes_per_pixel / bytes_per_pixel;
- const u32 pixel_index{out_x + pixel_base};
- data_ptrs[unswizzle ? 1 : 0] = swizzled_data + swizzle_offset;
- data_ptrs[unswizzle ? 0 : 1] = unswizzled_data + pixel_index;
- std::memcpy(data_ptrs[0], data_ptrs[1], FAST_SWIZZLE_ALIGN);
- }
- pixel_base += stride_x;
- if ((y + 1) % GOB_SIZE_Y == 0)
- y_address += GOB_SIZE;
- }
- z_address += xy_block_size;
- }
-}
+ const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
+ const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
+ const u32 slice_size =
+ Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size;
-/**
- * This function unswizzles or swizzles a texture by mapping Linear to BlockLinear Textue.
- * The body of this function takes care of splitting the swizzled texture into blocks,
- * and managing the extents of it. Once all the parameters of a single block are obtained,
- * the function calls 'ProcessBlock' to process that particular Block.
- *
- * Documentation for the memory layout and decoding can be found at:
- * https://envytools.readthedocs.io/en/latest/hw/memory/g80-surface.html#blocklinear-surfaces
- */
-template <bool fast>
-void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool unswizzle,
- const u32 width, const u32 height, const u32 depth, const u32 bytes_per_pixel,
- const u32 out_bytes_per_pixel, const u32 block_height, const u32 block_depth,
- const u32 width_spacing) {
- auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
- const u32 stride_x = width * out_bytes_per_pixel;
- const u32 layer_z = height * stride_x;
- const u32 gob_elements_x = GOB_SIZE_X / bytes_per_pixel;
- constexpr u32 gob_elements_y = GOB_SIZE_Y;
- constexpr u32 gob_elements_z = GOB_SIZE_Z;
- const u32 block_x_elements = gob_elements_x;
- const u32 block_y_elements = gob_elements_y * block_height;
- const u32 block_z_elements = gob_elements_z * block_depth;
- const u32 aligned_width = Common::AlignUp(width, gob_elements_x * width_spacing);
- const u32 blocks_on_x = div_ceil(aligned_width, block_x_elements);
- const u32 blocks_on_y = div_ceil(height, block_y_elements);
- const u32 blocks_on_z = div_ceil(depth, block_z_elements);
- const u32 xy_block_size = GOB_SIZE * block_height;
- const u32 block_size = xy_block_size * block_depth;
- u32 tile_offset = 0;
- for (u32 zb = 0; zb < blocks_on_z; zb++) {
- const u32 z_start = zb * block_z_elements;
- const u32 z_end = std::min(depth, z_start + block_z_elements);
- for (u32 yb = 0; yb < blocks_on_y; yb++) {
- const u32 y_start = yb * block_y_elements;
- const u32 y_end = std::min(height, y_start + block_y_elements);
- for (u32 xb = 0; xb < blocks_on_x; xb++) {
- const u32 x_start = xb * block_x_elements;
- const u32 x_end = std::min(width, x_start + block_x_elements);
- if constexpr (fast) {
- FastProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start,
- z_start, x_end, y_end, z_end, tile_offset, xy_block_size,
- layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel);
- } else {
- PreciseProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start,
- z_start, x_end, y_end, z_end, tile_offset, xy_block_size,
- layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel);
- }
- tile_offset += block_size;
+ const u32 block_height_mask = (1U << block_height) - 1;
+ const u32 block_depth_mask = (1U << block_depth) - 1;
+ const u32 x_shift = GOB_SIZE_SHIFT + block_height + block_depth;
+
+ for (u32 slice = 0; slice < depth; ++slice) {
+ const u32 z = slice + origin_z;
+ const u32 offset_z = (z >> block_depth) * slice_size +
+ ((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
+ for (u32 line = 0; line < height; ++line) {
+ const u32 y = line + origin_y;
+ const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y];
+
+ const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
+ const u32 offset_y = (block_y >> block_height) * block_size +
+ ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
+
+ for (u32 column = 0; column < width; ++column) {
+ const u32 x = (column + origin_x) * bytes_per_pixel;
+ const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
+
+ const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
+ const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X];
+
+ const u32 unswizzled_offset =
+ slice * pitch * height + line * pitch + column * bytes_per_pixel;
+
+ u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
+ const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
+ std::memcpy(dst, src, bytes_per_pixel);
}
}
}
}
-
} // Anonymous namespace
-void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
- u32 out_bytes_per_pixel, u8* const swizzled_data, u8* const unswizzled_data,
- bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing) {
- const u32 block_height_size{1U << block_height};
- const u32 block_depth_size{1U << block_depth};
- if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % FAST_SWIZZLE_ALIGN == 0) {
- SwizzledData<true>(swizzled_data, unswizzled_data, unswizzle, width, height, depth,
- bytes_per_pixel, out_bytes_per_pixel, block_height_size,
- block_depth_size, width_spacing);
- } else {
- SwizzledData<false>(swizzled_data, unswizzled_data, unswizzle, width, height, depth,
- bytes_per_pixel, out_bytes_per_pixel, block_height_size,
- block_depth_size, width_spacing);
- }
+SwizzleTable MakeSwizzleTable() {
+ return SWIZZLE_TABLE;
}
-void UnswizzleTexture(u8* const unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
- u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height,
- u32 block_depth, u32 width_spacing) {
- CopySwizzledData((width + tile_size_x - 1) / tile_size_x,
- (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel,
- bytes_per_pixel, address, unswizzled_data, true, block_height, block_depth,
- width_spacing);
+void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
+ u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
+ u32 stride_alignment) {
+ Swizzle<false>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth,
+ stride_alignment);
}
-std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel,
- u32 width, u32 height, u32 depth, u32 block_height,
- u32 block_depth, u32 width_spacing) {
- std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
- UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel,
- width, height, depth, block_height, block_depth, width_spacing);
- return unswizzled_data;
+void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
+ u32 height, u32 depth, u32 block_height, u32 block_depth,
+ u32 stride_alignment) {
+ Swizzle<true>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth,
+ stride_alignment);
}
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
@@ -213,7 +120,7 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
const u32 gob_address_y =
(dst_y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs +
((dst_y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
- const auto& table = LEGACY_SWIZZLE_TABLE[dst_y % GOB_SIZE_Y];
+ const auto& table = SWIZZLE_TABLE[dst_y % GOB_SIZE_Y];
for (u32 x = 0; x < subrect_width; ++x) {
const u32 dst_x = x + offset_x;
const u32 gob_address =
@@ -235,11 +142,11 @@ void UnswizzleSubrect(u32 line_length_in, u32 line_count, u32 pitch, u32 width,
const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height);
const u32 block_height_mask = (1U << block_height) - 1;
- const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height;
+ const u32 x_shift = GOB_SIZE_SHIFT + block_height;
for (u32 line = 0; line < line_count; ++line) {
const u32 src_y = line + origin_y;
- const auto& table = LEGACY_SWIZZLE_TABLE[src_y % GOB_SIZE_Y];
+ const auto& table = SWIZZLE_TABLE[src_y % GOB_SIZE_Y];
const u32 block_y = src_y >> GOB_SIZE_Y_SHIFT;
const u32 src_offset_y = (block_y >> block_height) * block_size +
@@ -270,7 +177,7 @@ void SwizzleSliceToVoxel(u32 line_length_in, u32 line_count, u32 pitch, u32 widt
const u32 x_shift = static_cast<u32>(GOB_SIZE_SHIFT) + block_height + block_depth;
for (u32 line = 0; line < line_count; ++line) {
- const auto& table = LEGACY_SWIZZLE_TABLE[line % GOB_SIZE_Y];
+ const auto& table = SWIZZLE_TABLE[line % GOB_SIZE_Y];
const u32 block_y = line / GOB_SIZE_Y;
const u32 dst_offset_y =
(block_y >> block_height) * block_size + (block_y & block_height_mask) * GOB_SIZE;
@@ -293,7 +200,7 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32
const std::size_t gob_address_y =
(y / (GOB_SIZE_Y * block_height)) * GOB_SIZE * block_height * image_width_in_gobs +
((y % (GOB_SIZE_Y * block_height)) / GOB_SIZE_Y) * GOB_SIZE;
- const auto& table = LEGACY_SWIZZLE_TABLE[y % GOB_SIZE_Y];
+ const auto& table = SWIZZLE_TABLE[y % GOB_SIZE_Y];
for (std::size_t x = dst_x; x < width && count < copy_size; ++x) {
const std::size_t gob_address =
gob_address_y + (x / GOB_SIZE_X) * GOB_SIZE * block_height;
@@ -310,9 +217,9 @@ void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
u32 block_height, u32 block_depth) {
if (tiled) {
- const u32 aligned_width = Common::AlignBits(width * bytes_per_pixel, GOB_SIZE_X_SHIFT);
- const u32 aligned_height = Common::AlignBits(height, GOB_SIZE_Y_SHIFT + block_height);
- const u32 aligned_depth = Common::AlignBits(depth, GOB_SIZE_Z_SHIFT + block_depth);
+ const u32 aligned_width = Common::AlignUpLog2(width * bytes_per_pixel, GOB_SIZE_X_SHIFT);
+ const u32 aligned_height = Common::AlignUpLog2(height, GOB_SIZE_Y_SHIFT + block_height);
+ const u32 aligned_depth = Common::AlignUpLog2(depth, GOB_SIZE_Z_SHIFT + block_depth);
return aligned_width * aligned_height * aligned_depth;
} else {
return width * height * depth * bytes_per_pixel;
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index 01e156bc8..d7cdc81e8 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -4,7 +4,8 @@
#pragma once
-#include <vector>
+#include <span>
+
#include "common/common_types.h"
#include "video_core/textures/texture.h"
@@ -15,28 +16,25 @@ constexpr u32 GOB_SIZE_Y = 8;
constexpr u32 GOB_SIZE_Z = 1;
constexpr u32 GOB_SIZE = GOB_SIZE_X * GOB_SIZE_Y * GOB_SIZE_Z;
-constexpr std::size_t GOB_SIZE_X_SHIFT = 6;
-constexpr std::size_t GOB_SIZE_Y_SHIFT = 3;
-constexpr std::size_t GOB_SIZE_Z_SHIFT = 0;
-constexpr std::size_t GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
-
-/// Unswizzles a swizzled texture without changing its format.
-void UnswizzleTexture(u8* unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
- u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
- u32 block_height = TICEntry::DefaultBlockHeight,
- u32 block_depth = TICEntry::DefaultBlockHeight, u32 width_spacing = 0);
-
-/// Unswizzles a swizzled texture without changing its format.
-std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel,
- u32 width, u32 height, u32 depth,
- u32 block_height = TICEntry::DefaultBlockHeight,
- u32 block_depth = TICEntry::DefaultBlockHeight,
- u32 width_spacing = 0);
-
-/// Copies texture data from a buffer and performs swizzling/unswizzling as necessary.
-void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
- u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
- bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing);
+constexpr u32 GOB_SIZE_X_SHIFT = 6;
+constexpr u32 GOB_SIZE_Y_SHIFT = 3;
+constexpr u32 GOB_SIZE_Z_SHIFT = 0;
+constexpr u32 GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT;
+
+using SwizzleTable = std::array<std::array<u32, GOB_SIZE_X>, GOB_SIZE_Y>;
+
+/// Returns a z-order swizzle table
+SwizzleTable MakeSwizzleTable();
+
+/// Unswizzles a block linear texture into linear memory.
+void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
+ u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
+ u32 stride_alignment = 1);
+
+/// Swizzles linear memory into a block linear texture.
+void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
+ u32 height, u32 depth, u32 block_height, u32 block_depth,
+ u32 stride_alignment = 1);
/// This function calculates the correct size of a texture depending if it's tiled or not.
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp
index 4171e3ef2..ae5621a7d 100644
--- a/src/video_core/textures/texture.cpp
+++ b/src/video_core/textures/texture.cpp
@@ -5,9 +5,13 @@
#include <algorithm>
#include <array>
+#include "common/cityhash.h"
#include "core/settings.h"
#include "video_core/textures/texture.h"
+using Tegra::Texture::TICEntry;
+using Tegra::Texture::TSCEntry;
+
namespace Tegra::Texture {
namespace {
@@ -65,7 +69,7 @@ unsigned SettingsMinimumAnisotropy() noexcept {
} // Anonymous namespace
-std::array<float, 4> TSCEntry::GetBorderColor() const noexcept {
+std::array<float, 4> TSCEntry::BorderColor() const noexcept {
if (!srgb_conversion) {
return border_color;
}
@@ -73,8 +77,16 @@ std::array<float, 4> TSCEntry::GetBorderColor() const noexcept {
SRGB_CONVERSION_LUT[srgb_border_color_b], border_color[3]};
}
-float TSCEntry::GetMaxAnisotropy() const noexcept {
+float TSCEntry::MaxAnisotropy() const noexcept {
return static_cast<float>(std::max(1U << max_anisotropy, SettingsMinimumAnisotropy()));
}
} // namespace Tegra::Texture
+
+size_t std::hash<TICEntry>::operator()(const TICEntry& tic) const noexcept {
+ return Common::CityHash64(reinterpret_cast<const char*>(&tic), sizeof tic);
+}
+
+size_t std::hash<TSCEntry>::operator()(const TSCEntry& tsc) const noexcept {
+ return Common::CityHash64(reinterpret_cast<const char*>(&tsc), sizeof tsc);
+}
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
index 0574fef12..c1d14335e 100644
--- a/src/video_core/textures/texture.h
+++ b/src/video_core/textures/texture.h
@@ -53,27 +53,27 @@ enum class TextureFormat : u32 {
BC4 = 0x27,
BC5 = 0x28,
S8D24 = 0x29,
- X8Z24 = 0x2a,
+ X8D24 = 0x2a,
D24S8 = 0x2b,
- X4V4Z24__COV4R4V = 0x2c,
- X4V4Z24__COV8R8V = 0x2d,
- V8Z24__COV4R12V = 0x2e,
+ X4V4D24__COV4R4V = 0x2c,
+ X4V4D24__COV8R8V = 0x2d,
+ V8D24__COV4R12V = 0x2e,
D32 = 0x2f,
D32S8 = 0x30,
- X8Z24_X20V4S8__COV4R4V = 0x31,
- X8Z24_X20V4S8__COV8R8V = 0x32,
- ZF32_X20V4X8__COV4R4V = 0x33,
- ZF32_X20V4X8__COV8R8V = 0x34,
- ZF32_X20V4S8__COV4R4V = 0x35,
- ZF32_X20V4S8__COV8R8V = 0x36,
- X8Z24_X16V8S8__COV4R12V = 0x37,
- ZF32_X16V8X8__COV4R12V = 0x38,
- ZF32_X16V8S8__COV4R12V = 0x39,
+ X8D24_X20V4S8__COV4R4V = 0x31,
+ X8D24_X20V4S8__COV8R8V = 0x32,
+ D32_X20V4X8__COV4R4V = 0x33,
+ D32_X20V4X8__COV8R8V = 0x34,
+ D32_X20V4S8__COV4R4V = 0x35,
+ D32_X20V4S8__COV8R8V = 0x36,
+ X8D24_X16V8S8__COV4R12V = 0x37,
+ D32_X16V8X8__COV4R12V = 0x38,
+ D32_X16V8S8__COV4R12V = 0x39,
D16 = 0x3a,
- V8Z24__COV8R24V = 0x3b,
- X8Z24_X16V8S8__COV8R24V = 0x3c,
- ZF32_X16V8X8__COV8R24V = 0x3d,
- ZF32_X16V8S8__COV8R24V = 0x3e,
+ V8D24__COV8R24V = 0x3b,
+ X8D24_X16V8S8__COV8R24V = 0x3c,
+ D32_X16V8X8__COV8R24V = 0x3d,
+ D32_X16V8S8__COV8R24V = 0x3e,
ASTC_2D_4X4 = 0x40,
ASTC_2D_5X5 = 0x41,
ASTC_2D_6X6 = 0x42,
@@ -146,7 +146,7 @@ enum class MsaaMode : u32 {
};
union TextureHandle {
- TextureHandle(u32 raw) : raw{raw} {}
+ /* implicit */ constexpr TextureHandle(u32 raw_) : raw{raw_} {}
u32 raw;
BitField<0, 20, u32> tic_id;
@@ -155,124 +155,124 @@ union TextureHandle {
static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size");
struct TICEntry {
- static constexpr u32 DefaultBlockHeight = 16;
- static constexpr u32 DefaultBlockDepth = 1;
-
- union {
- u32 raw;
- BitField<0, 7, TextureFormat> format;
- BitField<7, 3, ComponentType> r_type;
- BitField<10, 3, ComponentType> g_type;
- BitField<13, 3, ComponentType> b_type;
- BitField<16, 3, ComponentType> a_type;
-
- BitField<19, 3, SwizzleSource> x_source;
- BitField<22, 3, SwizzleSource> y_source;
- BitField<25, 3, SwizzleSource> z_source;
- BitField<28, 3, SwizzleSource> w_source;
- };
- u32 address_low;
union {
- BitField<0, 16, u32> address_high;
- BitField<21, 3, TICHeaderVersion> header_version;
- };
- union {
- BitField<0, 3, u32> block_width;
- BitField<3, 3, u32> block_height;
- BitField<6, 3, u32> block_depth;
+ struct {
+ union {
+ BitField<0, 7, TextureFormat> format;
+ BitField<7, 3, ComponentType> r_type;
+ BitField<10, 3, ComponentType> g_type;
+ BitField<13, 3, ComponentType> b_type;
+ BitField<16, 3, ComponentType> a_type;
+
+ BitField<19, 3, SwizzleSource> x_source;
+ BitField<22, 3, SwizzleSource> y_source;
+ BitField<25, 3, SwizzleSource> z_source;
+ BitField<28, 3, SwizzleSource> w_source;
+ };
+ u32 address_low;
+ union {
+ BitField<0, 16, u32> address_high;
+ BitField<16, 5, u32> layer_base_3_7;
+ BitField<21, 3, TICHeaderVersion> header_version;
+ BitField<24, 1, u32> load_store_hint;
+ BitField<25, 4, u32> view_coherency_hash;
+ BitField<29, 3, u32> layer_base_8_10;
+ };
+ union {
+ BitField<0, 3, u32> block_width;
+ BitField<3, 3, u32> block_height;
+ BitField<6, 3, u32> block_depth;
- BitField<10, 3, u32> tile_width_spacing;
+ BitField<10, 3, u32> tile_width_spacing;
- // High 16 bits of the pitch value
- BitField<0, 16, u32> pitch_high;
- BitField<26, 1, u32> use_header_opt_control;
- BitField<27, 1, u32> depth_texture;
- BitField<28, 4, u32> max_mip_level;
+ // High 16 bits of the pitch value
+ BitField<0, 16, u32> pitch_high;
+ BitField<26, 1, u32> use_header_opt_control;
+ BitField<27, 1, u32> depth_texture;
+ BitField<28, 4, u32> max_mip_level;
- BitField<0, 16, u32> buffer_high_width_minus_one;
- };
- union {
- BitField<0, 16, u32> width_minus_1;
- BitField<22, 1, u32> srgb_conversion;
- BitField<23, 4, TextureType> texture_type;
- BitField<29, 3, u32> border_size;
+ BitField<0, 16, u32> buffer_high_width_minus_one;
+ };
+ union {
+ BitField<0, 16, u32> width_minus_one;
+ BitField<16, 3, u32> layer_base_0_2;
+ BitField<22, 1, u32> srgb_conversion;
+ BitField<23, 4, TextureType> texture_type;
+ BitField<29, 3, u32> border_size;
- BitField<0, 16, u32> buffer_low_width_minus_one;
- };
- union {
- BitField<0, 16, u32> height_minus_1;
- BitField<16, 14, u32> depth_minus_1;
- };
- union {
- BitField<6, 13, u32> mip_lod_bias;
- BitField<27, 3, u32> max_anisotropy;
+ BitField<0, 16, u32> buffer_low_width_minus_one;
+ };
+ union {
+ BitField<0, 16, u32> height_minus_1;
+ BitField<16, 14, u32> depth_minus_1;
+ BitField<30, 1, u32> is_sparse;
+ BitField<31, 1, u32> normalized_coords;
+ };
+ union {
+ BitField<6, 13, u32> mip_lod_bias;
+ BitField<27, 3, u32> max_anisotropy;
+ };
+ union {
+ BitField<0, 4, u32> res_min_mip_level;
+ BitField<4, 4, u32> res_max_mip_level;
+ BitField<8, 4, MsaaMode> msaa_mode;
+ BitField<12, 12, u32> min_lod_clamp;
+ };
+ };
+ std::array<u64, 4> raw;
};
- union {
- BitField<0, 4, u32> res_min_mip_level;
- BitField<4, 4, u32> res_max_mip_level;
- BitField<8, 4, MsaaMode> msaa_mode;
- BitField<12, 12, u32> min_lod_clamp;
- };
+ constexpr bool operator==(const TICEntry& rhs) const noexcept {
+ return raw == rhs.raw;
+ }
- GPUVAddr Address() const {
+ constexpr bool operator!=(const TICEntry& rhs) const noexcept {
+ return raw != rhs.raw;
+ }
+
+ constexpr GPUVAddr Address() const {
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
}
- u32 Pitch() const {
+ constexpr u32 Pitch() const {
ASSERT(header_version == TICHeaderVersion::Pitch ||
header_version == TICHeaderVersion::PitchColorKey);
// The pitch value is 21 bits, and is 32B aligned.
return pitch_high << 5;
}
- u32 Width() const {
+ constexpr u32 Width() const {
if (header_version != TICHeaderVersion::OneDBuffer) {
- return width_minus_1 + 1;
+ return width_minus_one + 1;
}
- return ((buffer_high_width_minus_one << 16) | buffer_low_width_minus_one) + 1;
+ return (buffer_high_width_minus_one << 16 | buffer_low_width_minus_one) + 1;
}
- u32 Height() const {
+ constexpr u32 Height() const {
return height_minus_1 + 1;
}
- u32 Depth() const {
+ constexpr u32 Depth() const {
return depth_minus_1 + 1;
}
- u32 BlockWidth() const {
- ASSERT(IsTiled());
- return block_width;
- }
-
- u32 BlockHeight() const {
- ASSERT(IsTiled());
- return block_height;
- }
-
- u32 BlockDepth() const {
- ASSERT(IsTiled());
- return block_depth;
+ constexpr u32 BaseLayer() const {
+ return layer_base_0_2 | layer_base_3_7 << 3 | layer_base_8_10 << 8;
}
- bool IsTiled() const {
+ constexpr bool IsBlockLinear() const {
return header_version == TICHeaderVersion::BlockLinear ||
header_version == TICHeaderVersion::BlockLinearColorKey;
}
- bool IsLineal() const {
+ constexpr bool IsPitchLinear() const {
return header_version == TICHeaderVersion::Pitch ||
header_version == TICHeaderVersion::PitchColorKey;
}
- bool IsBuffer() const {
+ constexpr bool IsBuffer() const {
return header_version == TICHeaderVersion::OneDBuffer;
}
-
- bool IsSrgbConversionEnabled() const {
- return srgb_conversion != 0;
- }
};
static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size");
@@ -309,6 +309,12 @@ enum class TextureMipmapFilter : u32 {
Linear = 3,
};
+enum class SamplerReduction : u32 {
+ WeightedAverage = 0,
+ Min = 1,
+ Max = 2,
+};
+
enum class Anisotropy {
Default,
Filter2x,
@@ -333,8 +339,12 @@ struct TSCEntry {
BitField<0, 2, TextureFilter> mag_filter;
BitField<4, 2, TextureFilter> min_filter;
BitField<6, 2, TextureMipmapFilter> mipmap_filter;
+ BitField<8, 1, u32> cubemap_anisotropy;
BitField<9, 1, u32> cubemap_interface_filtering;
+ BitField<10, 2, SamplerReduction> reduction_filter;
BitField<12, 13, u32> mip_lod_bias;
+ BitField<25, 1, u32> float_coord_normalization;
+ BitField<26, 5, u32> trilin_opt;
};
union {
BitField<0, 12, u32> min_lod_clamp;
@@ -347,32 +357,45 @@ struct TSCEntry {
};
std::array<f32, 4> border_color;
};
- std::array<u8, 0x20> raw;
+ std::array<u64, 4> raw;
};
- std::array<float, 4> GetBorderColor() const noexcept;
+ constexpr bool operator==(const TSCEntry& rhs) const noexcept {
+ return raw == rhs.raw;
+ }
+
+ constexpr bool operator!=(const TSCEntry& rhs) const noexcept {
+ return raw != rhs.raw;
+ }
+
+ std::array<float, 4> BorderColor() const noexcept;
- float GetMaxAnisotropy() const noexcept;
+ float MaxAnisotropy() const noexcept;
- float GetMinLod() const {
+ float MinLod() const {
return static_cast<float>(min_lod_clamp) / 256.0f;
}
- float GetMaxLod() const {
+ float MaxLod() const {
return static_cast<float>(max_lod_clamp) / 256.0f;
}
- float GetLodBias() const {
+ float LodBias() const {
// Sign extend the 13-bit value.
- constexpr u32 mask = 1U << (13 - 1);
+ static constexpr u32 mask = 1U << (13 - 1);
return static_cast<float>(static_cast<s32>((mip_lod_bias ^ mask) - mask)) / 256.0f;
}
};
static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size");
-struct FullTextureInfo {
- TICEntry tic;
- TSCEntry tsc;
+} // namespace Tegra::Texture
+
+template <>
+struct std::hash<Tegra::Texture::TICEntry> {
+ size_t operator()(const Tegra::Texture::TICEntry& tic) const noexcept;
};
-} // namespace Tegra::Texture
+template <>
+struct std::hash<Tegra::Texture::TSCEntry> {
+ size_t operator()(const Tegra::Texture::TSCEntry& tsc) const noexcept;
+};
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 4e3a092c7..e1b38c6ac 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -7,13 +7,9 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
-#include "video_core/gpu_asynch.h"
-#include "video_core/gpu_synch.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
-#ifdef HAS_VULKAN
#include "video_core/renderer_vulkan/renderer_vulkan.h"
-#endif
#include "video_core/video_core.h"
namespace {
@@ -21,15 +17,16 @@ namespace {
std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
Core::System& system, Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
std::unique_ptr<Core::Frontend::GraphicsContext> context) {
+ auto& telemetry_session = system.TelemetrySession();
+ auto& cpu_memory = system.Memory();
+
switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
- return std::make_unique<OpenGL::RendererOpenGL>(system, emu_window, gpu,
- std::move(context));
-#ifdef HAS_VULKAN
+ return std::make_unique<OpenGL::RendererOpenGL>(telemetry_session, emu_window, cpu_memory,
+ gpu, std::move(context));
case Settings::RendererBackend::Vulkan:
- return std::make_unique<Vulkan::RendererVulkan>(system, emu_window, gpu,
- std::move(context));
-#endif
+ return std::make_unique<Vulkan::RendererVulkan>(telemetry_session, emu_window, cpu_memory,
+ gpu, std::move(context));
default:
return nullptr;
}
@@ -40,23 +37,19 @@ std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
namespace VideoCore {
std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) {
- std::unique_ptr<Tegra::GPU> gpu;
- if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
- gpu = std::make_unique<VideoCommon::GPUAsynch>(system);
- } else {
- gpu = std::make_unique<VideoCommon::GPUSynch>(system);
- }
-
+ const bool use_nvdec = Settings::values.use_nvdec_emulation.GetValue();
+ const bool use_async = Settings::values.use_asynchronous_gpu_emulation.GetValue();
+ auto gpu = std::make_unique<Tegra::GPU>(system, use_async, use_nvdec);
auto context = emu_window.CreateSharedContext();
- const auto scope = context->Acquire();
-
- auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context));
- if (!renderer->Init()) {
+ auto scope = context->Acquire();
+ try {
+ auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context));
+ gpu->BindRenderer(std::move(renderer));
+ return gpu;
+ } catch (const std::runtime_error& exception) {
+ LOG_ERROR(HW_GPU, "Failed to initialize GPU: {}", exception.what());
return nullptr;
}
-
- gpu->BindRenderer(std::move(renderer));
- return gpu;
}
u16 GetResolutionScaleFactor(const RendererBase& renderer) {
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
new file mode 100644
index 000000000..7a9d00d4f
--- /dev/null
+++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.cpp
@@ -0,0 +1,203 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef HAS_NSIGHT_AFTERMATH
+
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "common/common_paths.h"
+#include "common/common_types.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "video_core/vulkan_common/nsight_aftermath_tracker.h"
+
+namespace Vulkan {
+
+static constexpr char AFTERMATH_LIB_NAME[] = "GFSDK_Aftermath_Lib.x64.dll";
+
+NsightAftermathTracker::NsightAftermathTracker() {
+ if (!dl.Open(AFTERMATH_LIB_NAME)) {
+ LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath DLL");
+ return;
+ }
+ if (!dl.GetSymbol("GFSDK_Aftermath_DisableGpuCrashDumps",
+ &GFSDK_Aftermath_DisableGpuCrashDumps) ||
+ !dl.GetSymbol("GFSDK_Aftermath_EnableGpuCrashDumps",
+ &GFSDK_Aftermath_EnableGpuCrashDumps) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GetShaderDebugInfoIdentifier",
+ &GFSDK_Aftermath_GetShaderDebugInfoIdentifier) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GetShaderHashSpirv", &GFSDK_Aftermath_GetShaderHashSpirv) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_CreateDecoder",
+ &GFSDK_Aftermath_GpuCrashDump_CreateDecoder) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_DestroyDecoder",
+ &GFSDK_Aftermath_GpuCrashDump_DestroyDecoder) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GenerateJSON",
+ &GFSDK_Aftermath_GpuCrashDump_GenerateJSON) ||
+ !dl.GetSymbol("GFSDK_Aftermath_GpuCrashDump_GetJSON",
+ &GFSDK_Aftermath_GpuCrashDump_GetJSON)) {
+ LOG_ERROR(Render_Vulkan, "Failed to load Nsight Aftermath function pointers");
+ return;
+ }
+ dump_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir) + "gpucrash";
+
+ void(Common::FS::DeleteDirRecursively(dump_dir));
+ if (!Common::FS::CreateDir(dump_dir)) {
+ LOG_ERROR(Render_Vulkan, "Failed to create Nsight Aftermath dump directory");
+ return;
+ }
+ if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_EnableGpuCrashDumps(
+ GFSDK_Aftermath_Version_API, GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan,
+ GFSDK_Aftermath_GpuCrashDumpFeatureFlags_Default, GpuCrashDumpCallback,
+ ShaderDebugInfoCallback, CrashDumpDescriptionCallback, this))) {
+ LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_EnableGpuCrashDumps failed");
+ return;
+ }
+ LOG_INFO(Render_Vulkan, "Nsight Aftermath dump directory is \"{}\"", dump_dir);
+ initialized = true;
+}
+
+NsightAftermathTracker::~NsightAftermathTracker() {
+ if (initialized) {
+ (void)GFSDK_Aftermath_DisableGpuCrashDumps();
+ }
+}
+
+void NsightAftermathTracker::SaveShader(const std::vector<u32>& spirv) const {
+ if (!initialized) {
+ return;
+ }
+
+ std::vector<u32> spirv_copy = spirv;
+ GFSDK_Aftermath_SpirvCode shader;
+ shader.pData = spirv_copy.data();
+ shader.size = static_cast<u32>(spirv_copy.size() * 4);
+
+ std::scoped_lock lock{mutex};
+
+ GFSDK_Aftermath_ShaderHash hash;
+ if (!GFSDK_Aftermath_SUCCEED(
+ GFSDK_Aftermath_GetShaderHashSpirv(GFSDK_Aftermath_Version_API, &shader, &hash))) {
+ LOG_ERROR(Render_Vulkan, "Failed to hash SPIR-V module");
+ return;
+ }
+
+ Common::FS::IOFile file(fmt::format("{}/source_{:016x}.spv", dump_dir, hash.hash), "wb");
+ if (!file.IsOpen()) {
+ LOG_ERROR(Render_Vulkan, "Failed to dump SPIR-V module with hash={:016x}", hash.hash);
+ return;
+ }
+ if (file.WriteArray(spirv.data(), spirv.size()) != spirv.size()) {
+ LOG_ERROR(Render_Vulkan, "Failed to write SPIR-V module with hash={:016x}", hash.hash);
+ return;
+ }
+}
+
+void NsightAftermathTracker::OnGpuCrashDumpCallback(const void* gpu_crash_dump,
+ u32 gpu_crash_dump_size) {
+ std::scoped_lock lock{mutex};
+
+ LOG_CRITICAL(Render_Vulkan, "called");
+
+ GFSDK_Aftermath_GpuCrashDump_Decoder decoder;
+ if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_CreateDecoder(
+ GFSDK_Aftermath_Version_API, gpu_crash_dump, gpu_crash_dump_size, &decoder))) {
+ LOG_ERROR(Render_Vulkan, "Failed to create decoder");
+ return;
+ }
+ SCOPE_EXIT({ GFSDK_Aftermath_GpuCrashDump_DestroyDecoder(decoder); });
+
+ u32 json_size = 0;
+ if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GpuCrashDump_GenerateJSON(
+ decoder, GFSDK_Aftermath_GpuCrashDumpDecoderFlags_ALL_INFO,
+ GFSDK_Aftermath_GpuCrashDumpFormatterFlags_NONE, nullptr, nullptr, nullptr, nullptr,
+ this, &json_size))) {
+ LOG_ERROR(Render_Vulkan, "Failed to generate JSON");
+ return;
+ }
+ std::vector<char> json(json_size);
+ if (!GFSDK_Aftermath_SUCCEED(
+ GFSDK_Aftermath_GpuCrashDump_GetJSON(decoder, json_size, json.data()))) {
+ LOG_ERROR(Render_Vulkan, "Failed to query JSON");
+ return;
+ }
+
+ const std::string base_name = [this] {
+ const int id = dump_id++;
+ if (id == 0) {
+ return fmt::format("{}/crash.nv-gpudmp", dump_dir);
+ } else {
+ return fmt::format("{}/crash_{}.nv-gpudmp", dump_dir, id);
+ }
+ }();
+
+ std::string_view dump_view(static_cast<const char*>(gpu_crash_dump), gpu_crash_dump_size);
+ if (Common::FS::WriteStringToFile(false, base_name, dump_view) != gpu_crash_dump_size) {
+ LOG_ERROR(Render_Vulkan, "Failed to write dump file");
+ return;
+ }
+ const std::string_view json_view(json.data(), json.size());
+ if (Common::FS::WriteStringToFile(true, base_name + ".json", json_view) != json.size()) {
+ LOG_ERROR(Render_Vulkan, "Failed to write JSON");
+ return;
+ }
+}
+
+void NsightAftermathTracker::OnShaderDebugInfoCallback(const void* shader_debug_info,
+ u32 shader_debug_info_size) {
+ std::scoped_lock lock{mutex};
+
+ GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier;
+ if (!GFSDK_Aftermath_SUCCEED(GFSDK_Aftermath_GetShaderDebugInfoIdentifier(
+ GFSDK_Aftermath_Version_API, shader_debug_info, shader_debug_info_size, &identifier))) {
+ LOG_ERROR(Render_Vulkan, "GFSDK_Aftermath_GetShaderDebugInfoIdentifier failed");
+ return;
+ }
+
+ const std::string path =
+ fmt::format("{}/shader_{:016x}{:016x}.nvdbg", dump_dir, identifier.id[0], identifier.id[1]);
+ Common::FS::IOFile file(path, "wb");
+ if (!file.IsOpen()) {
+ LOG_ERROR(Render_Vulkan, "Failed to create file {}", path);
+ return;
+ }
+ if (file.WriteBytes(static_cast<const u8*>(shader_debug_info), shader_debug_info_size) !=
+ shader_debug_info_size) {
+ LOG_ERROR(Render_Vulkan, "Failed to write file {}", path);
+ return;
+ }
+}
+
+void NsightAftermathTracker::OnCrashDumpDescriptionCallback(
+ PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description) {
+ add_description(GFSDK_Aftermath_GpuCrashDumpDescriptionKey_ApplicationName, "yuzu");
+}
+
+void NsightAftermathTracker::GpuCrashDumpCallback(const void* gpu_crash_dump,
+ u32 gpu_crash_dump_size, void* user_data) {
+ static_cast<NsightAftermathTracker*>(user_data)->OnGpuCrashDumpCallback(gpu_crash_dump,
+ gpu_crash_dump_size);
+}
+
+void NsightAftermathTracker::ShaderDebugInfoCallback(const void* shader_debug_info,
+ u32 shader_debug_info_size, void* user_data) {
+ static_cast<NsightAftermathTracker*>(user_data)->OnShaderDebugInfoCallback(
+ shader_debug_info, shader_debug_info_size);
+}
+
+void NsightAftermathTracker::CrashDumpDescriptionCallback(
+ PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description, void* user_data) {
+ static_cast<NsightAftermathTracker*>(user_data)->OnCrashDumpDescriptionCallback(
+ add_description);
+}
+
+} // namespace Vulkan
+
+#endif // HAS_NSIGHT_AFTERMATH
diff --git a/src/video_core/vulkan_common/nsight_aftermath_tracker.h b/src/video_core/vulkan_common/nsight_aftermath_tracker.h
new file mode 100644
index 000000000..1ce8d4e8e
--- /dev/null
+++ b/src/video_core/vulkan_common/nsight_aftermath_tracker.h
@@ -0,0 +1,80 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/dynamic_library.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+#ifdef HAS_NSIGHT_AFTERMATH
+#include <GFSDK_Aftermath_Defines.h>
+#include <GFSDK_Aftermath_GpuCrashDump.h>
+#include <GFSDK_Aftermath_GpuCrashDumpDecoding.h>
+#endif
+
+namespace Vulkan {
+
+class NsightAftermathTracker {
+public:
+ NsightAftermathTracker();
+ ~NsightAftermathTracker();
+
+ NsightAftermathTracker(const NsightAftermathTracker&) = delete;
+ NsightAftermathTracker& operator=(const NsightAftermathTracker&) = delete;
+
+ // Delete move semantics because Aftermath initialization uses a pointer to this.
+ NsightAftermathTracker(NsightAftermathTracker&&) = delete;
+ NsightAftermathTracker& operator=(NsightAftermathTracker&&) = delete;
+
+ void SaveShader(const std::vector<u32>& spirv) const;
+
+private:
+#ifdef HAS_NSIGHT_AFTERMATH
+ static void GpuCrashDumpCallback(const void* gpu_crash_dump, u32 gpu_crash_dump_size,
+ void* user_data);
+
+ static void ShaderDebugInfoCallback(const void* shader_debug_info, u32 shader_debug_info_size,
+ void* user_data);
+
+ static void CrashDumpDescriptionCallback(
+ PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description, void* user_data);
+
+ void OnGpuCrashDumpCallback(const void* gpu_crash_dump, u32 gpu_crash_dump_size);
+
+ void OnShaderDebugInfoCallback(const void* shader_debug_info, u32 shader_debug_info_size);
+
+ void OnCrashDumpDescriptionCallback(
+ PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription add_description);
+
+ mutable std::mutex mutex;
+
+ std::string dump_dir;
+ int dump_id = 0;
+
+ bool initialized = false;
+
+ Common::DynamicLibrary dl;
+ PFN_GFSDK_Aftermath_DisableGpuCrashDumps GFSDK_Aftermath_DisableGpuCrashDumps;
+ PFN_GFSDK_Aftermath_EnableGpuCrashDumps GFSDK_Aftermath_EnableGpuCrashDumps;
+ PFN_GFSDK_Aftermath_GetShaderDebugInfoIdentifier GFSDK_Aftermath_GetShaderDebugInfoIdentifier;
+ PFN_GFSDK_Aftermath_GetShaderHashSpirv GFSDK_Aftermath_GetShaderHashSpirv;
+ PFN_GFSDK_Aftermath_GpuCrashDump_CreateDecoder GFSDK_Aftermath_GpuCrashDump_CreateDecoder;
+ PFN_GFSDK_Aftermath_GpuCrashDump_DestroyDecoder GFSDK_Aftermath_GpuCrashDump_DestroyDecoder;
+ PFN_GFSDK_Aftermath_GpuCrashDump_GenerateJSON GFSDK_Aftermath_GpuCrashDump_GenerateJSON;
+ PFN_GFSDK_Aftermath_GpuCrashDump_GetJSON GFSDK_Aftermath_GpuCrashDump_GetJSON;
+#endif
+};
+
+#ifndef HAS_NSIGHT_AFTERMATH
+inline NsightAftermathTracker::NsightAftermathTracker() = default;
+inline NsightAftermathTracker::~NsightAftermathTracker() = default;
+inline void NsightAftermathTracker::SaveShader(const std::vector<u32>&) const {}
+#endif
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
new file mode 100644
index 000000000..5c64c9bf7
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp
@@ -0,0 +1,46 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <string_view>
+#include "common/logging/log.h"
+#include "video_core/vulkan_common/vulkan_debug_callback.h"
+
+namespace Vulkan {
+namespace {
+VkBool32 Callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
+ VkDebugUtilsMessageTypeFlagsEXT type,
+ const VkDebugUtilsMessengerCallbackDataEXT* data,
+ [[maybe_unused]] void* user_data) {
+ const std::string_view message{data->pMessage};
+ if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
+ LOG_CRITICAL(Render_Vulkan, "{}", message);
+ } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
+ LOG_WARNING(Render_Vulkan, "{}", message);
+ } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
+ LOG_INFO(Render_Vulkan, "{}", message);
+ } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
+ LOG_DEBUG(Render_Vulkan, "{}", message);
+ }
+ return VK_FALSE;
+}
+} // Anonymous namespace
+
+vk::DebugUtilsMessenger CreateDebugCallback(const vk::Instance& instance) {
+ return instance.CreateDebugUtilsMessenger(VkDebugUtilsMessengerCreateInfoEXT{
+ .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .flags = 0,
+ .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT,
+ .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
+ .pfnUserCallback = Callback,
+ .pUserData = nullptr,
+ });
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.h b/src/video_core/vulkan_common/vulkan_debug_callback.h
new file mode 100644
index 000000000..b0519f132
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_debug_callback.h
@@ -0,0 +1,13 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+vk::DebugUtilsMessenger CreateDebugCallback(const vk::Instance& instance);
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
new file mode 100644
index 000000000..34d396434
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -0,0 +1,860 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <bitset>
+#include <chrono>
+#include <optional>
+#include <string_view>
+#include <thread>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "common/assert.h"
+#include "core/settings.h"
+#include "video_core/vulkan_common/nsight_aftermath_tracker.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+namespace {
+namespace Alternatives {
+constexpr std::array DEPTH24_UNORM_STENCIL8_UINT{
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_D16_UNORM_S8_UINT,
+ VK_FORMAT_UNDEFINED,
+};
+
+constexpr std::array DEPTH16_UNORM_STENCIL8_UINT{
+ VK_FORMAT_D24_UNORM_S8_UINT,
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_UNDEFINED,
+};
+} // namespace Alternatives
+
+constexpr std::array REQUIRED_EXTENSIONS{
+ VK_KHR_MAINTENANCE1_EXTENSION_NAME,
+ VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME,
+ VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME,
+ VK_KHR_16BIT_STORAGE_EXTENSION_NAME,
+ VK_KHR_8BIT_STORAGE_EXTENSION_NAME,
+ VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME,
+ VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME,
+ VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME,
+ VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME,
+ VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME,
+ VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME,
+ VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME,
+ VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
+ VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME,
+#ifdef _WIN32
+ VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME,
+#endif
+#ifdef __linux__
+ VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
+#endif
+};
+
+template <typename T>
+void SetNext(void**& next, T& data) {
+ *next = &data;
+ next = &data.pNext;
+}
+
+constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
+ switch (format) {
+ case VK_FORMAT_D24_UNORM_S8_UINT:
+ return Alternatives::DEPTH24_UNORM_STENCIL8_UINT.data();
+ case VK_FORMAT_D16_UNORM_S8_UINT:
+ return Alternatives::DEPTH16_UNORM_STENCIL8_UINT.data();
+ default:
+ return nullptr;
+ }
+}
+
+VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType format_type) {
+ switch (format_type) {
+ case FormatType::Linear:
+ return properties.linearTilingFeatures;
+ case FormatType::Optimal:
+ return properties.optimalTilingFeatures;
+ case FormatType::Buffer:
+ return properties.bufferFeatures;
+ default:
+ return {};
+ }
+}
+
+std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::PhysicalDevice physical) {
+ static constexpr std::array formats{
+ VK_FORMAT_A8B8G8R8_UNORM_PACK32,
+ VK_FORMAT_A8B8G8R8_UINT_PACK32,
+ VK_FORMAT_A8B8G8R8_SNORM_PACK32,
+ VK_FORMAT_A8B8G8R8_SINT_PACK32,
+ VK_FORMAT_A8B8G8R8_SRGB_PACK32,
+ VK_FORMAT_B5G6R5_UNORM_PACK16,
+ VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+ VK_FORMAT_A2B10G10R10_UINT_PACK32,
+ VK_FORMAT_A1R5G5B5_UNORM_PACK16,
+ VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_FORMAT_R32G32B32A32_SINT,
+ VK_FORMAT_R32G32B32A32_UINT,
+ VK_FORMAT_R32G32_SFLOAT,
+ VK_FORMAT_R32G32_SINT,
+ VK_FORMAT_R32G32_UINT,
+ VK_FORMAT_R16G16B16A16_SINT,
+ VK_FORMAT_R16G16B16A16_UINT,
+ VK_FORMAT_R16G16B16A16_SNORM,
+ VK_FORMAT_R16G16B16A16_UNORM,
+ VK_FORMAT_R16G16_UNORM,
+ VK_FORMAT_R16G16_SNORM,
+ VK_FORMAT_R16G16_SFLOAT,
+ VK_FORMAT_R16G16_SINT,
+ VK_FORMAT_R16_UNORM,
+ VK_FORMAT_R16_UINT,
+ VK_FORMAT_R8G8B8A8_SRGB,
+ VK_FORMAT_R8G8_UNORM,
+ VK_FORMAT_R8G8_SNORM,
+ VK_FORMAT_R8G8_SINT,
+ VK_FORMAT_R8G8_UINT,
+ VK_FORMAT_R8_UNORM,
+ VK_FORMAT_R8_SNORM,
+ VK_FORMAT_R8_SINT,
+ VK_FORMAT_R8_UINT,
+ VK_FORMAT_B10G11R11_UFLOAT_PACK32,
+ VK_FORMAT_R32_SFLOAT,
+ VK_FORMAT_R32_UINT,
+ VK_FORMAT_R32_SINT,
+ VK_FORMAT_R16_SFLOAT,
+ VK_FORMAT_R16G16B16A16_SFLOAT,
+ VK_FORMAT_B8G8R8A8_UNORM,
+ VK_FORMAT_B8G8R8A8_SRGB,
+ VK_FORMAT_R4G4B4A4_UNORM_PACK16,
+ VK_FORMAT_D32_SFLOAT,
+ VK_FORMAT_D16_UNORM,
+ VK_FORMAT_D16_UNORM_S8_UINT,
+ VK_FORMAT_D24_UNORM_S8_UINT,
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_BC1_RGBA_UNORM_BLOCK,
+ VK_FORMAT_BC2_UNORM_BLOCK,
+ VK_FORMAT_BC3_UNORM_BLOCK,
+ VK_FORMAT_BC4_UNORM_BLOCK,
+ VK_FORMAT_BC4_SNORM_BLOCK,
+ VK_FORMAT_BC5_UNORM_BLOCK,
+ VK_FORMAT_BC5_SNORM_BLOCK,
+ VK_FORMAT_BC7_UNORM_BLOCK,
+ VK_FORMAT_BC6H_UFLOAT_BLOCK,
+ VK_FORMAT_BC6H_SFLOAT_BLOCK,
+ VK_FORMAT_BC1_RGBA_SRGB_BLOCK,
+ VK_FORMAT_BC2_SRGB_BLOCK,
+ VK_FORMAT_BC3_SRGB_BLOCK,
+ VK_FORMAT_BC7_SRGB_BLOCK,
+ VK_FORMAT_ASTC_4x4_UNORM_BLOCK,
+ VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
+ VK_FORMAT_ASTC_5x4_UNORM_BLOCK,
+ VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
+ VK_FORMAT_ASTC_5x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x8_UNORM_BLOCK,
+ VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x8_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x10_UNORM_BLOCK,
+ VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
+ VK_FORMAT_ASTC_12x10_UNORM_BLOCK,
+ VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
+ VK_FORMAT_ASTC_12x12_UNORM_BLOCK,
+ VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x6_UNORM_BLOCK,
+ VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x5_UNORM_BLOCK,
+ VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
+ VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,
+ };
+ std::unordered_map<VkFormat, VkFormatProperties> format_properties;
+ for (const auto format : formats) {
+ format_properties.emplace(format, physical.GetFormatProperties(format));
+ }
+ return format_properties;
+}
+
+} // Anonymous namespace
+
+Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR surface,
+ const vk::InstanceDispatch& dld_)
+ : instance{instance_}, dld{dld_}, physical{physical_}, properties{physical.GetProperties()},
+ format_properties{GetFormatProperties(physical)} {
+ CheckSuitability(surface != nullptr);
+ SetupFamilies(surface);
+ SetupFeatures();
+
+ const auto queue_cis = GetDeviceQueueCreateInfos();
+ const std::vector extensions = LoadExtensions(surface != nullptr);
+
+ VkPhysicalDeviceFeatures2 features2{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
+ .pNext = nullptr,
+ .features{
+ .robustBufferAccess = true,
+ .fullDrawIndexUint32 = false,
+ .imageCubeArray = true,
+ .independentBlend = true,
+ .geometryShader = true,
+ .tessellationShader = true,
+ .sampleRateShading = false,
+ .dualSrcBlend = false,
+ .logicOp = false,
+ .multiDrawIndirect = false,
+ .drawIndirectFirstInstance = false,
+ .depthClamp = true,
+ .depthBiasClamp = true,
+ .fillModeNonSolid = false,
+ .depthBounds = false,
+ .wideLines = false,
+ .largePoints = true,
+ .alphaToOne = false,
+ .multiViewport = true,
+ .samplerAnisotropy = true,
+ .textureCompressionETC2 = false,
+ .textureCompressionASTC_LDR = is_optimal_astc_supported,
+ .textureCompressionBC = false,
+ .occlusionQueryPrecise = true,
+ .pipelineStatisticsQuery = false,
+ .vertexPipelineStoresAndAtomics = true,
+ .fragmentStoresAndAtomics = true,
+ .shaderTessellationAndGeometryPointSize = false,
+ .shaderImageGatherExtended = true,
+ .shaderStorageImageExtendedFormats = false,
+ .shaderStorageImageMultisample = is_shader_storage_image_multisample,
+ .shaderStorageImageReadWithoutFormat = is_formatless_image_load_supported,
+ .shaderStorageImageWriteWithoutFormat = true,
+ .shaderUniformBufferArrayDynamicIndexing = false,
+ .shaderSampledImageArrayDynamicIndexing = false,
+ .shaderStorageBufferArrayDynamicIndexing = false,
+ .shaderStorageImageArrayDynamicIndexing = false,
+ .shaderClipDistance = false,
+ .shaderCullDistance = false,
+ .shaderFloat64 = false,
+ .shaderInt64 = false,
+ .shaderInt16 = false,
+ .shaderResourceResidency = false,
+ .shaderResourceMinLod = false,
+ .sparseBinding = false,
+ .sparseResidencyBuffer = false,
+ .sparseResidencyImage2D = false,
+ .sparseResidencyImage3D = false,
+ .sparseResidency2Samples = false,
+ .sparseResidency4Samples = false,
+ .sparseResidency8Samples = false,
+ .sparseResidency16Samples = false,
+ .sparseResidencyAliased = false,
+ .variableMultisampleRate = false,
+ .inheritedQueries = false,
+ },
+ };
+ const void* first_next = &features2;
+ void** next = &features2.pNext;
+
+ VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timeline_semaphore{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR,
+ .pNext = nullptr,
+ .timelineSemaphore = true,
+ };
+ SetNext(next, timeline_semaphore);
+
+ VkPhysicalDevice16BitStorageFeaturesKHR bit16_storage{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR,
+ .pNext = nullptr,
+ .storageBuffer16BitAccess = false,
+ .uniformAndStorageBuffer16BitAccess = true,
+ .storagePushConstant16 = false,
+ .storageInputOutput16 = false,
+ };
+ SetNext(next, bit16_storage);
+
+ VkPhysicalDevice8BitStorageFeaturesKHR bit8_storage{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES_KHR,
+ .pNext = nullptr,
+ .storageBuffer8BitAccess = false,
+ .uniformAndStorageBuffer8BitAccess = true,
+ .storagePushConstant8 = false,
+ };
+ SetNext(next, bit8_storage);
+
+ VkPhysicalDeviceHostQueryResetFeaturesEXT host_query_reset{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT,
+ .pNext = nullptr,
+ .hostQueryReset = true,
+ };
+ SetNext(next, host_query_reset);
+
+ VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8;
+ if (is_float16_supported) {
+ float16_int8 = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR,
+ .pNext = nullptr,
+ .shaderFloat16 = true,
+ .shaderInt8 = false,
+ };
+ SetNext(next, float16_int8);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support float16 natively");
+ }
+
+ if (!nv_viewport_swizzle) {
+ LOG_INFO(Render_Vulkan, "Device doesn't support viewport swizzles");
+ }
+
+ VkPhysicalDeviceUniformBufferStandardLayoutFeaturesKHR std430_layout;
+ if (khr_uniform_buffer_standard_layout) {
+ std430_layout = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES_KHR,
+ .pNext = nullptr,
+ .uniformBufferStandardLayout = true,
+ };
+ SetNext(next, std430_layout);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support packed UBOs");
+ }
+
+ VkPhysicalDeviceIndexTypeUint8FeaturesEXT index_type_uint8;
+ if (ext_index_type_uint8) {
+ index_type_uint8 = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT,
+ .pNext = nullptr,
+ .indexTypeUint8 = true,
+ };
+ SetNext(next, index_type_uint8);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support uint8 indexes");
+ }
+
+ VkPhysicalDeviceTransformFeedbackFeaturesEXT transform_feedback;
+ if (ext_transform_feedback) {
+ transform_feedback = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT,
+ .pNext = nullptr,
+ .transformFeedback = true,
+ .geometryStreams = true,
+ };
+ SetNext(next, transform_feedback);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support transform feedbacks");
+ }
+
+ VkPhysicalDeviceCustomBorderColorFeaturesEXT custom_border;
+ if (ext_custom_border_color) {
+ custom_border = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT,
+ .pNext = nullptr,
+ .customBorderColors = VK_TRUE,
+ .customBorderColorWithoutFormat = VK_TRUE,
+ };
+ SetNext(next, custom_border);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors");
+ }
+
+ VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
+ if (ext_extended_dynamic_state) {
+ dynamic_state = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT,
+ .pNext = nullptr,
+ .extendedDynamicState = VK_TRUE,
+ };
+ SetNext(next, dynamic_state);
+ } else {
+ LOG_INFO(Render_Vulkan, "Device doesn't support extended dynamic state");
+ }
+
+ if (!ext_depth_range_unrestricted) {
+ LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
+ }
+
+ VkDeviceDiagnosticsConfigCreateInfoNV diagnostics_nv;
+ if (nv_device_diagnostics_config) {
+ nsight_aftermath_tracker = std::make_unique<NsightAftermathTracker>();
+
+ diagnostics_nv = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV,
+ .pNext = &features2,
+ .flags = VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV |
+ VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV |
+ VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV,
+ };
+ first_next = &diagnostics_nv;
+ }
+ logical = vk::Device::Create(physical, queue_cis, extensions, first_next, dld);
+
+ CollectTelemetryParameters();
+ CollectToolingInfo();
+
+ if (ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_MESA_RADV) {
+ LOG_WARNING(
+ Render_Vulkan,
+ "Blacklisting RADV for VK_EXT_extended_dynamic state, likely due to a bug in yuzu");
+ ext_extended_dynamic_state = false;
+ }
+ if (is_float16_supported && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
+ // Intel's compiler crashes when using fp16 on Astral Chain, disable it for the time being.
+ LOG_WARNING(Render_Vulkan, "Blacklisting Intel proprietary from float16 math");
+ is_float16_supported = false;
+ }
+
+ graphics_queue = logical.GetQueue(graphics_family);
+ present_queue = logical.GetQueue(present_family);
+
+ use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue();
+}
+
+Device::~Device() = default;
+
+VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
+ FormatType format_type) const {
+ if (IsFormatSupported(wanted_format, wanted_usage, format_type)) {
+ return wanted_format;
+ }
+ // The wanted format is not supported by hardware, search for alternatives
+ const VkFormat* alternatives = GetFormatAlternatives(wanted_format);
+ if (alternatives == nullptr) {
+ UNREACHABLE_MSG("Format={} with usage={} and type={} has no defined alternatives and host "
+ "hardware does not support it",
+ wanted_format, wanted_usage, format_type);
+ return wanted_format;
+ }
+
+ std::size_t i = 0;
+ for (VkFormat alternative = *alternatives; alternative; alternative = alternatives[++i]) {
+ if (!IsFormatSupported(alternative, wanted_usage, format_type)) {
+ continue;
+ }
+ LOG_WARNING(Render_Vulkan,
+ "Emulating format={} with alternative format={} with usage={} and type={}",
+ wanted_format, alternative, wanted_usage, format_type);
+ return alternative;
+ }
+
+ // No alternatives found, panic
+ UNREACHABLE_MSG("Format={} with usage={} and type={} is not supported by the host hardware and "
+ "doesn't support any of the alternatives",
+ wanted_format, wanted_usage, format_type);
+ return wanted_format;
+}
+
+void Device::ReportLoss() const {
+ LOG_CRITICAL(Render_Vulkan, "Device loss occured!");
+
+ // Wait for the log to flush and for Nsight Aftermath to dump the results
+ std::this_thread::sleep_for(std::chrono::seconds{15});
+}
+
+void Device::SaveShader(const std::vector<u32>& spirv) const {
+ if (nsight_aftermath_tracker) {
+ nsight_aftermath_tracker->SaveShader(spirv);
+ }
+}
+
+bool Device::IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const {
+ // Disable for now to avoid converting ASTC twice.
+ static constexpr std::array astc_formats = {
+ VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
+ VK_FORMAT_ASTC_5x4_UNORM_BLOCK, VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
+ VK_FORMAT_ASTC_5x5_UNORM_BLOCK, VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x5_UNORM_BLOCK, VK_FORMAT_ASTC_6x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_6x6_UNORM_BLOCK, VK_FORMAT_ASTC_6x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x5_UNORM_BLOCK, VK_FORMAT_ASTC_8x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x6_UNORM_BLOCK, VK_FORMAT_ASTC_8x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_8x8_UNORM_BLOCK, VK_FORMAT_ASTC_8x8_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x5_UNORM_BLOCK, VK_FORMAT_ASTC_10x5_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x6_UNORM_BLOCK, VK_FORMAT_ASTC_10x6_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x8_UNORM_BLOCK, VK_FORMAT_ASTC_10x8_SRGB_BLOCK,
+ VK_FORMAT_ASTC_10x10_UNORM_BLOCK, VK_FORMAT_ASTC_10x10_SRGB_BLOCK,
+ VK_FORMAT_ASTC_12x10_UNORM_BLOCK, VK_FORMAT_ASTC_12x10_SRGB_BLOCK,
+ VK_FORMAT_ASTC_12x12_UNORM_BLOCK, VK_FORMAT_ASTC_12x12_SRGB_BLOCK,
+ };
+ if (!features.textureCompressionASTC_LDR) {
+ return false;
+ }
+ const auto format_feature_usage{
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT |
+ VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
+ VK_FORMAT_FEATURE_TRANSFER_DST_BIT};
+ for (const auto format : astc_formats) {
+ const auto physical_format_properties{physical.GetFormatProperties(format)};
+ if ((physical_format_properties.optimalTilingFeatures & format_feature_usage) == 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Device::TestDepthStencilBlits() const {
+ static constexpr VkFormatFeatureFlags required_features =
+ VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
+ const auto test_features = [](VkFormatProperties props) {
+ return (props.optimalTilingFeatures & required_features) == required_features;
+ };
+ return test_features(format_properties.at(VK_FORMAT_D32_SFLOAT_S8_UINT)) &&
+ test_features(format_properties.at(VK_FORMAT_D24_UNORM_S8_UINT));
+}
+
+bool Device::IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
+ FormatType format_type) const {
+ const auto it = format_properties.find(wanted_format);
+ if (it == format_properties.end()) {
+ UNIMPLEMENTED_MSG("Unimplemented format query={}", wanted_format);
+ return true;
+ }
+ const auto supported_usage = GetFormatFeatures(it->second, format_type);
+ return (supported_usage & wanted_usage) == wanted_usage;
+}
+
+void Device::CheckSuitability(bool requires_swapchain) const {
+ std::bitset<REQUIRED_EXTENSIONS.size()> available_extensions;
+ bool has_swapchain = false;
+ for (const VkExtensionProperties& property : physical.EnumerateDeviceExtensionProperties()) {
+ const std::string_view name{property.extensionName};
+ for (size_t i = 0; i < REQUIRED_EXTENSIONS.size(); ++i) {
+ if (available_extensions[i]) {
+ continue;
+ }
+ available_extensions[i] = name == REQUIRED_EXTENSIONS[i];
+ }
+ has_swapchain = has_swapchain || name == VK_KHR_SWAPCHAIN_EXTENSION_NAME;
+ }
+ for (size_t i = 0; i < REQUIRED_EXTENSIONS.size(); ++i) {
+ if (available_extensions[i]) {
+ continue;
+ }
+ LOG_ERROR(Render_Vulkan, "Missing required extension: {}", REQUIRED_EXTENSIONS[i]);
+ throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT);
+ }
+ if (requires_swapchain && !has_swapchain) {
+ LOG_ERROR(Render_Vulkan, "Missing required extension: VK_KHR_swapchain");
+ throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT);
+ }
+
+ struct LimitTuple {
+ u32 minimum;
+ u32 value;
+ const char* name;
+ };
+ const VkPhysicalDeviceLimits& limits{properties.limits};
+ const std::array limits_report{
+ LimitTuple{65536, limits.maxUniformBufferRange, "maxUniformBufferRange"},
+ LimitTuple{16, limits.maxViewports, "maxViewports"},
+ LimitTuple{8, limits.maxColorAttachments, "maxColorAttachments"},
+ LimitTuple{8, limits.maxClipDistances, "maxClipDistances"},
+ };
+ for (const auto& tuple : limits_report) {
+ if (tuple.value < tuple.minimum) {
+ LOG_ERROR(Render_Vulkan, "{} has to be {} or greater but it is {}", tuple.name,
+ tuple.minimum, tuple.value);
+ throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT);
+ }
+ }
+ VkPhysicalDeviceRobustness2FeaturesEXT robustness2{};
+ robustness2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT;
+
+ VkPhysicalDeviceFeatures2 features2{};
+ features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+ features2.pNext = &robustness2;
+
+ physical.GetFeatures2KHR(features2);
+
+ const VkPhysicalDeviceFeatures& features{features2.features};
+ const std::array feature_report{
+ std::make_pair(features.robustBufferAccess, "robustBufferAccess"),
+ std::make_pair(features.vertexPipelineStoresAndAtomics, "vertexPipelineStoresAndAtomics"),
+ std::make_pair(features.robustBufferAccess, "robustBufferAccess"),
+ std::make_pair(features.imageCubeArray, "imageCubeArray"),
+ std::make_pair(features.independentBlend, "independentBlend"),
+ std::make_pair(features.depthClamp, "depthClamp"),
+ std::make_pair(features.samplerAnisotropy, "samplerAnisotropy"),
+ std::make_pair(features.largePoints, "largePoints"),
+ std::make_pair(features.multiViewport, "multiViewport"),
+ std::make_pair(features.depthBiasClamp, "depthBiasClamp"),
+ std::make_pair(features.geometryShader, "geometryShader"),
+ std::make_pair(features.tessellationShader, "tessellationShader"),
+ std::make_pair(features.occlusionQueryPrecise, "occlusionQueryPrecise"),
+ std::make_pair(features.fragmentStoresAndAtomics, "fragmentStoresAndAtomics"),
+ std::make_pair(features.shaderImageGatherExtended, "shaderImageGatherExtended"),
+ std::make_pair(features.shaderStorageImageWriteWithoutFormat,
+ "shaderStorageImageWriteWithoutFormat"),
+ std::make_pair(robustness2.robustBufferAccess2, "robustBufferAccess2"),
+ std::make_pair(robustness2.robustImageAccess2, "robustImageAccess2"),
+ std::make_pair(robustness2.nullDescriptor, "nullDescriptor"),
+ };
+ for (const auto& [is_supported, name] : feature_report) {
+ if (is_supported) {
+ continue;
+ }
+ LOG_ERROR(Render_Vulkan, "Missing required feature: {}", name);
+ throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT);
+ }
+}
+
+std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
+ std::vector<const char*> extensions;
+ extensions.reserve(8 + REQUIRED_EXTENSIONS.size());
+ extensions.insert(extensions.begin(), REQUIRED_EXTENSIONS.begin(), REQUIRED_EXTENSIONS.end());
+ if (requires_surface) {
+ extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+ }
+
+ bool has_khr_shader_float16_int8{};
+ bool has_ext_subgroup_size_control{};
+ bool has_ext_transform_feedback{};
+ bool has_ext_custom_border_color{};
+ bool has_ext_extended_dynamic_state{};
+ for (const VkExtensionProperties& extension : physical.EnumerateDeviceExtensionProperties()) {
+ const auto test = [&](std::optional<std::reference_wrapper<bool>> status, const char* name,
+ bool push) {
+ if (extension.extensionName != std::string_view(name)) {
+ return;
+ }
+ if (push) {
+ extensions.push_back(name);
+ }
+ if (status) {
+ status->get() = true;
+ }
+ };
+ test(nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true);
+ test(khr_uniform_buffer_standard_layout,
+ VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, true);
+ test(has_khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, false);
+ test(ext_depth_range_unrestricted, VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, true);
+ test(ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true);
+ test(ext_sampler_filter_minmax, VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME, true);
+ test(ext_shader_viewport_index_layer, VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME,
+ true);
+ test(ext_tooling_info, VK_EXT_TOOLING_INFO_EXTENSION_NAME, true);
+ test(ext_shader_stencil_export, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME, true);
+ test(has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, false);
+ test(has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, false);
+ test(has_ext_extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false);
+ test(has_ext_subgroup_size_control, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false);
+ if (Settings::values.renderer_debug) {
+ test(nv_device_diagnostics_config, VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME,
+ true);
+ }
+ }
+ VkPhysicalDeviceFeatures2KHR features;
+ features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
+
+ VkPhysicalDeviceProperties2KHR physical_properties;
+ physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
+
+ if (has_khr_shader_float16_int8) {
+ VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8_features;
+ float16_int8_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR;
+ float16_int8_features.pNext = nullptr;
+ features.pNext = &float16_int8_features;
+
+ physical.GetFeatures2KHR(features);
+ is_float16_supported = float16_int8_features.shaderFloat16;
+ extensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME);
+ }
+ if (has_ext_subgroup_size_control) {
+ VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroup_features;
+ subgroup_features.sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT;
+ subgroup_features.pNext = nullptr;
+ features.pNext = &subgroup_features;
+ physical.GetFeatures2KHR(features);
+
+ VkPhysicalDeviceSubgroupSizeControlPropertiesEXT subgroup_properties;
+ subgroup_properties.sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES_EXT;
+ subgroup_properties.pNext = nullptr;
+ physical_properties.pNext = &subgroup_properties;
+ physical.GetProperties2KHR(physical_properties);
+
+ is_warp_potentially_bigger = subgroup_properties.maxSubgroupSize > GuestWarpSize;
+
+ if (subgroup_features.subgroupSizeControl &&
+ subgroup_properties.minSubgroupSize <= GuestWarpSize &&
+ subgroup_properties.maxSubgroupSize >= GuestWarpSize) {
+ extensions.push_back(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
+ guest_warp_stages = subgroup_properties.requiredSubgroupSizeStages;
+ }
+ } else {
+ is_warp_potentially_bigger = true;
+ }
+ if (has_ext_transform_feedback) {
+ VkPhysicalDeviceTransformFeedbackFeaturesEXT tfb_features;
+ tfb_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT;
+ tfb_features.pNext = nullptr;
+ features.pNext = &tfb_features;
+ physical.GetFeatures2KHR(features);
+
+ VkPhysicalDeviceTransformFeedbackPropertiesEXT tfb_properties;
+ tfb_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
+ tfb_properties.pNext = nullptr;
+ physical_properties.pNext = &tfb_properties;
+ physical.GetProperties2KHR(physical_properties);
+
+ if (tfb_features.transformFeedback && tfb_features.geometryStreams &&
+ tfb_properties.maxTransformFeedbackStreams >= 4 &&
+ tfb_properties.maxTransformFeedbackBuffers && tfb_properties.transformFeedbackQueries &&
+ tfb_properties.transformFeedbackDraw) {
+ extensions.push_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
+ ext_transform_feedback = true;
+ }
+ }
+ if (has_ext_custom_border_color) {
+ VkPhysicalDeviceCustomBorderColorFeaturesEXT border_features;
+ border_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT;
+ border_features.pNext = nullptr;
+ features.pNext = &border_features;
+ physical.GetFeatures2KHR(features);
+
+ if (border_features.customBorderColors && border_features.customBorderColorWithoutFormat) {
+ extensions.push_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
+ ext_custom_border_color = true;
+ }
+ }
+ if (has_ext_extended_dynamic_state) {
+ VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
+ dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
+ dynamic_state.pNext = nullptr;
+ features.pNext = &dynamic_state;
+ physical.GetFeatures2KHR(features);
+
+ if (dynamic_state.extendedDynamicState) {
+ extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
+ ext_extended_dynamic_state = true;
+ }
+ }
+ return extensions;
+}
+
+void Device::SetupFamilies(VkSurfaceKHR surface) {
+ const std::vector queue_family_properties = physical.GetQueueFamilyProperties();
+ std::optional<u32> graphics;
+ std::optional<u32> present;
+ for (u32 index = 0; index < static_cast<u32>(queue_family_properties.size()); ++index) {
+ if (graphics && (present || !surface)) {
+ break;
+ }
+ const VkQueueFamilyProperties& queue_family = queue_family_properties[index];
+ if (queue_family.queueCount == 0) {
+ continue;
+ }
+ if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ graphics = index;
+ }
+ if (surface && physical.GetSurfaceSupportKHR(index, surface)) {
+ present = index;
+ }
+ }
+ if (!graphics) {
+ LOG_ERROR(Render_Vulkan, "Device lacks a graphics queue");
+ throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT);
+ }
+ if (surface && !present) {
+ LOG_ERROR(Render_Vulkan, "Device lacks a present queue");
+ throw vk::Exception(VK_ERROR_FEATURE_NOT_PRESENT);
+ }
+ graphics_family = *graphics;
+ present_family = *present;
+}
+
+void Device::SetupFeatures() {
+ const auto supported_features{physical.GetFeatures()};
+ is_formatless_image_load_supported = supported_features.shaderStorageImageReadWithoutFormat;
+ is_shader_storage_image_multisample = supported_features.shaderStorageImageMultisample;
+ is_blit_depth_stencil_supported = TestDepthStencilBlits();
+ is_optimal_astc_supported = IsOptimalAstcSupported(supported_features);
+}
+
+void Device::CollectTelemetryParameters() {
+ VkPhysicalDeviceDriverPropertiesKHR driver{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR,
+ .pNext = nullptr,
+ .driverID = {},
+ .driverName = {},
+ .driverInfo = {},
+ .conformanceVersion = {},
+ };
+
+ VkPhysicalDeviceProperties2KHR device_properties{
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
+ .pNext = &driver,
+ .properties = {},
+ };
+ physical.GetProperties2KHR(device_properties);
+
+ driver_id = driver.driverID;
+ vendor_name = driver.driverName;
+
+ const std::vector extensions = physical.EnumerateDeviceExtensionProperties();
+ reported_extensions.reserve(std::size(extensions));
+ for (const auto& extension : extensions) {
+ reported_extensions.emplace_back(extension.extensionName);
+ }
+}
+
+void Device::CollectToolingInfo() {
+ if (!ext_tooling_info) {
+ return;
+ }
+ const auto vkGetPhysicalDeviceToolPropertiesEXT =
+ reinterpret_cast<PFN_vkGetPhysicalDeviceToolPropertiesEXT>(
+ dld.vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceToolPropertiesEXT"));
+ if (!vkGetPhysicalDeviceToolPropertiesEXT) {
+ return;
+ }
+ u32 tool_count = 0;
+ if (vkGetPhysicalDeviceToolPropertiesEXT(physical, &tool_count, nullptr) != VK_SUCCESS) {
+ return;
+ }
+ std::vector<VkPhysicalDeviceToolPropertiesEXT> tools(tool_count);
+ if (vkGetPhysicalDeviceToolPropertiesEXT(physical, &tool_count, tools.data()) != VK_SUCCESS) {
+ return;
+ }
+ for (const VkPhysicalDeviceToolPropertiesEXT& tool : tools) {
+ const std::string_view name = tool.name;
+ LOG_INFO(Render_Vulkan, "{}", name);
+ has_renderdoc = has_renderdoc || name == "RenderDoc";
+ has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics";
+ }
+}
+
+std::vector<VkDeviceQueueCreateInfo> Device::GetDeviceQueueCreateInfos() const {
+ static constexpr float QUEUE_PRIORITY = 1.0f;
+
+ std::unordered_set<u32> unique_queue_families{graphics_family, present_family};
+ std::vector<VkDeviceQueueCreateInfo> queue_cis;
+ queue_cis.reserve(unique_queue_families.size());
+
+ for (const u32 queue_family : unique_queue_families) {
+ auto& ci = queue_cis.emplace_back(VkDeviceQueueCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .queueFamilyIndex = queue_family,
+ .queueCount = 1,
+ .pQueuePriorities = nullptr,
+ });
+ ci.pQueuePriorities = &QUEUE_PRIORITY;
+ }
+
+ return queue_cis;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
new file mode 100644
index 000000000..67d70cd22
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -0,0 +1,307 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+#include "common/common_types.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+class NsightAftermathTracker;
+
+/// Format usage descriptor.
+enum class FormatType { Linear, Optimal, Buffer };
+
+/// Subgroup size of the guest emulated hardware (Nvidia has 32 threads per subgroup).
+const u32 GuestWarpSize = 32;
+
+/// Handles data specific to a physical device.
+class Device {
+public:
+ explicit Device(VkInstance instance, vk::PhysicalDevice physical, VkSurfaceKHR surface,
+ const vk::InstanceDispatch& dld);
+ ~Device();
+
+ /**
+ * Returns a format supported by the device for the passed requeriments.
+ * @param wanted_format The ideal format to be returned. It may not be the returned format.
+ * @param wanted_usage The usage that must be fulfilled even if the format is not supported.
+ * @param format_type Format type usage.
+ * @returns A format supported by the device.
+ */
+ VkFormat GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
+ FormatType format_type) const;
+
+ /// Reports a device loss.
+ void ReportLoss() const;
+
+ /// Reports a shader to Nsight Aftermath.
+ void SaveShader(const std::vector<u32>& spirv) const;
+
+ /// Returns the dispatch loader with direct function pointers of the device.
+ const vk::DeviceDispatch& GetDispatchLoader() const {
+ return dld;
+ }
+
+ /// Returns the logical device.
+ const vk::Device& GetLogical() const {
+ return logical;
+ }
+
+ /// Returns the physical device.
+ vk::PhysicalDevice GetPhysical() const {
+ return physical;
+ }
+
+ /// Returns the main graphics queue.
+ vk::Queue GetGraphicsQueue() const {
+ return graphics_queue;
+ }
+
+ /// Returns the main present queue.
+ vk::Queue GetPresentQueue() const {
+ return present_queue;
+ }
+
+ /// Returns main graphics queue family index.
+ u32 GetGraphicsFamily() const {
+ return graphics_family;
+ }
+
+ /// Returns main present queue family index.
+ u32 GetPresentFamily() const {
+ return present_family;
+ }
+
+ /// Returns the current Vulkan API version provided in Vulkan-formatted version numbers.
+ u32 ApiVersion() const {
+ return properties.apiVersion;
+ }
+
+ /// Returns the current driver version provided in Vulkan-formatted version numbers.
+ u32 GetDriverVersion() const {
+ return properties.driverVersion;
+ }
+
+ /// Returns the device name.
+ std::string_view GetModelName() const {
+ return properties.deviceName;
+ }
+
+ /// Returns the driver ID.
+ VkDriverIdKHR GetDriverID() const {
+ return driver_id;
+ }
+
+ /// Returns uniform buffer alignment requeriment.
+ VkDeviceSize GetUniformBufferAlignment() const {
+ return properties.limits.minUniformBufferOffsetAlignment;
+ }
+
+ /// Returns storage alignment requeriment.
+ VkDeviceSize GetStorageBufferAlignment() const {
+ return properties.limits.minStorageBufferOffsetAlignment;
+ }
+
+ /// Returns the maximum range for storage buffers.
+ VkDeviceSize GetMaxStorageBufferRange() const {
+ return properties.limits.maxStorageBufferRange;
+ }
+
+ /// Returns the maximum size for push constants.
+ VkDeviceSize GetMaxPushConstantsSize() const {
+ return properties.limits.maxPushConstantsSize;
+ }
+
+ /// Returns the maximum size for shared memory.
+ u32 GetMaxComputeSharedMemorySize() const {
+ return properties.limits.maxComputeSharedMemorySize;
+ }
+
+ /// Returns true if ASTC is natively supported.
+ bool IsOptimalAstcSupported() const {
+ return is_optimal_astc_supported;
+ }
+
+ /// Returns true if the device supports float16 natively
+ bool IsFloat16Supported() const {
+ return is_float16_supported;
+ }
+
+ /// Returns true if the device warp size can potentially be bigger than guest's warp size.
+ bool IsWarpSizePotentiallyBiggerThanGuest() const {
+ return is_warp_potentially_bigger;
+ }
+
+ /// Returns true if the device can be forced to use the guest warp size.
+ bool IsGuestWarpSizeSupported(VkShaderStageFlagBits stage) const {
+ return guest_warp_stages & stage;
+ }
+
+ /// Returns true if formatless image load is supported.
+ bool IsFormatlessImageLoadSupported() const {
+ return is_formatless_image_load_supported;
+ }
+
+ /// Returns true when blitting from and to depth stencil images is supported.
+ bool IsBlitDepthStencilSupported() const {
+ return is_blit_depth_stencil_supported;
+ }
+
+ /// Returns true if the device supports VK_NV_viewport_swizzle.
+ bool IsNvViewportSwizzleSupported() const {
+ return nv_viewport_swizzle;
+ }
+
+ /// Returns true if the device supports VK_EXT_scalar_block_layout.
+ bool IsKhrUniformBufferStandardLayoutSupported() const {
+ return khr_uniform_buffer_standard_layout;
+ }
+
+ /// Returns true if the device supports VK_EXT_index_type_uint8.
+ bool IsExtIndexTypeUint8Supported() const {
+ return ext_index_type_uint8;
+ }
+
+ /// Returns true if the device supports VK_EXT_sampler_filter_minmax.
+ bool IsExtSamplerFilterMinmaxSupported() const {
+ return ext_sampler_filter_minmax;
+ }
+
+ /// Returns true if the device supports VK_EXT_depth_range_unrestricted.
+ bool IsExtDepthRangeUnrestrictedSupported() const {
+ return ext_depth_range_unrestricted;
+ }
+
+ /// Returns true if the device supports VK_EXT_shader_viewport_index_layer.
+ bool IsExtShaderViewportIndexLayerSupported() const {
+ return ext_shader_viewport_index_layer;
+ }
+
+ /// Returns true if the device supports VK_EXT_transform_feedback.
+ bool IsExtTransformFeedbackSupported() const {
+ return ext_transform_feedback;
+ }
+
+ /// Returns true if the device supports VK_EXT_custom_border_color.
+ bool IsExtCustomBorderColorSupported() const {
+ return ext_custom_border_color;
+ }
+
+ /// Returns true if the device supports VK_EXT_extended_dynamic_state.
+ bool IsExtExtendedDynamicStateSupported() const {
+ return ext_extended_dynamic_state;
+ }
+
+ /// Returns true if the device supports VK_EXT_shader_stencil_export.
+ bool IsExtShaderStencilExportSupported() const {
+ return ext_shader_stencil_export;
+ }
+
+ /// Returns true when a known debugging tool is attached.
+ bool HasDebuggingToolAttached() const {
+ return has_renderdoc || has_nsight_graphics;
+ }
+
+ /// Returns the vendor name reported from Vulkan.
+ std::string_view GetVendorName() const {
+ return vendor_name;
+ }
+
+ /// Returns the list of available extensions.
+ const std::vector<std::string>& GetAvailableExtensions() const {
+ return reported_extensions;
+ }
+
+ /// Returns true if the setting for async shader compilation is enabled.
+ bool UseAsynchronousShaders() const {
+ return use_asynchronous_shaders;
+ }
+
+private:
+ /// Checks if the physical device is suitable.
+ void CheckSuitability(bool requires_swapchain) const;
+
+ /// Loads extensions into a vector and stores available ones in this object.
+ std::vector<const char*> LoadExtensions(bool requires_surface);
+
+ /// Sets up queue families.
+ void SetupFamilies(VkSurfaceKHR surface);
+
+ /// Sets up device features.
+ void SetupFeatures();
+
+ /// Collects telemetry information from the device.
+ void CollectTelemetryParameters();
+
+ /// Collects information about attached tools.
+ void CollectToolingInfo();
+
+ /// Returns a list of queue initialization descriptors.
+ std::vector<VkDeviceQueueCreateInfo> GetDeviceQueueCreateInfos() const;
+
+ /// Returns true if ASTC textures are natively supported.
+ bool IsOptimalAstcSupported(const VkPhysicalDeviceFeatures& features) const;
+
+ /// Returns true if the device natively supports blitting depth stencil images.
+ bool TestDepthStencilBlits() const;
+
+ /// Returns true if a format is supported.
+ bool IsFormatSupported(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
+ FormatType format_type) const;
+
+ VkInstance instance; ///< Vulkan instance.
+ vk::DeviceDispatch dld; ///< Device function pointers.
+ vk::PhysicalDevice physical; ///< Physical device.
+ VkPhysicalDeviceProperties properties; ///< Device properties.
+ vk::Device logical; ///< Logical device.
+ vk::Queue graphics_queue; ///< Main graphics queue.
+ vk::Queue present_queue; ///< Main present queue.
+ u32 instance_version{}; ///< Vulkan onstance version.
+ u32 graphics_family{}; ///< Main graphics queue family index.
+ u32 present_family{}; ///< Main present queue family index.
+ VkDriverIdKHR driver_id{}; ///< Driver ID.
+ VkShaderStageFlags guest_warp_stages{}; ///< Stages where the guest warp size can be forced.ed
+ bool is_optimal_astc_supported{}; ///< Support for native ASTC.
+ bool is_float16_supported{}; ///< Support for float16 arithmetics.
+ bool is_warp_potentially_bigger{}; ///< Host warp size can be bigger than guest.
+ bool is_formatless_image_load_supported{}; ///< Support for shader image read without format.
+ bool is_shader_storage_image_multisample{}; ///< Support for image operations on MSAA images.
+ bool is_blit_depth_stencil_supported{}; ///< Support for blitting from and to depth stencil.
+ bool nv_viewport_swizzle{}; ///< Support for VK_NV_viewport_swizzle.
+ bool khr_uniform_buffer_standard_layout{}; ///< Support for std430 on UBOs.
+ bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8.
+ bool ext_sampler_filter_minmax{}; ///< Support for VK_EXT_sampler_filter_minmax.
+ bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.
+ bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
+ bool ext_tooling_info{}; ///< Support for VK_EXT_tooling_info.
+ bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
+ bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color.
+ bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
+ bool ext_shader_stencil_export{}; ///< Support for VK_EXT_shader_stencil_export.
+ bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
+ bool has_renderdoc{}; ///< Has RenderDoc attached
+ bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
+
+ // Asynchronous Graphics Pipeline setting
+ bool use_asynchronous_shaders{}; ///< Setting to use asynchronous shaders/graphics pipeline
+
+ // Telemetry parameters
+ std::string vendor_name; ///< Device's driver name.
+ std::vector<std::string> reported_extensions; ///< Reported Vulkan extensions.
+
+ /// Format properties dictionary.
+ std::unordered_map<VkFormat, VkFormatProperties> format_properties;
+
+ /// Nsight Aftermath GPU crash tracker
+ std::unique_ptr<NsightAftermathTracker> nsight_aftermath_tracker;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp
new file mode 100644
index 000000000..bfd6e6add
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_instance.cpp
@@ -0,0 +1,155 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <future>
+#include <optional>
+#include <span>
+#include <utility>
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/dynamic_library.h"
+#include "common/logging/log.h"
+#include "core/frontend/emu_window.h"
+#include "video_core/vulkan_common/vulkan_instance.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+// Include these late to avoid polluting previous headers
+#ifdef _WIN32
+#include <windows.h>
+// ensure include order
+#include <vulkan/vulkan_win32.h>
+#endif
+
+#if !defined(_WIN32) && !defined(__APPLE__)
+#include <X11/Xlib.h>
+#include <vulkan/vulkan_wayland.h>
+#include <vulkan/vulkan_xlib.h>
+#endif
+
+namespace Vulkan {
+namespace {
+[[nodiscard]] std::vector<const char*> RequiredExtensions(
+ Core::Frontend::WindowSystemType window_type, bool enable_debug_utils) {
+ std::vector<const char*> extensions;
+ extensions.reserve(6);
+ switch (window_type) {
+ case Core::Frontend::WindowSystemType::Headless:
+ break;
+#ifdef _WIN32
+ case Core::Frontend::WindowSystemType::Windows:
+ extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
+ break;
+#endif
+#if !defined(_WIN32) && !defined(__APPLE__)
+ case Core::Frontend::WindowSystemType::X11:
+ extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
+ break;
+ case Core::Frontend::WindowSystemType::Wayland:
+ extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
+ break;
+#endif
+ default:
+ LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
+ break;
+ }
+ if (window_type != Core::Frontend::WindowSystemType::Headless) {
+ extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
+ }
+ if (enable_debug_utils) {
+ extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+ }
+ extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+ return extensions;
+}
+
+[[nodiscard]] bool AreExtensionsSupported(const vk::InstanceDispatch& dld,
+ std::span<const char* const> extensions) {
+ const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld);
+ if (!properties) {
+ LOG_ERROR(Render_Vulkan, "Failed to query extension properties");
+ return false;
+ }
+ for (const char* extension : extensions) {
+ const auto it = std::ranges::find_if(*properties, [extension](const auto& prop) {
+ return std::strcmp(extension, prop.extensionName) == 0;
+ });
+ if (it == properties->end()) {
+ LOG_ERROR(Render_Vulkan, "Required instance extension {} is not available", extension);
+ return false;
+ }
+ }
+ return true;
+}
+
+[[nodiscard]] std::vector<const char*> Layers(bool enable_layers) {
+ std::vector<const char*> layers;
+ if (enable_layers) {
+ layers.push_back("VK_LAYER_KHRONOS_validation");
+ }
+ return layers;
+}
+
+void RemoveUnavailableLayers(const vk::InstanceDispatch& dld, std::vector<const char*>& layers) {
+ const std::optional layer_properties = vk::EnumerateInstanceLayerProperties(dld);
+ if (!layer_properties) {
+ LOG_ERROR(Render_Vulkan, "Failed to query layer properties, disabling layers");
+ layers.clear();
+ }
+ std::erase_if(layers, [&layer_properties](const char* layer) {
+ const auto comp = [layer](const VkLayerProperties& layer_property) {
+ return std::strcmp(layer, layer_property.layerName) == 0;
+ };
+ const auto it = std::ranges::find_if(*layer_properties, comp);
+ if (it == layer_properties->end()) {
+ LOG_ERROR(Render_Vulkan, "Layer {} not available, removing it", layer);
+ return true;
+ }
+ return false;
+ });
+}
+} // Anonymous namespace
+
+vk::Instance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceDispatch& dld,
+ u32 required_version, Core::Frontend::WindowSystemType window_type,
+ bool enable_debug_utils, bool enable_layers) {
+ if (!library.IsOpen()) {
+ LOG_ERROR(Render_Vulkan, "Vulkan library not available");
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ if (!library.GetSymbol("vkGetInstanceProcAddr", &dld.vkGetInstanceProcAddr)) {
+ LOG_ERROR(Render_Vulkan, "vkGetInstanceProcAddr not present in Vulkan");
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ if (!vk::Load(dld)) {
+ LOG_ERROR(Render_Vulkan, "Failed to load Vulkan function pointers");
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ const std::vector<const char*> extensions = RequiredExtensions(window_type, enable_debug_utils);
+ if (!AreExtensionsSupported(dld, extensions)) {
+ throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT);
+ }
+ std::vector<const char*> layers = Layers(enable_layers);
+ RemoveUnavailableLayers(dld, layers);
+
+ const u32 available_version = vk::AvailableVersion(dld);
+ if (available_version < required_version) {
+ LOG_ERROR(Render_Vulkan, "Vulkan {}.{} is not supported, {}.{} is required",
+ VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version),
+ VK_VERSION_MAJOR(required_version), VK_VERSION_MINOR(required_version));
+ throw vk::Exception(VK_ERROR_INCOMPATIBLE_DRIVER);
+ }
+ vk::Instance instance =
+ std::async([&] {
+ return vk::Instance::Create(required_version, layers, extensions, dld);
+ }).get();
+ if (!vk::Load(*instance, dld)) {
+ LOG_ERROR(Render_Vulkan, "Failed to load Vulkan instance function pointers");
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ return instance;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_instance.h b/src/video_core/vulkan_common/vulkan_instance.h
new file mode 100644
index 000000000..e5e3a7144
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_instance.h
@@ -0,0 +1,32 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/dynamic_library.h"
+#include "core/frontend/emu_window.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+/**
+ * Create a Vulkan instance
+ *
+ * @param library Dynamic library to load the Vulkan instance from
+ * @param dld Dispatch table to load function pointers into
+ * @param required_version Required Vulkan version (for example, VK_API_VERSION_1_1)
+ * @param window_type Window system type's enabled extension
+ * @param enable_debug_utils Whether to enable VK_EXT_debug_utils_extension_name or not
+ * @param enable_layers Whether to enable Vulkan validation layers or not
+ *
+ * @return A new Vulkan instance
+ * @throw vk::Exception on failure
+ */
+[[nodiscard]] vk::Instance CreateInstance(
+ const Common::DynamicLibrary& library, vk::InstanceDispatch& dld, u32 required_version,
+ Core::Frontend::WindowSystemType window_type = Core::Frontend::WindowSystemType::Headless,
+ bool enable_debug_utils = false, bool enable_layers = false);
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp
new file mode 100644
index 000000000..557871d81
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_library.cpp
@@ -0,0 +1,36 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstdlib>
+#include <string>
+
+#include "common/dynamic_library.h"
+#include "common/file_util.h"
+#include "video_core/vulkan_common/vulkan_library.h"
+
+namespace Vulkan {
+
+Common::DynamicLibrary OpenLibrary() {
+ Common::DynamicLibrary library;
+#ifdef __APPLE__
+ // Check if a path to a specific Vulkan library has been specified.
+ char* const libvulkan_env = std::getenv("LIBVULKAN_PATH");
+ if (!libvulkan_env || !library.Open(libvulkan_env)) {
+ // Use the libvulkan.dylib from the application bundle.
+ const std::string filename =
+ Common::FS::GetBundleDirectory() + "/Contents/Frameworks/libvulkan.dylib";
+ void(library.Open(filename.c_str()));
+ }
+#else
+ std::string filename = Common::DynamicLibrary::GetVersionedFilename("vulkan", 1);
+ if (!library.Open(filename.c_str())) {
+ // Android devices may not have libvulkan.so.1, only libvulkan.so.
+ filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
+ void(library.Open(filename.c_str()));
+ }
+#endif
+ return library;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_library.h b/src/video_core/vulkan_common/vulkan_library.h
new file mode 100644
index 000000000..8b28b0e17
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_library.h
@@ -0,0 +1,13 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/dynamic_library.h"
+
+namespace Vulkan {
+
+Common::DynamicLibrary OpenLibrary();
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
new file mode 100644
index 000000000..2a8b7a907
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -0,0 +1,326 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <bit>
+#include <optional>
+#include <vector>
+
+#include <glad/glad.h>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+namespace {
+struct Range {
+ u64 begin;
+ u64 end;
+
+ [[nodiscard]] bool Contains(u64 iterator, u64 size) const noexcept {
+ return iterator < end && begin < iterator + size;
+ }
+};
+
+[[nodiscard]] u64 AllocationChunkSize(u64 required_size) {
+ static constexpr std::array sizes{
+ 0x1000ULL << 10, 0x1400ULL << 10, 0x1800ULL << 10, 0x1c00ULL << 10, 0x2000ULL << 10,
+ 0x3200ULL << 10, 0x4000ULL << 10, 0x6000ULL << 10, 0x8000ULL << 10, 0xA000ULL << 10,
+ 0x10000ULL << 10, 0x18000ULL << 10, 0x20000ULL << 10,
+ };
+ static_assert(std::is_sorted(sizes.begin(), sizes.end()));
+
+ const auto it = std::ranges::lower_bound(sizes, required_size);
+ return it != sizes.end() ? *it : Common::AlignUp(required_size, 4ULL << 20);
+}
+
+[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
+ switch (usage) {
+ case MemoryUsage::DeviceLocal:
+ return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ case MemoryUsage::Upload:
+ return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ case MemoryUsage::Download:
+ return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
+ VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
+ }
+ UNREACHABLE_MSG("Invalid memory usage={}", usage);
+ return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+}
+} // Anonymous namespace
+
+class MemoryAllocation {
+public:
+ explicit MemoryAllocation(vk::DeviceMemory memory_, VkMemoryPropertyFlags properties,
+ u64 allocation_size_, u32 type)
+ : memory{std::move(memory_)}, allocation_size{allocation_size_}, property_flags{properties},
+ shifted_memory_type{1U << type} {}
+
+#if defined(_WIN32) || defined(__linux__)
+ ~MemoryAllocation() {
+ if (owning_opengl_handle != 0) {
+ glDeleteMemoryObjectsEXT(1, &owning_opengl_handle);
+ }
+ }
+#endif
+
+ MemoryAllocation& operator=(const MemoryAllocation&) = delete;
+ MemoryAllocation(const MemoryAllocation&) = delete;
+
+ MemoryAllocation& operator=(MemoryAllocation&&) = delete;
+ MemoryAllocation(MemoryAllocation&&) = delete;
+
+ [[nodiscard]] std::optional<MemoryCommit> Commit(VkDeviceSize size, VkDeviceSize alignment) {
+ const std::optional<u64> alloc = FindFreeRegion(size, alignment);
+ if (!alloc) {
+ // Signal out of memory, it'll try to do more allocations.
+ return std::nullopt;
+ }
+ const Range range{
+ .begin = *alloc,
+ .end = *alloc + size,
+ };
+ commits.insert(std::ranges::upper_bound(commits, *alloc, {}, &Range::begin), range);
+ return std::make_optional<MemoryCommit>(this, *memory, *alloc, *alloc + size);
+ }
+
+ void Free(u64 begin) {
+ const auto it = std::ranges::find(commits, begin, &Range::begin);
+ ASSERT_MSG(it != commits.end(), "Invalid commit");
+ commits.erase(it);
+ }
+
+ [[nodiscard]] std::span<u8> Map() {
+ if (memory_mapped_span.empty()) {
+ u8* const raw_pointer = memory.Map(0, allocation_size);
+ memory_mapped_span = std::span<u8>(raw_pointer, allocation_size);
+ }
+ return memory_mapped_span;
+ }
+
+#ifdef _WIN32
+ [[nodiscard]] u32 ExportOpenGLHandle() {
+ if (!owning_opengl_handle) {
+ glCreateMemoryObjectsEXT(1, &owning_opengl_handle);
+ glImportMemoryWin32HandleEXT(owning_opengl_handle, allocation_size,
+ GL_HANDLE_TYPE_OPAQUE_WIN32_EXT,
+ memory.GetMemoryWin32HandleKHR());
+ }
+ return owning_opengl_handle;
+ }
+#elif __linux__
+ [[nodiscard]] u32 ExportOpenGLHandle() {
+ if (!owning_opengl_handle) {
+ glCreateMemoryObjectsEXT(1, &owning_opengl_handle);
+ glImportMemoryFdEXT(owning_opengl_handle, allocation_size, GL_HANDLE_TYPE_OPAQUE_FD_EXT,
+ memory.GetMemoryFdKHR());
+ }
+ return owning_opengl_handle;
+ }
+#else
+ [[nodiscard]] u32 ExportOpenGLHandle() {
+ return 0;
+ }
+#endif
+
+ /// Returns whether this allocation is compatible with the arguments.
+ [[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags flags, u32 type_mask) const {
+ return (flags & property_flags) && (type_mask & shifted_memory_type) != 0;
+ }
+
+private:
+ [[nodiscard]] static constexpr u32 ShiftType(u32 type) {
+ return 1U << type;
+ }
+
+ [[nodiscard]] std::optional<u64> FindFreeRegion(u64 size, u64 alignment) noexcept {
+ ASSERT(std::has_single_bit(alignment));
+ const u64 alignment_log2 = std::countr_zero(alignment);
+ std::optional<u64> candidate;
+ u64 iterator = 0;
+ auto commit = commits.begin();
+ while (iterator + size <= allocation_size) {
+ candidate = candidate.value_or(iterator);
+ if (commit == commits.end()) {
+ break;
+ }
+ if (commit->Contains(*candidate, size)) {
+ candidate = std::nullopt;
+ }
+ iterator = Common::AlignUpLog2(commit->end, alignment_log2);
+ ++commit;
+ }
+ return candidate;
+ }
+
+ const vk::DeviceMemory memory; ///< Vulkan memory allocation handler.
+ const u64 allocation_size; ///< Size of this allocation.
+ const VkMemoryPropertyFlags property_flags; ///< Vulkan memory property flags.
+ const u32 shifted_memory_type; ///< Shifted Vulkan memory type.
+ std::vector<Range> commits; ///< All commit ranges done from this allocation.
+ std::span<u8> memory_mapped_span; ///< Memory mapped span. Empty if not queried before.
+#if defined(_WIN32) || defined(__linux__)
+ u32 owning_opengl_handle{}; ///< Owning OpenGL memory object handle.
+#endif
+};
+
+MemoryCommit::MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_,
+ u64 end_) noexcept
+ : allocation{allocation_}, memory{memory_}, begin{begin_}, end{end_} {}
+
+MemoryCommit::~MemoryCommit() {
+ Release();
+}
+
+MemoryCommit& MemoryCommit::operator=(MemoryCommit&& rhs) noexcept {
+ Release();
+ allocation = std::exchange(rhs.allocation, nullptr);
+ memory = rhs.memory;
+ begin = rhs.begin;
+ end = rhs.end;
+ span = std::exchange(rhs.span, std::span<u8>{});
+ return *this;
+}
+
+MemoryCommit::MemoryCommit(MemoryCommit&& rhs) noexcept
+ : allocation{std::exchange(rhs.allocation, nullptr)}, memory{rhs.memory}, begin{rhs.begin},
+ end{rhs.end}, span{std::exchange(rhs.span, std::span<u8>{})} {}
+
+std::span<u8> MemoryCommit::Map() {
+ if (span.empty()) {
+ span = allocation->Map().subspan(begin, end - begin);
+ }
+ return span;
+}
+
+u32 MemoryCommit::ExportOpenGLHandle() const {
+ return allocation->ExportOpenGLHandle();
+}
+
+void MemoryCommit::Release() {
+ if (allocation) {
+ allocation->Free(begin);
+ }
+}
+
+MemoryAllocator::MemoryAllocator(const Device& device_, bool export_allocations_)
+ : device{device_}, properties{device_.GetPhysical().GetMemoryProperties()},
+ export_allocations{export_allocations_} {}
+
+MemoryAllocator::~MemoryAllocator() = default;
+
+MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) {
+ // Find the fastest memory flags we can afford with the current requirements
+ const VkMemoryPropertyFlags flags = MemoryPropertyFlags(requirements.memoryTypeBits, usage);
+ if (std::optional<MemoryCommit> commit = TryCommit(requirements, flags)) {
+ return std::move(*commit);
+ }
+ // Commit has failed, allocate more memory.
+ // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory.
+ AllocMemory(flags, requirements.memoryTypeBits, AllocationChunkSize(requirements.size));
+
+ // Commit again, this time it won't fail since there's a fresh allocation above.
+ // If it does, there's a bug.
+ return TryCommit(requirements, flags).value();
+}
+
+MemoryCommit MemoryAllocator::Commit(const vk::Buffer& buffer, MemoryUsage usage) {
+ auto commit = Commit(device.GetLogical().GetBufferMemoryRequirements(*buffer), usage);
+ buffer.BindMemory(commit.Memory(), commit.Offset());
+ return commit;
+}
+
+MemoryCommit MemoryAllocator::Commit(const vk::Image& image, MemoryUsage usage) {
+ auto commit = Commit(device.GetLogical().GetImageMemoryRequirements(*image), usage);
+ image.BindMemory(commit.Memory(), commit.Offset());
+ return commit;
+}
+
+void MemoryAllocator::AllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) {
+ const u32 type = FindType(flags, type_mask).value();
+ const VkExportMemoryAllocateInfo export_allocate_info{
+ .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
+ .pNext = nullptr,
+#ifdef _WIN32
+ .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT,
+#elif __linux__
+ .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT,
+#else
+ .handleTypes = 0,
+#endif
+ };
+ vk::DeviceMemory memory = device.GetLogical().AllocateMemory({
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .pNext = export_allocations ? &export_allocate_info : nullptr,
+ .allocationSize = size,
+ .memoryTypeIndex = type,
+ });
+ allocations.push_back(std::make_unique<MemoryAllocation>(std::move(memory), flags, size, type));
+}
+
+std::optional<MemoryCommit> MemoryAllocator::TryCommit(const VkMemoryRequirements& requirements,
+ VkMemoryPropertyFlags flags) {
+ for (auto& allocation : allocations) {
+ if (!allocation->IsCompatible(flags, requirements.memoryTypeBits)) {
+ continue;
+ }
+ if (auto commit = allocation->Commit(requirements.size, requirements.alignment)) {
+ return commit;
+ }
+ }
+ return std::nullopt;
+}
+
+VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask, MemoryUsage usage) const {
+ return MemoryPropertyFlags(type_mask, MemoryUsagePropertyFlags(usage));
+}
+
+VkMemoryPropertyFlags MemoryAllocator::MemoryPropertyFlags(u32 type_mask,
+ VkMemoryPropertyFlags flags) const {
+ if (FindType(flags, type_mask)) {
+ // Found a memory type with those requirements
+ return flags;
+ }
+ if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
+ // Remove host cached bit in case it's not supported
+ return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
+ }
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+ // Remove device local, if it's not supported by the requested resource
+ return MemoryPropertyFlags(type_mask, flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ }
+ UNREACHABLE_MSG("No compatible memory types found");
+ return 0;
+}
+
+std::optional<u32> MemoryAllocator::FindType(VkMemoryPropertyFlags flags, u32 type_mask) const {
+ for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) {
+ const VkMemoryPropertyFlags type_flags = properties.memoryTypes[type_index].propertyFlags;
+ if ((type_mask & (1U << type_index)) && (type_flags & flags)) {
+ // The type matches in type and in the wanted properties.
+ return type_index;
+ }
+ }
+ // Failed to find index
+ return std::nullopt;
+}
+
+bool IsHostVisible(MemoryUsage usage) noexcept {
+ switch (usage) {
+ case MemoryUsage::DeviceLocal:
+ return false;
+ case MemoryUsage::Upload:
+ case MemoryUsage::Download:
+ return true;
+ }
+ UNREACHABLE_MSG("Invalid memory usage={}", usage);
+ return false;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
new file mode 100644
index 000000000..d1ce29450
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -0,0 +1,129 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <span>
+#include <utility>
+#include <vector>
+#include "common/common_types.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan {
+
+class Device;
+class MemoryMap;
+class MemoryAllocation;
+
+/// Hints and requirements for the backing memory type of a commit
+enum class MemoryUsage {
+ DeviceLocal, ///< Hints device local usages, fastest memory type to read and write from the GPU
+ Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads
+ Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks
+};
+
+/// Ownership handle of a memory commitment.
+/// Points to a subregion of a memory allocation.
+class MemoryCommit {
+public:
+ explicit MemoryCommit() noexcept = default;
+ explicit MemoryCommit(MemoryAllocation* allocation_, VkDeviceMemory memory_, u64 begin_,
+ u64 end_) noexcept;
+ ~MemoryCommit();
+
+ MemoryCommit& operator=(MemoryCommit&&) noexcept;
+ MemoryCommit(MemoryCommit&&) noexcept;
+
+ MemoryCommit& operator=(const MemoryCommit&) = delete;
+ MemoryCommit(const MemoryCommit&) = delete;
+
+ /// Returns a host visible memory map.
+ /// It will map the backing allocation if it hasn't been mapped before.
+ std::span<u8> Map();
+
+ /// Returns an non-owning OpenGL handle, creating one if it doesn't exist.
+ u32 ExportOpenGLHandle() const;
+
+ /// Returns the Vulkan memory handler.
+ VkDeviceMemory Memory() const {
+ return memory;
+ }
+
+ /// Returns the start position of the commit relative to the allocation.
+ VkDeviceSize Offset() const {
+ return static_cast<VkDeviceSize>(begin);
+ }
+
+private:
+ void Release();
+
+ MemoryAllocation* allocation{}; ///< Pointer to the large memory allocation.
+ VkDeviceMemory memory{}; ///< Vulkan device memory handler.
+ u64 begin{}; ///< Beginning offset in bytes to where the commit exists.
+ u64 end{}; ///< Offset in bytes where the commit ends.
+ std::span<u8> span; ///< Host visible memory span. Empty if not queried before.
+};
+
+/// Memory allocator container.
+/// Allocates and releases memory allocations on demand.
+class MemoryAllocator {
+public:
+ /**
+ * Construct memory allocator
+ *
+ * @param device_ Device to allocate from
+ * @param export_allocations_ True when allocations have to be exported
+ *
+ * @throw vk::Exception on failure
+ */
+ explicit MemoryAllocator(const Device& device_, bool export_allocations_);
+ ~MemoryAllocator();
+
+ MemoryAllocator& operator=(const MemoryAllocator&) = delete;
+ MemoryAllocator(const MemoryAllocator&) = delete;
+
+ /**
+ * Commits a memory with the specified requirements.
+ *
+ * @param requirements Requirements returned from a Vulkan call.
+ * @param usage Indicates how the memory will be used.
+ *
+ * @returns A memory commit.
+ */
+ MemoryCommit Commit(const VkMemoryRequirements& requirements, MemoryUsage usage);
+
+ /// Commits memory required by the buffer and binds it.
+ MemoryCommit Commit(const vk::Buffer& buffer, MemoryUsage usage);
+
+ /// Commits memory required by the image and binds it.
+ MemoryCommit Commit(const vk::Image& image, MemoryUsage usage);
+
+private:
+ /// Allocates a chunk of memory.
+ void AllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size);
+
+ /// Tries to allocate a memory commit.
+ std::optional<MemoryCommit> TryCommit(const VkMemoryRequirements& requirements,
+ VkMemoryPropertyFlags flags);
+
+ /// Returns the fastest compatible memory property flags from a wanted usage.
+ VkMemoryPropertyFlags MemoryPropertyFlags(u32 type_mask, MemoryUsage usage) const;
+
+ /// Returns the fastest compatible memory property flags from the wanted flags.
+ VkMemoryPropertyFlags MemoryPropertyFlags(u32 type_mask, VkMemoryPropertyFlags flags) const;
+
+ /// Returns index to the fastest memory type compatible with the passed requirements.
+ std::optional<u32> FindType(VkMemoryPropertyFlags flags, u32 type_mask) const;
+
+ const Device& device; ///< Device handle.
+ const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
+ const bool export_allocations; ///< True when memory allocations have to be exported.
+ std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
+};
+
+/// Returns true when a memory usage is guaranteed to be host visible.
+bool IsHostVisible(MemoryUsage usage) noexcept;
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp
new file mode 100644
index 000000000..3c3238f96
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_surface.cpp
@@ -0,0 +1,81 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/frontend/emu_window.h"
+#include "video_core/vulkan_common/vulkan_surface.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+// Include these late to avoid polluting previous headers
+#ifdef _WIN32
+#include <windows.h>
+// ensure include order
+#include <vulkan/vulkan_win32.h>
+#endif
+
+#if !defined(_WIN32) && !defined(__APPLE__)
+#include <X11/Xlib.h>
+#include <vulkan/vulkan_wayland.h>
+#include <vulkan/vulkan_xlib.h>
+#endif
+
+namespace Vulkan {
+
+vk::SurfaceKHR CreateSurface(const vk::Instance& instance,
+ const Core::Frontend::EmuWindow& emu_window) {
+ [[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch();
+ [[maybe_unused]] const auto& window_info = emu_window.GetWindowInfo();
+ VkSurfaceKHR unsafe_surface = nullptr;
+
+#ifdef _WIN32
+ if (window_info.type == Core::Frontend::WindowSystemType::Windows) {
+ const HWND hWnd = static_cast<HWND>(window_info.render_surface);
+ const VkWin32SurfaceCreateInfoKHR win32_ci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
+ nullptr, 0, nullptr, hWnd};
+ const auto vkCreateWin32SurfaceKHR = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>(
+ dld.vkGetInstanceProcAddr(*instance, "vkCreateWin32SurfaceKHR"));
+ if (!vkCreateWin32SurfaceKHR ||
+ vkCreateWin32SurfaceKHR(*instance, &win32_ci, nullptr, &unsafe_surface) != VK_SUCCESS) {
+ LOG_ERROR(Render_Vulkan, "Failed to initialize Win32 surface");
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ }
+#endif
+#if !defined(_WIN32) && !defined(__APPLE__)
+ if (window_info.type == Core::Frontend::WindowSystemType::X11) {
+ const VkXlibSurfaceCreateInfoKHR xlib_ci{
+ VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, 0,
+ static_cast<Display*>(window_info.display_connection),
+ reinterpret_cast<Window>(window_info.render_surface)};
+ const auto vkCreateXlibSurfaceKHR = reinterpret_cast<PFN_vkCreateXlibSurfaceKHR>(
+ dld.vkGetInstanceProcAddr(*instance, "vkCreateXlibSurfaceKHR"));
+ if (!vkCreateXlibSurfaceKHR ||
+ vkCreateXlibSurfaceKHR(*instance, &xlib_ci, nullptr, &unsafe_surface) != VK_SUCCESS) {
+ LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface");
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ }
+ if (window_info.type == Core::Frontend::WindowSystemType::Wayland) {
+ const VkWaylandSurfaceCreateInfoKHR wayland_ci{
+ VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
+ static_cast<wl_display*>(window_info.display_connection),
+ static_cast<wl_surface*>(window_info.render_surface)};
+ const auto vkCreateWaylandSurfaceKHR = reinterpret_cast<PFN_vkCreateWaylandSurfaceKHR>(
+ dld.vkGetInstanceProcAddr(*instance, "vkCreateWaylandSurfaceKHR"));
+ if (!vkCreateWaylandSurfaceKHR ||
+ vkCreateWaylandSurfaceKHR(*instance, &wayland_ci, nullptr, &unsafe_surface) !=
+ VK_SUCCESS) {
+ LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface");
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ }
+#endif
+ if (!unsafe_surface) {
+ LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform");
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ return vk::SurfaceKHR(unsafe_surface, *instance, dld);
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_surface.h b/src/video_core/vulkan_common/vulkan_surface.h
new file mode 100644
index 000000000..05a169e32
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_surface.h
@@ -0,0 +1,18 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Core::Frontend {
+class EmuWindow;
+}
+
+namespace Vulkan {
+
+[[nodiscard]] vk::SurfaceKHR CreateSurface(const vk::Instance& instance,
+ const Core::Frontend::EmuWindow& emu_window);
+
+} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
new file mode 100644
index 000000000..2aa0ffbe6
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -0,0 +1,940 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <exception>
+#include <memory>
+#include <optional>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/logging/log.h"
+
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Vulkan::vk {
+
+namespace {
+
+template <typename Func>
+void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld,
+ Func&& func) {
+ // Calling GetProperties calls Vulkan more than needed. But they are supposed to be cheap
+ // functions.
+ std::stable_sort(devices.begin(), devices.end(),
+ [&dld, &func](VkPhysicalDevice lhs, VkPhysicalDevice rhs) {
+ return func(vk::PhysicalDevice(lhs, dld).GetProperties(),
+ vk::PhysicalDevice(rhs, dld).GetProperties());
+ });
+}
+
+void SortPhysicalDevicesPerVendor(std::vector<VkPhysicalDevice>& devices,
+ const InstanceDispatch& dld,
+ std::initializer_list<u32> vendor_ids) {
+ for (auto it = vendor_ids.end(); it != vendor_ids.begin();) {
+ --it;
+ SortPhysicalDevices(devices, dld, [id = *it](const auto& lhs, const auto& rhs) {
+ return lhs.vendorID == id && rhs.vendorID != id;
+ });
+ }
+}
+
+void SortPhysicalDevices(std::vector<VkPhysicalDevice>& devices, const InstanceDispatch& dld) {
+ // Sort by name, this will set a base and make GPUs with higher numbers appear first
+ // (e.g. GTX 1650 will intentionally be listed before a GTX 1080).
+ SortPhysicalDevices(devices, dld, [](const auto& lhs, const auto& rhs) {
+ return std::string_view{lhs.deviceName} > std::string_view{rhs.deviceName};
+ });
+ // Prefer discrete over non-discrete
+ SortPhysicalDevices(devices, dld, [](const auto& lhs, const auto& rhs) {
+ return lhs.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
+ rhs.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU;
+ });
+ // Prefer Nvidia over AMD, AMD over Intel, Intel over the rest.
+ SortPhysicalDevicesPerVendor(devices, dld, {0x10DE, 0x1002, 0x8086});
+}
+
+template <typename T>
+bool Proc(T& result, const InstanceDispatch& dld, const char* proc_name,
+ VkInstance instance = nullptr) noexcept {
+ result = reinterpret_cast<T>(dld.vkGetInstanceProcAddr(instance, proc_name));
+ return result != nullptr;
+}
+
+template <typename T>
+void Proc(T& result, const DeviceDispatch& dld, const char* proc_name, VkDevice device) noexcept {
+ result = reinterpret_cast<T>(dld.vkGetDeviceProcAddr(device, proc_name));
+}
+
+void Load(VkDevice device, DeviceDispatch& dld) noexcept {
+#define X(name) Proc(dld.name, dld, #name, device)
+ X(vkAcquireNextImageKHR);
+ X(vkAllocateCommandBuffers);
+ X(vkAllocateDescriptorSets);
+ X(vkAllocateMemory);
+ X(vkBeginCommandBuffer);
+ X(vkBindBufferMemory);
+ X(vkBindImageMemory);
+ X(vkCmdBeginQuery);
+ X(vkCmdBeginRenderPass);
+ X(vkCmdBeginTransformFeedbackEXT);
+ X(vkCmdBeginDebugUtilsLabelEXT);
+ X(vkCmdBindDescriptorSets);
+ X(vkCmdBindIndexBuffer);
+ X(vkCmdBindPipeline);
+ X(vkCmdBindTransformFeedbackBuffersEXT);
+ X(vkCmdBindVertexBuffers);
+ X(vkCmdBlitImage);
+ X(vkCmdClearAttachments);
+ X(vkCmdCopyBuffer);
+ X(vkCmdCopyBufferToImage);
+ X(vkCmdCopyImage);
+ X(vkCmdCopyImageToBuffer);
+ X(vkCmdDispatch);
+ X(vkCmdDraw);
+ X(vkCmdDrawIndexed);
+ X(vkCmdEndQuery);
+ X(vkCmdEndRenderPass);
+ X(vkCmdEndTransformFeedbackEXT);
+ X(vkCmdEndDebugUtilsLabelEXT);
+ X(vkCmdFillBuffer);
+ X(vkCmdPipelineBarrier);
+ X(vkCmdPushConstants);
+ X(vkCmdSetBlendConstants);
+ X(vkCmdSetDepthBias);
+ X(vkCmdSetDepthBounds);
+ X(vkCmdSetEvent);
+ X(vkCmdSetScissor);
+ X(vkCmdSetStencilCompareMask);
+ X(vkCmdSetStencilReference);
+ X(vkCmdSetStencilWriteMask);
+ X(vkCmdSetViewport);
+ X(vkCmdWaitEvents);
+ X(vkCmdBindVertexBuffers2EXT);
+ X(vkCmdSetCullModeEXT);
+ X(vkCmdSetDepthBoundsTestEnableEXT);
+ X(vkCmdSetDepthCompareOpEXT);
+ X(vkCmdSetDepthTestEnableEXT);
+ X(vkCmdSetDepthWriteEnableEXT);
+ X(vkCmdSetFrontFaceEXT);
+ X(vkCmdSetPrimitiveTopologyEXT);
+ X(vkCmdSetStencilOpEXT);
+ X(vkCmdSetStencilTestEnableEXT);
+ X(vkCmdResolveImage);
+ X(vkCreateBuffer);
+ X(vkCreateBufferView);
+ X(vkCreateCommandPool);
+ X(vkCreateComputePipelines);
+ X(vkCreateDescriptorPool);
+ X(vkCreateDescriptorSetLayout);
+ X(vkCreateDescriptorUpdateTemplateKHR);
+ X(vkCreateEvent);
+ X(vkCreateFence);
+ X(vkCreateFramebuffer);
+ X(vkCreateGraphicsPipelines);
+ X(vkCreateImage);
+ X(vkCreateImageView);
+ X(vkCreatePipelineLayout);
+ X(vkCreateQueryPool);
+ X(vkCreateRenderPass);
+ X(vkCreateSampler);
+ X(vkCreateSemaphore);
+ X(vkCreateShaderModule);
+ X(vkCreateSwapchainKHR);
+ X(vkDestroyBuffer);
+ X(vkDestroyBufferView);
+ X(vkDestroyCommandPool);
+ X(vkDestroyDescriptorPool);
+ X(vkDestroyDescriptorSetLayout);
+ X(vkDestroyDescriptorUpdateTemplateKHR);
+ X(vkDestroyEvent);
+ X(vkDestroyFence);
+ X(vkDestroyFramebuffer);
+ X(vkDestroyImage);
+ X(vkDestroyImageView);
+ X(vkDestroyPipeline);
+ X(vkDestroyPipelineLayout);
+ X(vkDestroyQueryPool);
+ X(vkDestroyRenderPass);
+ X(vkDestroySampler);
+ X(vkDestroySemaphore);
+ X(vkDestroyShaderModule);
+ X(vkDestroySwapchainKHR);
+ X(vkDeviceWaitIdle);
+ X(vkEndCommandBuffer);
+ X(vkFreeCommandBuffers);
+ X(vkFreeDescriptorSets);
+ X(vkFreeMemory);
+ X(vkGetBufferMemoryRequirements2);
+ X(vkGetDeviceQueue);
+ X(vkGetEventStatus);
+ X(vkGetFenceStatus);
+ X(vkGetImageMemoryRequirements);
+ X(vkGetMemoryFdKHR);
+#ifdef _WIN32
+ X(vkGetMemoryWin32HandleKHR);
+#endif
+ X(vkGetQueryPoolResults);
+ X(vkGetSemaphoreCounterValueKHR);
+ X(vkMapMemory);
+ X(vkQueueSubmit);
+ X(vkResetFences);
+ X(vkResetQueryPoolEXT);
+ X(vkSetDebugUtilsObjectNameEXT);
+ X(vkSetDebugUtilsObjectTagEXT);
+ X(vkUnmapMemory);
+ X(vkUpdateDescriptorSetWithTemplateKHR);
+ X(vkUpdateDescriptorSets);
+ X(vkWaitForFences);
+ X(vkWaitSemaphoresKHR);
+#undef X
+}
+
+template <typename T>
+void SetObjectName(const DeviceDispatch* dld, VkDevice device, T handle, VkObjectType type,
+ const char* name) {
+ const VkDebugUtilsObjectNameInfoEXT name_info{
+ .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
+ .pNext = nullptr,
+ .objectType = VK_OBJECT_TYPE_IMAGE,
+ .objectHandle = reinterpret_cast<u64>(handle),
+ .pObjectName = name,
+ };
+ Check(dld->vkSetDebugUtilsObjectNameEXT(device, &name_info));
+}
+
+} // Anonymous namespace
+
+bool Load(InstanceDispatch& dld) noexcept {
+#define X(name) Proc(dld.name, dld, #name)
+ return X(vkCreateInstance) && X(vkEnumerateInstanceExtensionProperties) &&
+ X(vkEnumerateInstanceLayerProperties);
+#undef X
+}
+
+bool Load(VkInstance instance, InstanceDispatch& dld) noexcept {
+#define X(name) Proc(dld.name, dld, #name, instance)
+ // These functions may fail to load depending on the enabled extensions.
+ // Don't return a failure on these.
+ X(vkCreateDebugUtilsMessengerEXT);
+ X(vkDestroyDebugUtilsMessengerEXT);
+ X(vkDestroySurfaceKHR);
+ X(vkGetPhysicalDeviceFeatures2KHR);
+ X(vkGetPhysicalDeviceProperties2KHR);
+ X(vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
+ X(vkGetPhysicalDeviceSurfaceFormatsKHR);
+ X(vkGetPhysicalDeviceSurfacePresentModesKHR);
+ X(vkGetPhysicalDeviceSurfaceSupportKHR);
+ X(vkGetSwapchainImagesKHR);
+ X(vkQueuePresentKHR);
+
+ return X(vkCreateDevice) && X(vkDestroyDevice) && X(vkDestroyDevice) &&
+ X(vkEnumerateDeviceExtensionProperties) && X(vkEnumeratePhysicalDevices) &&
+ X(vkGetDeviceProcAddr) && X(vkGetPhysicalDeviceFormatProperties) &&
+ X(vkGetPhysicalDeviceMemoryProperties) && X(vkGetPhysicalDeviceProperties) &&
+ X(vkGetPhysicalDeviceQueueFamilyProperties);
+#undef X
+}
+
+const char* Exception::what() const noexcept {
+ return ToString(result);
+}
+
+const char* ToString(VkResult result) noexcept {
+ switch (result) {
+ case VkResult::VK_SUCCESS:
+ return "VK_SUCCESS";
+ case VkResult::VK_NOT_READY:
+ return "VK_NOT_READY";
+ case VkResult::VK_TIMEOUT:
+ return "VK_TIMEOUT";
+ case VkResult::VK_EVENT_SET:
+ return "VK_EVENT_SET";
+ case VkResult::VK_EVENT_RESET:
+ return "VK_EVENT_RESET";
+ case VkResult::VK_INCOMPLETE:
+ return "VK_INCOMPLETE";
+ case VkResult::VK_ERROR_OUT_OF_HOST_MEMORY:
+ return "VK_ERROR_OUT_OF_HOST_MEMORY";
+ case VkResult::VK_ERROR_OUT_OF_DEVICE_MEMORY:
+ return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
+ case VkResult::VK_ERROR_INITIALIZATION_FAILED:
+ return "VK_ERROR_INITIALIZATION_FAILED";
+ case VkResult::VK_ERROR_DEVICE_LOST:
+ return "VK_ERROR_DEVICE_LOST";
+ case VkResult::VK_ERROR_MEMORY_MAP_FAILED:
+ return "VK_ERROR_MEMORY_MAP_FAILED";
+ case VkResult::VK_ERROR_LAYER_NOT_PRESENT:
+ return "VK_ERROR_LAYER_NOT_PRESENT";
+ case VkResult::VK_ERROR_EXTENSION_NOT_PRESENT:
+ return "VK_ERROR_EXTENSION_NOT_PRESENT";
+ case VkResult::VK_ERROR_FEATURE_NOT_PRESENT:
+ return "VK_ERROR_FEATURE_NOT_PRESENT";
+ case VkResult::VK_ERROR_INCOMPATIBLE_DRIVER:
+ return "VK_ERROR_INCOMPATIBLE_DRIVER";
+ case VkResult::VK_ERROR_TOO_MANY_OBJECTS:
+ return "VK_ERROR_TOO_MANY_OBJECTS";
+ case VkResult::VK_ERROR_FORMAT_NOT_SUPPORTED:
+ return "VK_ERROR_FORMAT_NOT_SUPPORTED";
+ case VkResult::VK_ERROR_FRAGMENTED_POOL:
+ return "VK_ERROR_FRAGMENTED_POOL";
+ case VkResult::VK_ERROR_OUT_OF_POOL_MEMORY:
+ return "VK_ERROR_OUT_OF_POOL_MEMORY";
+ case VkResult::VK_ERROR_INVALID_EXTERNAL_HANDLE:
+ return "VK_ERROR_INVALID_EXTERNAL_HANDLE";
+ case VkResult::VK_ERROR_SURFACE_LOST_KHR:
+ return "VK_ERROR_SURFACE_LOST_KHR";
+ case VkResult::VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
+ return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
+ case VkResult::VK_SUBOPTIMAL_KHR:
+ return "VK_SUBOPTIMAL_KHR";
+ case VkResult::VK_ERROR_OUT_OF_DATE_KHR:
+ return "VK_ERROR_OUT_OF_DATE_KHR";
+ case VkResult::VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
+ return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
+ case VkResult::VK_ERROR_VALIDATION_FAILED_EXT:
+ return "VK_ERROR_VALIDATION_FAILED_EXT";
+ case VkResult::VK_ERROR_INVALID_SHADER_NV:
+ return "VK_ERROR_INVALID_SHADER_NV";
+ case VkResult::VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT:
+ return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
+ case VkResult::VK_ERROR_FRAGMENTATION_EXT:
+ return "VK_ERROR_FRAGMENTATION_EXT";
+ case VkResult::VK_ERROR_NOT_PERMITTED_EXT:
+ return "VK_ERROR_NOT_PERMITTED_EXT";
+ case VkResult::VK_ERROR_INVALID_DEVICE_ADDRESS_EXT:
+ return "VK_ERROR_INVALID_DEVICE_ADDRESS_EXT";
+ case VkResult::VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
+ return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
+ case VkResult::VK_ERROR_UNKNOWN:
+ return "VK_ERROR_UNKNOWN";
+ case VkResult::VK_ERROR_INCOMPATIBLE_VERSION_KHR:
+ return "VK_ERROR_INCOMPATIBLE_VERSION_KHR";
+ case VkResult::VK_THREAD_IDLE_KHR:
+ return "VK_THREAD_IDLE_KHR";
+ case VkResult::VK_THREAD_DONE_KHR:
+ return "VK_THREAD_DONE_KHR";
+ case VkResult::VK_OPERATION_DEFERRED_KHR:
+ return "VK_OPERATION_DEFERRED_KHR";
+ case VkResult::VK_OPERATION_NOT_DEFERRED_KHR:
+ return "VK_OPERATION_NOT_DEFERRED_KHR";
+ case VkResult::VK_PIPELINE_COMPILE_REQUIRED_EXT:
+ return "VK_PIPELINE_COMPILE_REQUIRED_EXT";
+ case VkResult::VK_RESULT_MAX_ENUM:
+ return "VK_RESULT_MAX_ENUM";
+ }
+ return "Unknown";
+}
+
+void Destroy(VkInstance instance, const InstanceDispatch& dld) noexcept {
+ dld.vkDestroyInstance(instance, nullptr);
+}
+
+void Destroy(VkDevice device, const InstanceDispatch& dld) noexcept {
+ dld.vkDestroyDevice(device, nullptr);
+}
+
+void Destroy(VkDevice device, VkBuffer handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyBuffer(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkBufferView handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyBufferView(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkCommandPool handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyCommandPool(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkDescriptorPool handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyDescriptorPool(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkDescriptorSetLayout handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyDescriptorSetLayout(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkDescriptorUpdateTemplateKHR handle,
+ const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyDescriptorUpdateTemplateKHR(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkDeviceMemory handle, const DeviceDispatch& dld) noexcept {
+ dld.vkFreeMemory(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkEvent handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyEvent(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkFence handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyFence(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkFramebuffer handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyFramebuffer(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkImage handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyImage(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkImageView handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyImageView(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkPipeline handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyPipeline(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkPipelineLayout handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyPipelineLayout(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkQueryPool handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyQueryPool(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkRenderPass handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyRenderPass(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkSampler handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroySampler(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkSwapchainKHR handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroySwapchainKHR(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkSemaphore handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroySemaphore(device, handle, nullptr);
+}
+
+void Destroy(VkDevice device, VkShaderModule handle, const DeviceDispatch& dld) noexcept {
+ dld.vkDestroyShaderModule(device, handle, nullptr);
+}
+
+void Destroy(VkInstance instance, VkDebugUtilsMessengerEXT handle,
+ const InstanceDispatch& dld) noexcept {
+ dld.vkDestroyDebugUtilsMessengerEXT(instance, handle, nullptr);
+}
+
+void Destroy(VkInstance instance, VkSurfaceKHR handle, const InstanceDispatch& dld) noexcept {
+ dld.vkDestroySurfaceKHR(instance, handle, nullptr);
+}
+
+VkResult Free(VkDevice device, VkDescriptorPool handle, Span<VkDescriptorSet> sets,
+ const DeviceDispatch& dld) noexcept {
+ return dld.vkFreeDescriptorSets(device, handle, sets.size(), sets.data());
+}
+
+VkResult Free(VkDevice device, VkCommandPool handle, Span<VkCommandBuffer> buffers,
+ const DeviceDispatch& dld) noexcept {
+ dld.vkFreeCommandBuffers(device, handle, buffers.size(), buffers.data());
+ return VK_SUCCESS;
+}
+
+Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char*> extensions,
+ InstanceDispatch& dispatch) {
+ const VkApplicationInfo application_info{
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .pNext = nullptr,
+ .pApplicationName = "yuzu Emulator",
+ .applicationVersion = VK_MAKE_VERSION(0, 1, 0),
+ .pEngineName = "yuzu Emulator",
+ .engineVersion = VK_MAKE_VERSION(0, 1, 0),
+ .apiVersion = version,
+ };
+ const VkInstanceCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .pApplicationInfo = &application_info,
+ .enabledLayerCount = layers.size(),
+ .ppEnabledLayerNames = layers.data(),
+ .enabledExtensionCount = extensions.size(),
+ .ppEnabledExtensionNames = extensions.data(),
+ };
+ VkInstance instance;
+ Check(dispatch.vkCreateInstance(&ci, nullptr, &instance));
+ if (!Proc(dispatch.vkDestroyInstance, dispatch, "vkDestroyInstance", instance)) {
+ // We successfully created an instance but the destroy function couldn't be loaded.
+ // This is a good moment to panic.
+ throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED);
+ }
+ return Instance(instance, dispatch);
+}
+
+std::vector<VkPhysicalDevice> Instance::EnumeratePhysicalDevices() const {
+ u32 num;
+ Check(dld->vkEnumeratePhysicalDevices(handle, &num, nullptr));
+ std::vector<VkPhysicalDevice> physical_devices(num);
+ Check(dld->vkEnumeratePhysicalDevices(handle, &num, physical_devices.data()));
+ SortPhysicalDevices(physical_devices, *dld);
+ return physical_devices;
+}
+
+DebugUtilsMessenger Instance::CreateDebugUtilsMessenger(
+ const VkDebugUtilsMessengerCreateInfoEXT& create_info) const {
+ VkDebugUtilsMessengerEXT object;
+ Check(dld->vkCreateDebugUtilsMessengerEXT(handle, &create_info, nullptr, &object));
+ return DebugUtilsMessenger(object, handle, *dld);
+}
+
+void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
+ Check(dld->vkBindBufferMemory(owner, handle, memory, offset));
+}
+
+void Buffer::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER, name);
+}
+
+void BufferView::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER_VIEW, name);
+}
+
+void Image::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
+ Check(dld->vkBindImageMemory(owner, handle, memory, offset));
+}
+
+void Image::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE, name);
+}
+
+void ImageView::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE_VIEW, name);
+}
+
+int DeviceMemory::GetMemoryFdKHR() const {
+ const VkMemoryGetFdInfoKHR get_fd_info{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
+ .pNext = nullptr,
+ .memory = handle,
+ .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR,
+ };
+ int fd;
+ Check(dld->vkGetMemoryFdKHR(owner, &get_fd_info, &fd));
+ return fd;
+}
+
+#ifdef _WIN32
+HANDLE DeviceMemory::GetMemoryWin32HandleKHR() const {
+ const VkMemoryGetWin32HandleInfoKHR get_win32_handle_info{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR,
+ .pNext = nullptr,
+ .memory = handle,
+ .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR,
+ };
+ HANDLE win32_handle;
+ Check(dld->vkGetMemoryWin32HandleKHR(owner, &get_win32_handle_info, &win32_handle));
+ return win32_handle;
+}
+#endif
+
+void DeviceMemory::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_DEVICE_MEMORY, name);
+}
+
+void Fence::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_FENCE, name);
+}
+
+void Framebuffer::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_FRAMEBUFFER, name);
+}
+
+DescriptorSets DescriptorPool::Allocate(const VkDescriptorSetAllocateInfo& ai) const {
+ const std::size_t num = ai.descriptorSetCount;
+ std::unique_ptr sets = std::make_unique<VkDescriptorSet[]>(num);
+ switch (const VkResult result = dld->vkAllocateDescriptorSets(owner, &ai, sets.get())) {
+ case VK_SUCCESS:
+ return DescriptorSets(std::move(sets), num, owner, handle, *dld);
+ case VK_ERROR_OUT_OF_POOL_MEMORY:
+ return {};
+ default:
+ throw Exception(result);
+ }
+}
+
+void DescriptorPool::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_DESCRIPTOR_POOL, name);
+}
+
+CommandBuffers CommandPool::Allocate(std::size_t num_buffers, VkCommandBufferLevel level) const {
+ const VkCommandBufferAllocateInfo ai{
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .pNext = nullptr,
+ .commandPool = handle,
+ .level = level,
+ .commandBufferCount = static_cast<u32>(num_buffers),
+ };
+
+ std::unique_ptr buffers = std::make_unique<VkCommandBuffer[]>(num_buffers);
+ switch (const VkResult result = dld->vkAllocateCommandBuffers(owner, &ai, buffers.get())) {
+ case VK_SUCCESS:
+ return CommandBuffers(std::move(buffers), num_buffers, owner, handle, *dld);
+ case VK_ERROR_OUT_OF_POOL_MEMORY:
+ return {};
+ default:
+ throw Exception(result);
+ }
+}
+
+void CommandPool::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_COMMAND_POOL, name);
+}
+
+std::vector<VkImage> SwapchainKHR::GetImages() const {
+ u32 num;
+ Check(dld->vkGetSwapchainImagesKHR(owner, handle, &num, nullptr));
+ std::vector<VkImage> images(num);
+ Check(dld->vkGetSwapchainImagesKHR(owner, handle, &num, images.data()));
+ return images;
+}
+
+void Event::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_EVENT, name);
+}
+
+void ShaderModule::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_SHADER_MODULE, name);
+}
+
+void Semaphore::SetObjectNameEXT(const char* name) const {
+ SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_SEMAPHORE, name);
+}
+
+Device Device::Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
+ Span<const char*> enabled_extensions, const void* next,
+ DeviceDispatch& dispatch) {
+ const VkDeviceCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .pNext = next,
+ .flags = 0,
+ .queueCreateInfoCount = queues_ci.size(),
+ .pQueueCreateInfos = queues_ci.data(),
+ .enabledLayerCount = 0,
+ .ppEnabledLayerNames = nullptr,
+ .enabledExtensionCount = enabled_extensions.size(),
+ .ppEnabledExtensionNames = enabled_extensions.data(),
+ .pEnabledFeatures = nullptr,
+ };
+ VkDevice device;
+ Check(dispatch.vkCreateDevice(physical_device, &ci, nullptr, &device));
+ Load(device, dispatch);
+ return Device(device, dispatch);
+}
+
+Queue Device::GetQueue(u32 family_index) const noexcept {
+ VkQueue queue;
+ dld->vkGetDeviceQueue(handle, family_index, 0, &queue);
+ return Queue(queue, *dld);
+}
+
+Buffer Device::CreateBuffer(const VkBufferCreateInfo& ci) const {
+ VkBuffer object;
+ Check(dld->vkCreateBuffer(handle, &ci, nullptr, &object));
+ return Buffer(object, handle, *dld);
+}
+
+BufferView Device::CreateBufferView(const VkBufferViewCreateInfo& ci) const {
+ VkBufferView object;
+ Check(dld->vkCreateBufferView(handle, &ci, nullptr, &object));
+ return BufferView(object, handle, *dld);
+}
+
+Image Device::CreateImage(const VkImageCreateInfo& ci) const {
+ VkImage object;
+ Check(dld->vkCreateImage(handle, &ci, nullptr, &object));
+ return Image(object, handle, *dld);
+}
+
+ImageView Device::CreateImageView(const VkImageViewCreateInfo& ci) const {
+ VkImageView object;
+ Check(dld->vkCreateImageView(handle, &ci, nullptr, &object));
+ return ImageView(object, handle, *dld);
+}
+
+Semaphore Device::CreateSemaphore() const {
+ static constexpr VkSemaphoreCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ };
+ return CreateSemaphore(ci);
+}
+
+Semaphore Device::CreateSemaphore(const VkSemaphoreCreateInfo& ci) const {
+ VkSemaphore object;
+ Check(dld->vkCreateSemaphore(handle, &ci, nullptr, &object));
+ return Semaphore(object, handle, *dld);
+}
+
+Fence Device::CreateFence(const VkFenceCreateInfo& ci) const {
+ VkFence object;
+ Check(dld->vkCreateFence(handle, &ci, nullptr, &object));
+ return Fence(object, handle, *dld);
+}
+
+DescriptorPool Device::CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const {
+ VkDescriptorPool object;
+ Check(dld->vkCreateDescriptorPool(handle, &ci, nullptr, &object));
+ return DescriptorPool(object, handle, *dld);
+}
+
+RenderPass Device::CreateRenderPass(const VkRenderPassCreateInfo& ci) const {
+ VkRenderPass object;
+ Check(dld->vkCreateRenderPass(handle, &ci, nullptr, &object));
+ return RenderPass(object, handle, *dld);
+}
+
+DescriptorSetLayout Device::CreateDescriptorSetLayout(
+ const VkDescriptorSetLayoutCreateInfo& ci) const {
+ VkDescriptorSetLayout object;
+ Check(dld->vkCreateDescriptorSetLayout(handle, &ci, nullptr, &object));
+ return DescriptorSetLayout(object, handle, *dld);
+}
+
+PipelineLayout Device::CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const {
+ VkPipelineLayout object;
+ Check(dld->vkCreatePipelineLayout(handle, &ci, nullptr, &object));
+ return PipelineLayout(object, handle, *dld);
+}
+
+Pipeline Device::CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci) const {
+ VkPipeline object;
+ Check(dld->vkCreateGraphicsPipelines(handle, nullptr, 1, &ci, nullptr, &object));
+ return Pipeline(object, handle, *dld);
+}
+
+Pipeline Device::CreateComputePipeline(const VkComputePipelineCreateInfo& ci) const {
+ VkPipeline object;
+ Check(dld->vkCreateComputePipelines(handle, nullptr, 1, &ci, nullptr, &object));
+ return Pipeline(object, handle, *dld);
+}
+
+Sampler Device::CreateSampler(const VkSamplerCreateInfo& ci) const {
+ VkSampler object;
+ Check(dld->vkCreateSampler(handle, &ci, nullptr, &object));
+ return Sampler(object, handle, *dld);
+}
+
+Framebuffer Device::CreateFramebuffer(const VkFramebufferCreateInfo& ci) const {
+ VkFramebuffer object;
+ Check(dld->vkCreateFramebuffer(handle, &ci, nullptr, &object));
+ return Framebuffer(object, handle, *dld);
+}
+
+CommandPool Device::CreateCommandPool(const VkCommandPoolCreateInfo& ci) const {
+ VkCommandPool object;
+ Check(dld->vkCreateCommandPool(handle, &ci, nullptr, &object));
+ return CommandPool(object, handle, *dld);
+}
+
+DescriptorUpdateTemplateKHR Device::CreateDescriptorUpdateTemplateKHR(
+ const VkDescriptorUpdateTemplateCreateInfoKHR& ci) const {
+ VkDescriptorUpdateTemplateKHR object;
+ Check(dld->vkCreateDescriptorUpdateTemplateKHR(handle, &ci, nullptr, &object));
+ return DescriptorUpdateTemplateKHR(object, handle, *dld);
+}
+
+QueryPool Device::CreateQueryPool(const VkQueryPoolCreateInfo& ci) const {
+ VkQueryPool object;
+ Check(dld->vkCreateQueryPool(handle, &ci, nullptr, &object));
+ return QueryPool(object, handle, *dld);
+}
+
+ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) const {
+ VkShaderModule object;
+ Check(dld->vkCreateShaderModule(handle, &ci, nullptr, &object));
+ return ShaderModule(object, handle, *dld);
+}
+
+Event Device::CreateEvent() const {
+ static constexpr VkEventCreateInfo ci{
+ .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ };
+
+ VkEvent object;
+ Check(dld->vkCreateEvent(handle, &ci, nullptr, &object));
+ return Event(object, handle, *dld);
+}
+
+SwapchainKHR Device::CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const {
+ VkSwapchainKHR object;
+ Check(dld->vkCreateSwapchainKHR(handle, &ci, nullptr, &object));
+ return SwapchainKHR(object, handle, *dld);
+}
+
+DeviceMemory Device::TryAllocateMemory(const VkMemoryAllocateInfo& ai) const noexcept {
+ VkDeviceMemory memory;
+ if (dld->vkAllocateMemory(handle, &ai, nullptr, &memory) != VK_SUCCESS) {
+ return {};
+ }
+ return DeviceMemory(memory, handle, *dld);
+}
+
+DeviceMemory Device::AllocateMemory(const VkMemoryAllocateInfo& ai) const {
+ VkDeviceMemory memory;
+ Check(dld->vkAllocateMemory(handle, &ai, nullptr, &memory));
+ return DeviceMemory(memory, handle, *dld);
+}
+
+VkMemoryRequirements Device::GetBufferMemoryRequirements(VkBuffer buffer,
+ void* pnext) const noexcept {
+ const VkBufferMemoryRequirementsInfo2 info{
+ .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2,
+ .pNext = nullptr,
+ .buffer = buffer,
+ };
+ VkMemoryRequirements2 requirements{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
+ .pNext = pnext,
+ .memoryRequirements{},
+ };
+ dld->vkGetBufferMemoryRequirements2(handle, &info, &requirements);
+ return requirements.memoryRequirements;
+}
+
+VkMemoryRequirements Device::GetImageMemoryRequirements(VkImage image) const noexcept {
+ VkMemoryRequirements requirements;
+ dld->vkGetImageMemoryRequirements(handle, image, &requirements);
+ return requirements;
+}
+
+void Device::UpdateDescriptorSets(Span<VkWriteDescriptorSet> writes,
+ Span<VkCopyDescriptorSet> copies) const noexcept {
+ dld->vkUpdateDescriptorSets(handle, writes.size(), writes.data(), copies.size(), copies.data());
+}
+
+VkPhysicalDeviceProperties PhysicalDevice::GetProperties() const noexcept {
+ VkPhysicalDeviceProperties properties;
+ dld->vkGetPhysicalDeviceProperties(physical_device, &properties);
+ return properties;
+}
+
+void PhysicalDevice::GetProperties2KHR(VkPhysicalDeviceProperties2KHR& properties) const noexcept {
+ dld->vkGetPhysicalDeviceProperties2KHR(physical_device, &properties);
+}
+
+VkPhysicalDeviceFeatures PhysicalDevice::GetFeatures() const noexcept {
+ VkPhysicalDeviceFeatures2KHR features2;
+ features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
+ features2.pNext = nullptr;
+ dld->vkGetPhysicalDeviceFeatures2KHR(physical_device, &features2);
+ return features2.features;
+}
+
+void PhysicalDevice::GetFeatures2KHR(VkPhysicalDeviceFeatures2KHR& features) const noexcept {
+ dld->vkGetPhysicalDeviceFeatures2KHR(physical_device, &features);
+}
+
+VkFormatProperties PhysicalDevice::GetFormatProperties(VkFormat format) const noexcept {
+ VkFormatProperties properties;
+ dld->vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties);
+ return properties;
+}
+
+std::vector<VkExtensionProperties> PhysicalDevice::EnumerateDeviceExtensionProperties() const {
+ u32 num;
+ dld->vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &num, nullptr);
+ std::vector<VkExtensionProperties> properties(num);
+ dld->vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &num, properties.data());
+ return properties;
+}
+
+std::vector<VkQueueFamilyProperties> PhysicalDevice::GetQueueFamilyProperties() const {
+ u32 num;
+ dld->vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &num, nullptr);
+ std::vector<VkQueueFamilyProperties> properties(num);
+ dld->vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &num, properties.data());
+ return properties;
+}
+
+bool PhysicalDevice::GetSurfaceSupportKHR(u32 queue_family_index, VkSurfaceKHR surface) const {
+ VkBool32 supported;
+ Check(dld->vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, queue_family_index, surface,
+ &supported));
+ return supported == VK_TRUE;
+}
+
+VkSurfaceCapabilitiesKHR PhysicalDevice::GetSurfaceCapabilitiesKHR(VkSurfaceKHR surface) const {
+ VkSurfaceCapabilitiesKHR capabilities;
+ Check(dld->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities));
+ return capabilities;
+}
+
+std::vector<VkSurfaceFormatKHR> PhysicalDevice::GetSurfaceFormatsKHR(VkSurfaceKHR surface) const {
+ u32 num;
+ Check(dld->vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &num, nullptr));
+ std::vector<VkSurfaceFormatKHR> formats(num);
+ Check(
+ dld->vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &num, formats.data()));
+ return formats;
+}
+
+std::vector<VkPresentModeKHR> PhysicalDevice::GetSurfacePresentModesKHR(
+ VkSurfaceKHR surface) const {
+ u32 num;
+ Check(dld->vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &num, nullptr));
+ std::vector<VkPresentModeKHR> modes(num);
+ Check(dld->vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &num,
+ modes.data()));
+ return modes;
+}
+
+VkPhysicalDeviceMemoryProperties PhysicalDevice::GetMemoryProperties() const noexcept {
+ VkPhysicalDeviceMemoryProperties properties;
+ dld->vkGetPhysicalDeviceMemoryProperties(physical_device, &properties);
+ return properties;
+}
+
+u32 AvailableVersion(const InstanceDispatch& dld) noexcept {
+ PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
+ if (!Proc(vkEnumerateInstanceVersion, dld, "vkEnumerateInstanceVersion")) {
+ // If the procedure is not found, Vulkan 1.0 is assumed
+ return VK_API_VERSION_1_0;
+ }
+ u32 version;
+ if (const VkResult result = vkEnumerateInstanceVersion(&version); result != VK_SUCCESS) {
+ LOG_ERROR(Render_Vulkan, "vkEnumerateInstanceVersion returned {}, assuming Vulkan 1.1",
+ ToString(result));
+ return VK_API_VERSION_1_1;
+ }
+ return version;
+}
+
+std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProperties(
+ const InstanceDispatch& dld) {
+ u32 num;
+ if (dld.vkEnumerateInstanceExtensionProperties(nullptr, &num, nullptr) != VK_SUCCESS) {
+ return std::nullopt;
+ }
+ std::vector<VkExtensionProperties> properties(num);
+ if (dld.vkEnumerateInstanceExtensionProperties(nullptr, &num, properties.data()) !=
+ VK_SUCCESS) {
+ return std::nullopt;
+ }
+ return properties;
+}
+
+std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
+ const InstanceDispatch& dld) {
+ u32 num;
+ if (dld.vkEnumerateInstanceLayerProperties(&num, nullptr) != VK_SUCCESS) {
+ return std::nullopt;
+ }
+ std::vector<VkLayerProperties> properties(num);
+ if (dld.vkEnumerateInstanceLayerProperties(&num, properties.data()) != VK_SUCCESS) {
+ return std::nullopt;
+ }
+ return properties;
+}
+
+} // namespace Vulkan::vk
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
new file mode 100644
index 000000000..3e36d356a
--- /dev/null
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -0,0 +1,1253 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <exception>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <span>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#define VK_NO_PROTOTYPES
+#ifdef _WIN32
+#define VK_USE_PLATFORM_WIN32_KHR
+#endif
+#include <vulkan/vulkan.h>
+
+// Sanitize macros
+#ifdef CreateEvent
+#undef CreateEvent
+#endif
+#ifdef CreateSemaphore
+#undef CreateSemaphore
+#endif
+
+#include "common/common_types.h"
+
+#ifdef _MSC_VER
+#pragma warning(disable : 26812) // Disable prefer enum class over enum
+#endif
+
+namespace Vulkan::vk {
+
+/**
+ * Span for Vulkan arrays.
+ * Based on std::span but optimized for array access instead of iterators.
+ * Size returns uint32_t instead of size_t to ease interaction with Vulkan functions.
+ */
+template <typename T>
+class Span {
+public:
+ using value_type = T;
+ using size_type = u32;
+ using difference_type = std::ptrdiff_t;
+ using reference = const T&;
+ using const_reference = const T&;
+ using pointer = const T*;
+ using const_pointer = const T*;
+ using iterator = const T*;
+ using const_iterator = const T*;
+
+ /// Construct an empty span.
+ constexpr Span() noexcept = default;
+
+ /// Construct an empty span
+ constexpr Span(std::nullptr_t) noexcept {}
+
+ /// Construct a span from a single element.
+ constexpr Span(const T& value) noexcept : ptr{&value}, num{1} {}
+
+ /// Construct a span from a range.
+ template <typename Range>
+ // requires std::data(const Range&)
+ // requires std::size(const Range&)
+ constexpr Span(const Range& range) : ptr{std::data(range)}, num{std::size(range)} {}
+
+ /// Construct a span from a pointer and a size.
+ /// This is inteded for subranges.
+ constexpr Span(const T* ptr_, std::size_t num_) noexcept : ptr{ptr_}, num{num_} {}
+
+ /// Returns the data pointer by the span.
+ constexpr const T* data() const noexcept {
+ return ptr;
+ }
+
+ /// Returns the number of elements in the span.
+ /// @note Returns a 32 bits integer because most Vulkan functions expect this type.
+ constexpr u32 size() const noexcept {
+ return static_cast<u32>(num);
+ }
+
+ /// Returns true when the span is empty.
+ constexpr bool empty() const noexcept {
+ return num == 0;
+ }
+
+ /// Returns a reference to the element in the passed index.
+ /// @pre: index < size()
+ constexpr const T& operator[](std::size_t index) const noexcept {
+ return ptr[index];
+ }
+
+ /// Returns an iterator to the beginning of the span.
+ constexpr const T* begin() const noexcept {
+ return ptr;
+ }
+
+ /// Returns an iterator to the end of the span.
+ constexpr const T* end() const noexcept {
+ return ptr + num;
+ }
+
+ /// Returns an iterator to the beginning of the span.
+ constexpr const T* cbegin() const noexcept {
+ return ptr;
+ }
+
+ /// Returns an iterator to the end of the span.
+ constexpr const T* cend() const noexcept {
+ return ptr + num;
+ }
+
+private:
+ const T* ptr = nullptr;
+ std::size_t num = 0;
+};
+
+/// Vulkan exception generated from a VkResult.
+class Exception final : public std::exception {
+public:
+ /// Construct the exception with a result.
+ /// @pre result != VK_SUCCESS
+ explicit Exception(VkResult result_) : result{result_} {}
+ virtual ~Exception() = default;
+
+ const char* what() const noexcept override;
+
+private:
+ VkResult result;
+};
+
+/// Converts a VkResult enum into a rodata string
+const char* ToString(VkResult) noexcept;
+
+/// Throws a Vulkan exception if result is not success.
+inline void Check(VkResult result) {
+ if (result != VK_SUCCESS) {
+ throw Exception(result);
+ }
+}
+
+/// Throws a Vulkan exception if result is an error.
+/// @return result
+inline VkResult Filter(VkResult result) {
+ if (result < 0) {
+ throw Exception(result);
+ }
+ return result;
+}
+
+/// Table holding Vulkan instance function pointers.
+struct InstanceDispatch {
+ PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{};
+
+ PFN_vkCreateInstance vkCreateInstance{};
+ PFN_vkDestroyInstance vkDestroyInstance{};
+ PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties{};
+ PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties{};
+
+ PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT{};
+ PFN_vkCreateDevice vkCreateDevice{};
+ PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT{};
+ PFN_vkDestroyDevice vkDestroyDevice{};
+ PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR{};
+ PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties{};
+ PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices{};
+ PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr{};
+ PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR{};
+ PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties{};
+ PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties{};
+ PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties{};
+ PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR{};
+ PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties{};
+ PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR{};
+ PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR{};
+ PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR{};
+ PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR{};
+ PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR{};
+ PFN_vkQueuePresentKHR vkQueuePresentKHR{};
+};
+
+/// Table holding Vulkan device function pointers.
+struct DeviceDispatch : InstanceDispatch {
+ PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR{};
+ PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers{};
+ PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets{};
+ PFN_vkAllocateMemory vkAllocateMemory{};
+ PFN_vkBeginCommandBuffer vkBeginCommandBuffer{};
+ PFN_vkBindBufferMemory vkBindBufferMemory{};
+ PFN_vkBindImageMemory vkBindImageMemory{};
+ PFN_vkCmdBeginQuery vkCmdBeginQuery{};
+ PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass{};
+ PFN_vkCmdBeginTransformFeedbackEXT vkCmdBeginTransformFeedbackEXT{};
+ PFN_vkCmdBeginDebugUtilsLabelEXT vkCmdBeginDebugUtilsLabelEXT{};
+ PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets{};
+ PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer{};
+ PFN_vkCmdBindPipeline vkCmdBindPipeline{};
+ PFN_vkCmdBindTransformFeedbackBuffersEXT vkCmdBindTransformFeedbackBuffersEXT{};
+ PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers{};
+ PFN_vkCmdBlitImage vkCmdBlitImage{};
+ PFN_vkCmdClearAttachments vkCmdClearAttachments{};
+ PFN_vkCmdCopyBuffer vkCmdCopyBuffer{};
+ PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage{};
+ PFN_vkCmdCopyImage vkCmdCopyImage{};
+ PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer{};
+ PFN_vkCmdDispatch vkCmdDispatch{};
+ PFN_vkCmdDraw vkCmdDraw{};
+ PFN_vkCmdDrawIndexed vkCmdDrawIndexed{};
+ PFN_vkCmdEndQuery vkCmdEndQuery{};
+ PFN_vkCmdEndRenderPass vkCmdEndRenderPass{};
+ PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT{};
+ PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{};
+ PFN_vkCmdFillBuffer vkCmdFillBuffer{};
+ PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier{};
+ PFN_vkCmdPushConstants vkCmdPushConstants{};
+ PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{};
+ PFN_vkCmdSetDepthBias vkCmdSetDepthBias{};
+ PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds{};
+ PFN_vkCmdSetEvent vkCmdSetEvent{};
+ PFN_vkCmdSetScissor vkCmdSetScissor{};
+ PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask{};
+ PFN_vkCmdSetStencilReference vkCmdSetStencilReference{};
+ PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask{};
+ PFN_vkCmdSetViewport vkCmdSetViewport{};
+ PFN_vkCmdWaitEvents vkCmdWaitEvents{};
+ PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT{};
+ PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{};
+ PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT{};
+ PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT{};
+ PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT{};
+ PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT{};
+ PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT{};
+ PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT{};
+ PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT{};
+ PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT{};
+ PFN_vkCmdResolveImage vkCmdResolveImage{};
+ PFN_vkCreateBuffer vkCreateBuffer{};
+ PFN_vkCreateBufferView vkCreateBufferView{};
+ PFN_vkCreateCommandPool vkCreateCommandPool{};
+ PFN_vkCreateComputePipelines vkCreateComputePipelines{};
+ PFN_vkCreateDescriptorPool vkCreateDescriptorPool{};
+ PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout{};
+ PFN_vkCreateDescriptorUpdateTemplateKHR vkCreateDescriptorUpdateTemplateKHR{};
+ PFN_vkCreateEvent vkCreateEvent{};
+ PFN_vkCreateFence vkCreateFence{};
+ PFN_vkCreateFramebuffer vkCreateFramebuffer{};
+ PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines{};
+ PFN_vkCreateImage vkCreateImage{};
+ PFN_vkCreateImageView vkCreateImageView{};
+ PFN_vkCreatePipelineLayout vkCreatePipelineLayout{};
+ PFN_vkCreateQueryPool vkCreateQueryPool{};
+ PFN_vkCreateRenderPass vkCreateRenderPass{};
+ PFN_vkCreateSampler vkCreateSampler{};
+ PFN_vkCreateSemaphore vkCreateSemaphore{};
+ PFN_vkCreateShaderModule vkCreateShaderModule{};
+ PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR{};
+ PFN_vkDestroyBuffer vkDestroyBuffer{};
+ PFN_vkDestroyBufferView vkDestroyBufferView{};
+ PFN_vkDestroyCommandPool vkDestroyCommandPool{};
+ PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool{};
+ PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout{};
+ PFN_vkDestroyDescriptorUpdateTemplateKHR vkDestroyDescriptorUpdateTemplateKHR{};
+ PFN_vkDestroyEvent vkDestroyEvent{};
+ PFN_vkDestroyFence vkDestroyFence{};
+ PFN_vkDestroyFramebuffer vkDestroyFramebuffer{};
+ PFN_vkDestroyImage vkDestroyImage{};
+ PFN_vkDestroyImageView vkDestroyImageView{};
+ PFN_vkDestroyPipeline vkDestroyPipeline{};
+ PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout{};
+ PFN_vkDestroyQueryPool vkDestroyQueryPool{};
+ PFN_vkDestroyRenderPass vkDestroyRenderPass{};
+ PFN_vkDestroySampler vkDestroySampler{};
+ PFN_vkDestroySemaphore vkDestroySemaphore{};
+ PFN_vkDestroyShaderModule vkDestroyShaderModule{};
+ PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR{};
+ PFN_vkDeviceWaitIdle vkDeviceWaitIdle{};
+ PFN_vkEndCommandBuffer vkEndCommandBuffer{};
+ PFN_vkFreeCommandBuffers vkFreeCommandBuffers{};
+ PFN_vkFreeDescriptorSets vkFreeDescriptorSets{};
+ PFN_vkFreeMemory vkFreeMemory{};
+ PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2{};
+ PFN_vkGetDeviceQueue vkGetDeviceQueue{};
+ PFN_vkGetEventStatus vkGetEventStatus{};
+ PFN_vkGetFenceStatus vkGetFenceStatus{};
+ PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements{};
+ PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR{};
+#ifdef _WIN32
+ PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR{};
+#endif
+ PFN_vkGetQueryPoolResults vkGetQueryPoolResults{};
+ PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR{};
+ PFN_vkMapMemory vkMapMemory{};
+ PFN_vkQueueSubmit vkQueueSubmit{};
+ PFN_vkResetFences vkResetFences{};
+ PFN_vkResetQueryPoolEXT vkResetQueryPoolEXT{};
+ PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{};
+ PFN_vkSetDebugUtilsObjectTagEXT vkSetDebugUtilsObjectTagEXT{};
+ PFN_vkUnmapMemory vkUnmapMemory{};
+ PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR{};
+ PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets{};
+ PFN_vkWaitForFences vkWaitForFences{};
+ PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR{};
+};
+
+/// Loads instance agnostic function pointers.
+/// @return True on success, false on error.
+bool Load(InstanceDispatch&) noexcept;
+
+/// Loads instance function pointers.
+/// @return True on success, false on error.
+bool Load(VkInstance, InstanceDispatch&) noexcept;
+
+void Destroy(VkInstance, const InstanceDispatch&) noexcept;
+void Destroy(VkDevice, const InstanceDispatch&) noexcept;
+
+void Destroy(VkDevice, VkBuffer, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkBufferView, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkCommandPool, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkDescriptorPool, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkDescriptorSetLayout, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkDescriptorUpdateTemplateKHR, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkDeviceMemory, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkEvent, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkFence, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkFramebuffer, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkImage, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkImageView, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkPipeline, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkPipelineLayout, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkQueryPool, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkRenderPass, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkSampler, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkSwapchainKHR, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkSemaphore, const DeviceDispatch&) noexcept;
+void Destroy(VkDevice, VkShaderModule, const DeviceDispatch&) noexcept;
+void Destroy(VkInstance, VkDebugUtilsMessengerEXT, const InstanceDispatch&) noexcept;
+void Destroy(VkInstance, VkSurfaceKHR, const InstanceDispatch&) noexcept;
+
+VkResult Free(VkDevice, VkDescriptorPool, Span<VkDescriptorSet>, const DeviceDispatch&) noexcept;
+VkResult Free(VkDevice, VkCommandPool, Span<VkCommandBuffer>, const DeviceDispatch&) noexcept;
+
+template <typename Type, typename OwnerType, typename Dispatch>
+class Handle;
+
+/// Handle with an owning type.
+/// Analogue to std::unique_ptr.
+template <typename Type, typename OwnerType, typename Dispatch>
+class Handle {
+public:
+ /// Construct a handle and hold it's ownership.
+ explicit Handle(Type handle_, OwnerType owner_, const Dispatch& dld_) noexcept
+ : handle{handle_}, owner{owner_}, dld{&dld_} {}
+
+ /// Construct an empty handle.
+ Handle() = default;
+
+ /// Construct an empty handle.
+ Handle(std::nullptr_t) {}
+
+ /// Copying Vulkan objects is not supported and will never be.
+ Handle(const Handle&) = delete;
+ Handle& operator=(const Handle&) = delete;
+
+ /// Construct a handle transfering the ownership from another handle.
+ Handle(Handle&& rhs) noexcept
+ : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {}
+
+ /// Assign the current handle transfering the ownership from another handle.
+ /// Destroys any previously held object.
+ Handle& operator=(Handle&& rhs) noexcept {
+ Release();
+ handle = std::exchange(rhs.handle, nullptr);
+ owner = rhs.owner;
+ dld = rhs.dld;
+ return *this;
+ }
+
+ /// Destroys the current handle if it existed.
+ ~Handle() noexcept {
+ Release();
+ }
+
+ /// Destroys any held object.
+ void reset() noexcept {
+ Release();
+ handle = nullptr;
+ }
+
+ /// Returns the address of the held object.
+ /// Intended for Vulkan structures that expect a pointer to an array.
+ const Type* address() const noexcept {
+ return std::addressof(handle);
+ }
+
+ /// Returns the held Vulkan handle.
+ Type operator*() const noexcept {
+ return handle;
+ }
+
+ /// Returns true when there's a held object.
+ explicit operator bool() const noexcept {
+ return handle != nullptr;
+ }
+
+protected:
+ Type handle = nullptr;
+ OwnerType owner = nullptr;
+ const Dispatch* dld = nullptr;
+
+private:
+ /// Destroys the held object if it exists.
+ void Release() noexcept {
+ if (handle) {
+ Destroy(owner, handle, *dld);
+ }
+ }
+};
+
+/// Dummy type used to specify a handle has no owner.
+struct NoOwner {};
+
+/// Handle without an owning type.
+/// Analogue to std::unique_ptr
+template <typename Type, typename Dispatch>
+class Handle<Type, NoOwner, Dispatch> {
+public:
+ /// Construct a handle and hold it's ownership.
+ explicit Handle(Type handle_, const Dispatch& dld_) noexcept : handle{handle_}, dld{&dld_} {}
+
+ /// Construct an empty handle.
+ Handle() noexcept = default;
+
+ /// Copying Vulkan objects is not supported and will never be.
+ Handle(const Handle&) = delete;
+ Handle& operator=(const Handle&) = delete;
+
+ /// Construct a handle transfering ownership from another handle.
+ Handle(Handle&& rhs) noexcept : handle{std::exchange(rhs.handle, nullptr)}, dld{rhs.dld} {}
+
+ /// Assign the current handle transfering the ownership from another handle.
+ /// Destroys any previously held object.
+ Handle& operator=(Handle&& rhs) noexcept {
+ Release();
+ handle = std::exchange(rhs.handle, nullptr);
+ dld = rhs.dld;
+ return *this;
+ }
+
+ /// Destroys the current handle if it existed.
+ ~Handle() noexcept {
+ Release();
+ }
+
+ /// Destroys any held object.
+ void reset() noexcept {
+ Release();
+ handle = nullptr;
+ }
+
+ /// Returns the address of the held object.
+ /// Intended for Vulkan structures that expect a pointer to an array.
+ const Type* address() const noexcept {
+ return std::addressof(handle);
+ }
+
+ /// Returns the held Vulkan handle.
+ Type operator*() const noexcept {
+ return handle;
+ }
+
+ /// Returns true when there's a held object.
+ operator bool() const noexcept {
+ return handle != nullptr;
+ }
+
+protected:
+ Type handle = nullptr;
+ const Dispatch* dld = nullptr;
+
+private:
+ /// Destroys the held object if it exists.
+ void Release() noexcept {
+ if (handle) {
+ Destroy(handle, *dld);
+ }
+ }
+};
+
+/// Array of a pool allocation.
+/// Analogue to std::vector
+template <typename AllocationType, typename PoolType>
+class PoolAllocations {
+public:
+ /// Construct an empty allocation.
+ PoolAllocations() = default;
+
+ /// Construct an allocation. Errors are reported through IsOutOfPoolMemory().
+ explicit PoolAllocations(std::unique_ptr<AllocationType[]> allocations_, std::size_t num_,
+ VkDevice device_, PoolType pool_, const DeviceDispatch& dld_) noexcept
+ : allocations{std::move(allocations_)}, num{num_}, device{device_}, pool{pool_},
+ dld{&dld_} {}
+
+ /// Copying Vulkan allocations is not supported and will never be.
+ PoolAllocations(const PoolAllocations&) = delete;
+ PoolAllocations& operator=(const PoolAllocations&) = delete;
+
+ /// Construct an allocation transfering ownership from another allocation.
+ PoolAllocations(PoolAllocations&& rhs) noexcept
+ : allocations{std::move(rhs.allocations)}, num{rhs.num}, device{rhs.device}, pool{rhs.pool},
+ dld{rhs.dld} {}
+
+ /// Assign an allocation transfering ownership from another allocation.
+ /// Releases any previously held allocation.
+ PoolAllocations& operator=(PoolAllocations&& rhs) noexcept {
+ Release();
+ allocations = std::move(rhs.allocations);
+ num = rhs.num;
+ device = rhs.device;
+ pool = rhs.pool;
+ dld = rhs.dld;
+ return *this;
+ }
+
+ /// Destroys any held allocation.
+ ~PoolAllocations() {
+ Release();
+ }
+
+ /// Returns the number of allocations.
+ std::size_t size() const noexcept {
+ return num;
+ }
+
+ /// Returns a pointer to the array of allocations.
+ AllocationType const* data() const noexcept {
+ return allocations.get();
+ }
+
+ /// Returns the allocation in the specified index.
+ /// @pre index < size()
+ AllocationType operator[](std::size_t index) const noexcept {
+ return allocations[index];
+ }
+
+ /// True when a pool fails to construct.
+ bool IsOutOfPoolMemory() const noexcept {
+ return !device;
+ }
+
+private:
+ /// Destroys the held allocations if they exist.
+ void Release() noexcept {
+ if (!allocations) {
+ return;
+ }
+ const Span<AllocationType> span(allocations.get(), num);
+ const VkResult result = Free(device, pool, span, *dld);
+ // There's no way to report errors from a destructor.
+ if (result != VK_SUCCESS) {
+ std::terminate();
+ }
+ }
+
+ std::unique_ptr<AllocationType[]> allocations;
+ std::size_t num = 0;
+ VkDevice device = nullptr;
+ PoolType pool = nullptr;
+ const DeviceDispatch* dld = nullptr;
+};
+
+using DebugUtilsMessenger = Handle<VkDebugUtilsMessengerEXT, VkInstance, InstanceDispatch>;
+using DescriptorSetLayout = Handle<VkDescriptorSetLayout, VkDevice, DeviceDispatch>;
+using DescriptorUpdateTemplateKHR = Handle<VkDescriptorUpdateTemplateKHR, VkDevice, DeviceDispatch>;
+using Pipeline = Handle<VkPipeline, VkDevice, DeviceDispatch>;
+using PipelineLayout = Handle<VkPipelineLayout, VkDevice, DeviceDispatch>;
+using QueryPool = Handle<VkQueryPool, VkDevice, DeviceDispatch>;
+using RenderPass = Handle<VkRenderPass, VkDevice, DeviceDispatch>;
+using Sampler = Handle<VkSampler, VkDevice, DeviceDispatch>;
+using SurfaceKHR = Handle<VkSurfaceKHR, VkInstance, InstanceDispatch>;
+
+using DescriptorSets = PoolAllocations<VkDescriptorSet, VkDescriptorPool>;
+using CommandBuffers = PoolAllocations<VkCommandBuffer, VkCommandPool>;
+
+/// Vulkan instance owning handle.
+class Instance : public Handle<VkInstance, NoOwner, InstanceDispatch> {
+ using Handle<VkInstance, NoOwner, InstanceDispatch>::Handle;
+
+public:
+ /// Creates a Vulkan instance.
+ /// @throw Exception on initialization error.
+ static Instance Create(u32 version, Span<const char*> layers, Span<const char*> extensions,
+ InstanceDispatch& dispatch);
+
+ /// Enumerates physical devices.
+ /// @return Physical devices and an empty handle on failure.
+ /// @throw Exception on Vulkan error.
+ std::vector<VkPhysicalDevice> EnumeratePhysicalDevices() const;
+
+ /// Creates a debug callback messenger.
+ /// @throw Exception on creation failure.
+ DebugUtilsMessenger CreateDebugUtilsMessenger(
+ const VkDebugUtilsMessengerCreateInfoEXT& create_info) const;
+
+ /// Returns dispatch table.
+ const InstanceDispatch& Dispatch() const noexcept {
+ return *dld;
+ }
+};
+
+class Queue {
+public:
+ /// Construct an empty queue handle.
+ constexpr Queue() noexcept = default;
+
+ /// Construct a queue handle.
+ constexpr Queue(VkQueue queue_, const DeviceDispatch& dld_) noexcept
+ : queue{queue_}, dld{&dld_} {}
+
+ VkResult Submit(Span<VkSubmitInfo> submit_infos,
+ VkFence fence = VK_NULL_HANDLE) const noexcept {
+ return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence);
+ }
+
+ VkResult Present(const VkPresentInfoKHR& present_info) const noexcept {
+ return dld->vkQueuePresentKHR(queue, &present_info);
+ }
+
+private:
+ VkQueue queue = nullptr;
+ const DeviceDispatch* dld = nullptr;
+};
+
+class Buffer : public Handle<VkBuffer, VkDevice, DeviceDispatch> {
+ using Handle<VkBuffer, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Attaches a memory allocation.
+ void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const;
+
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+};
+
+class BufferView : public Handle<VkBufferView, VkDevice, DeviceDispatch> {
+ using Handle<VkBufferView, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+};
+
+class Image : public Handle<VkImage, VkDevice, DeviceDispatch> {
+ using Handle<VkImage, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Attaches a memory allocation.
+ void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const;
+
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+};
+
+class ImageView : public Handle<VkImageView, VkDevice, DeviceDispatch> {
+ using Handle<VkImageView, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+};
+
+class DeviceMemory : public Handle<VkDeviceMemory, VkDevice, DeviceDispatch> {
+ using Handle<VkDeviceMemory, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ int GetMemoryFdKHR() const;
+
+#ifdef _WIN32
+ HANDLE GetMemoryWin32HandleKHR() const;
+#endif
+
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+
+ u8* Map(VkDeviceSize offset, VkDeviceSize size) const {
+ void* data;
+ Check(dld->vkMapMemory(owner, handle, offset, size, 0, &data));
+ return static_cast<u8*>(data);
+ }
+
+ void Unmap() const noexcept {
+ dld->vkUnmapMemory(owner, handle);
+ }
+};
+
+class Fence : public Handle<VkFence, VkDevice, DeviceDispatch> {
+ using Handle<VkFence, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+
+ VkResult Wait(u64 timeout = std::numeric_limits<u64>::max()) const noexcept {
+ return dld->vkWaitForFences(owner, 1, &handle, true, timeout);
+ }
+
+ VkResult GetStatus() const noexcept {
+ return dld->vkGetFenceStatus(owner, handle);
+ }
+
+ void Reset() const {
+ Check(dld->vkResetFences(owner, 1, &handle));
+ }
+};
+
+class Framebuffer : public Handle<VkFramebuffer, VkDevice, DeviceDispatch> {
+ using Handle<VkFramebuffer, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+};
+
+class DescriptorPool : public Handle<VkDescriptorPool, VkDevice, DeviceDispatch> {
+ using Handle<VkDescriptorPool, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ DescriptorSets Allocate(const VkDescriptorSetAllocateInfo& ai) const;
+
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+};
+
+class CommandPool : public Handle<VkCommandPool, VkDevice, DeviceDispatch> {
+ using Handle<VkCommandPool, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ CommandBuffers Allocate(std::size_t num_buffers,
+ VkCommandBufferLevel level = VK_COMMAND_BUFFER_LEVEL_PRIMARY) const;
+
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+};
+
+class SwapchainKHR : public Handle<VkSwapchainKHR, VkDevice, DeviceDispatch> {
+ using Handle<VkSwapchainKHR, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ std::vector<VkImage> GetImages() const;
+};
+
+class Event : public Handle<VkEvent, VkDevice, DeviceDispatch> {
+ using Handle<VkEvent, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+
+ VkResult GetStatus() const noexcept {
+ return dld->vkGetEventStatus(owner, handle);
+ }
+};
+
+class ShaderModule : public Handle<VkShaderModule, VkDevice, DeviceDispatch> {
+ using Handle<VkShaderModule, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+};
+
+class Semaphore : public Handle<VkSemaphore, VkDevice, DeviceDispatch> {
+ using Handle<VkSemaphore, VkDevice, DeviceDispatch>::Handle;
+
+public:
+ /// Set object name.
+ void SetObjectNameEXT(const char* name) const;
+
+ [[nodiscard]] u64 GetCounter() const {
+ u64 value;
+ Check(dld->vkGetSemaphoreCounterValueKHR(owner, handle, &value));
+ return value;
+ }
+
+ /**
+ * Waits for a timeline semaphore on the host.
+ *
+ * @param value Value to wait
+ * @param timeout Time in nanoseconds to timeout
+ * @return True on successful wait, false on timeout
+ */
+ bool Wait(u64 value, u64 timeout = std::numeric_limits<u64>::max()) const {
+ const VkSemaphoreWaitInfoKHR wait_info{
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR,
+ .pNext = nullptr,
+ .flags = 0,
+ .semaphoreCount = 1,
+ .pSemaphores = &handle,
+ .pValues = &value,
+ };
+ const VkResult result = dld->vkWaitSemaphoresKHR(owner, &wait_info, timeout);
+ switch (result) {
+ case VK_SUCCESS:
+ return true;
+ case VK_TIMEOUT:
+ return false;
+ default:
+ throw Exception(result);
+ }
+ }
+};
+
+class Device : public Handle<VkDevice, NoOwner, DeviceDispatch> {
+ using Handle<VkDevice, NoOwner, DeviceDispatch>::Handle;
+
+public:
+ static Device Create(VkPhysicalDevice physical_device, Span<VkDeviceQueueCreateInfo> queues_ci,
+ Span<const char*> enabled_extensions, const void* next,
+ DeviceDispatch& dispatch);
+
+ Queue GetQueue(u32 family_index) const noexcept;
+
+ Buffer CreateBuffer(const VkBufferCreateInfo& ci) const;
+
+ BufferView CreateBufferView(const VkBufferViewCreateInfo& ci) const;
+
+ Image CreateImage(const VkImageCreateInfo& ci) const;
+
+ ImageView CreateImageView(const VkImageViewCreateInfo& ci) const;
+
+ Semaphore CreateSemaphore() const;
+
+ Semaphore CreateSemaphore(const VkSemaphoreCreateInfo& ci) const;
+
+ Fence CreateFence(const VkFenceCreateInfo& ci) const;
+
+ DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const;
+
+ RenderPass CreateRenderPass(const VkRenderPassCreateInfo& ci) const;
+
+ DescriptorSetLayout CreateDescriptorSetLayout(const VkDescriptorSetLayoutCreateInfo& ci) const;
+
+ PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const;
+
+ Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci) const;
+
+ Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci) const;
+
+ Sampler CreateSampler(const VkSamplerCreateInfo& ci) const;
+
+ Framebuffer CreateFramebuffer(const VkFramebufferCreateInfo& ci) const;
+
+ CommandPool CreateCommandPool(const VkCommandPoolCreateInfo& ci) const;
+
+ DescriptorUpdateTemplateKHR CreateDescriptorUpdateTemplateKHR(
+ const VkDescriptorUpdateTemplateCreateInfoKHR& ci) const;
+
+ QueryPool CreateQueryPool(const VkQueryPoolCreateInfo& ci) const;
+
+ ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const;
+
+ Event CreateEvent() const;
+
+ SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const;
+
+ DeviceMemory TryAllocateMemory(const VkMemoryAllocateInfo& ai) const noexcept;
+
+ DeviceMemory AllocateMemory(const VkMemoryAllocateInfo& ai) const;
+
+ VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer,
+ void* pnext = nullptr) const noexcept;
+
+ VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept;
+
+ void UpdateDescriptorSets(Span<VkWriteDescriptorSet> writes,
+ Span<VkCopyDescriptorSet> copies) const noexcept;
+
+ void UpdateDescriptorSet(VkDescriptorSet set, VkDescriptorUpdateTemplateKHR update_template,
+ const void* data) const noexcept {
+ dld->vkUpdateDescriptorSetWithTemplateKHR(handle, set, update_template, data);
+ }
+
+ VkResult AcquireNextImageKHR(VkSwapchainKHR swapchain, u64 timeout, VkSemaphore semaphore,
+ VkFence fence, u32* image_index) const noexcept {
+ return dld->vkAcquireNextImageKHR(handle, swapchain, timeout, semaphore, fence,
+ image_index);
+ }
+
+ VkResult WaitIdle() const noexcept {
+ return dld->vkDeviceWaitIdle(handle);
+ }
+
+ void ResetQueryPoolEXT(VkQueryPool query_pool, u32 first, u32 count) const noexcept {
+ dld->vkResetQueryPoolEXT(handle, query_pool, first, count);
+ }
+
+ VkResult GetQueryResults(VkQueryPool query_pool, u32 first, u32 count, std::size_t data_size,
+ void* data, VkDeviceSize stride,
+ VkQueryResultFlags flags) const noexcept {
+ return dld->vkGetQueryPoolResults(handle, query_pool, first, count, data_size, data, stride,
+ flags);
+ }
+};
+
+class PhysicalDevice {
+public:
+ constexpr PhysicalDevice() noexcept = default;
+
+ constexpr PhysicalDevice(VkPhysicalDevice physical_device_,
+ const InstanceDispatch& dld_) noexcept
+ : physical_device{physical_device_}, dld{&dld_} {}
+
+ constexpr operator VkPhysicalDevice() const noexcept {
+ return physical_device;
+ }
+
+ VkPhysicalDeviceProperties GetProperties() const noexcept;
+
+ void GetProperties2KHR(VkPhysicalDeviceProperties2KHR&) const noexcept;
+
+ VkPhysicalDeviceFeatures GetFeatures() const noexcept;
+
+ void GetFeatures2KHR(VkPhysicalDeviceFeatures2KHR&) const noexcept;
+
+ VkFormatProperties GetFormatProperties(VkFormat) const noexcept;
+
+ std::vector<VkExtensionProperties> EnumerateDeviceExtensionProperties() const;
+
+ std::vector<VkQueueFamilyProperties> GetQueueFamilyProperties() const;
+
+ bool GetSurfaceSupportKHR(u32 queue_family_index, VkSurfaceKHR) const;
+
+ VkSurfaceCapabilitiesKHR GetSurfaceCapabilitiesKHR(VkSurfaceKHR) const;
+
+ std::vector<VkSurfaceFormatKHR> GetSurfaceFormatsKHR(VkSurfaceKHR) const;
+
+ std::vector<VkPresentModeKHR> GetSurfacePresentModesKHR(VkSurfaceKHR) const;
+
+ VkPhysicalDeviceMemoryProperties GetMemoryProperties() const noexcept;
+
+private:
+ VkPhysicalDevice physical_device = nullptr;
+ const InstanceDispatch* dld = nullptr;
+};
+
+class CommandBuffer {
+public:
+ CommandBuffer() noexcept = default;
+
+ explicit CommandBuffer(VkCommandBuffer handle_, const DeviceDispatch& dld_) noexcept
+ : handle{handle_}, dld{&dld_} {}
+
+ const VkCommandBuffer* address() const noexcept {
+ return &handle;
+ }
+
+ void Begin(const VkCommandBufferBeginInfo& begin_info) const {
+ Check(dld->vkBeginCommandBuffer(handle, &begin_info));
+ }
+
+ void End() const {
+ Check(dld->vkEndCommandBuffer(handle));
+ }
+
+ void BeginRenderPass(const VkRenderPassBeginInfo& renderpass_bi,
+ VkSubpassContents contents) const noexcept {
+ dld->vkCmdBeginRenderPass(handle, &renderpass_bi, contents);
+ }
+
+ void EndRenderPass() const noexcept {
+ dld->vkCmdEndRenderPass(handle);
+ }
+
+ void BeginQuery(VkQueryPool query_pool, u32 query, VkQueryControlFlags flags) const noexcept {
+ dld->vkCmdBeginQuery(handle, query_pool, query, flags);
+ }
+
+ void EndQuery(VkQueryPool query_pool, u32 query) const noexcept {
+ dld->vkCmdEndQuery(handle, query_pool, query);
+ }
+
+ void BindDescriptorSets(VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 first,
+ Span<VkDescriptorSet> sets, Span<u32> dynamic_offsets) const noexcept {
+ dld->vkCmdBindDescriptorSets(handle, bind_point, layout, first, sets.size(), sets.data(),
+ dynamic_offsets.size(), dynamic_offsets.data());
+ }
+
+ void BindPipeline(VkPipelineBindPoint bind_point, VkPipeline pipeline) const noexcept {
+ dld->vkCmdBindPipeline(handle, bind_point, pipeline);
+ }
+
+ void BindIndexBuffer(VkBuffer buffer, VkDeviceSize offset,
+ VkIndexType index_type) const noexcept {
+ dld->vkCmdBindIndexBuffer(handle, buffer, offset, index_type);
+ }
+
+ void BindVertexBuffers(u32 first, u32 count, const VkBuffer* buffers,
+ const VkDeviceSize* offsets) const noexcept {
+ dld->vkCmdBindVertexBuffers(handle, first, count, buffers, offsets);
+ }
+
+ void BindVertexBuffer(u32 binding, VkBuffer buffer, VkDeviceSize offset) const noexcept {
+ BindVertexBuffers(binding, 1, &buffer, &offset);
+ }
+
+ void Draw(u32 vertex_count, u32 instance_count, u32 first_vertex,
+ u32 first_instance) const noexcept {
+ dld->vkCmdDraw(handle, vertex_count, instance_count, first_vertex, first_instance);
+ }
+
+ void DrawIndexed(u32 index_count, u32 instance_count, u32 first_index, u32 vertex_offset,
+ u32 first_instance) const noexcept {
+ dld->vkCmdDrawIndexed(handle, index_count, instance_count, first_index, vertex_offset,
+ first_instance);
+ }
+
+ void ClearAttachments(Span<VkClearAttachment> attachments,
+ Span<VkClearRect> rects) const noexcept {
+ dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
+ rects.data());
+ }
+
+ void BlitImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image,
+ VkImageLayout dst_layout, Span<VkImageBlit> regions,
+ VkFilter filter) const noexcept {
+ dld->vkCmdBlitImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(),
+ regions.data(), filter);
+ }
+
+ void ResolveImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image,
+ VkImageLayout dst_layout, Span<VkImageResolve> regions) {
+ dld->vkCmdResolveImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(),
+ regions.data());
+ }
+
+ void Dispatch(u32 x, u32 y, u32 z) const noexcept {
+ dld->vkCmdDispatch(handle, x, y, z);
+ }
+
+ void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
+ VkDependencyFlags dependency_flags, Span<VkMemoryBarrier> memory_barriers,
+ Span<VkBufferMemoryBarrier> buffer_barriers,
+ Span<VkImageMemoryBarrier> image_barriers) const noexcept {
+ dld->vkCmdPipelineBarrier(handle, src_stage_mask, dst_stage_mask, dependency_flags,
+ memory_barriers.size(), memory_barriers.data(),
+ buffer_barriers.size(), buffer_barriers.data(),
+ image_barriers.size(), image_barriers.data());
+ }
+
+ void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
+ VkDependencyFlags dependency_flags = 0) const noexcept {
+ PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, {}, {});
+ }
+
+ void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
+ VkDependencyFlags dependency_flags,
+ const VkMemoryBarrier& memory_barrier) const noexcept {
+ PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, memory_barrier, {}, {});
+ }
+
+ void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
+ VkDependencyFlags dependency_flags,
+ const VkBufferMemoryBarrier& buffer_barrier) const noexcept {
+ PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, buffer_barrier, {});
+ }
+
+ void PipelineBarrier(VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
+ VkDependencyFlags dependency_flags,
+ const VkImageMemoryBarrier& image_barrier) const noexcept {
+ PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, {}, image_barrier);
+ }
+
+ void CopyBufferToImage(VkBuffer src_buffer, VkImage dst_image, VkImageLayout dst_image_layout,
+ Span<VkBufferImageCopy> regions) const noexcept {
+ dld->vkCmdCopyBufferToImage(handle, src_buffer, dst_image, dst_image_layout, regions.size(),
+ regions.data());
+ }
+
+ void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
+ Span<VkBufferCopy> regions) const noexcept {
+ dld->vkCmdCopyBuffer(handle, src_buffer, dst_buffer, regions.size(), regions.data());
+ }
+
+ void CopyImage(VkImage src_image, VkImageLayout src_layout, VkImage dst_image,
+ VkImageLayout dst_layout, Span<VkImageCopy> regions) const noexcept {
+ dld->vkCmdCopyImage(handle, src_image, src_layout, dst_image, dst_layout, regions.size(),
+ regions.data());
+ }
+
+ void CopyImageToBuffer(VkImage src_image, VkImageLayout src_layout, VkBuffer dst_buffer,
+ Span<VkBufferImageCopy> regions) const noexcept {
+ dld->vkCmdCopyImageToBuffer(handle, src_image, src_layout, dst_buffer, regions.size(),
+ regions.data());
+ }
+
+ void FillBuffer(VkBuffer dst_buffer, VkDeviceSize dst_offset, VkDeviceSize size,
+ u32 data) const noexcept {
+ dld->vkCmdFillBuffer(handle, dst_buffer, dst_offset, size, data);
+ }
+
+ void PushConstants(VkPipelineLayout layout, VkShaderStageFlags flags, u32 offset, u32 size,
+ const void* values) const noexcept {
+ dld->vkCmdPushConstants(handle, layout, flags, offset, size, values);
+ }
+
+ template <typename T>
+ void PushConstants(VkPipelineLayout layout, VkShaderStageFlags flags,
+ const T& data) const noexcept {
+ static_assert(std::is_trivially_copyable_v<T>, "<data> is not trivially copyable");
+ dld->vkCmdPushConstants(handle, layout, flags, 0, static_cast<u32>(sizeof(T)), &data);
+ }
+
+ void SetViewport(u32 first, Span<VkViewport> viewports) const noexcept {
+ dld->vkCmdSetViewport(handle, first, viewports.size(), viewports.data());
+ }
+
+ void SetScissor(u32 first, Span<VkRect2D> scissors) const noexcept {
+ dld->vkCmdSetScissor(handle, first, scissors.size(), scissors.data());
+ }
+
+ void SetBlendConstants(const float blend_constants[4]) const noexcept {
+ dld->vkCmdSetBlendConstants(handle, blend_constants);
+ }
+
+ void SetStencilCompareMask(VkStencilFaceFlags face_mask, u32 compare_mask) const noexcept {
+ dld->vkCmdSetStencilCompareMask(handle, face_mask, compare_mask);
+ }
+
+ void SetStencilReference(VkStencilFaceFlags face_mask, u32 reference) const noexcept {
+ dld->vkCmdSetStencilReference(handle, face_mask, reference);
+ }
+
+ void SetStencilWriteMask(VkStencilFaceFlags face_mask, u32 write_mask) const noexcept {
+ dld->vkCmdSetStencilWriteMask(handle, face_mask, write_mask);
+ }
+
+ void SetDepthBias(float constant_factor, float clamp, float slope_factor) const noexcept {
+ dld->vkCmdSetDepthBias(handle, constant_factor, clamp, slope_factor);
+ }
+
+ void SetDepthBounds(float min_depth_bounds, float max_depth_bounds) const noexcept {
+ dld->vkCmdSetDepthBounds(handle, min_depth_bounds, max_depth_bounds);
+ }
+
+ void SetEvent(VkEvent event, VkPipelineStageFlags stage_flags) const noexcept {
+ dld->vkCmdSetEvent(handle, event, stage_flags);
+ }
+
+ void WaitEvents(Span<VkEvent> events, VkPipelineStageFlags src_stage_mask,
+ VkPipelineStageFlags dst_stage_mask, Span<VkMemoryBarrier> memory_barriers,
+ Span<VkBufferMemoryBarrier> buffer_barriers,
+ Span<VkImageMemoryBarrier> image_barriers) const noexcept {
+ dld->vkCmdWaitEvents(handle, events.size(), events.data(), src_stage_mask, dst_stage_mask,
+ memory_barriers.size(), memory_barriers.data(), buffer_barriers.size(),
+ buffer_barriers.data(), image_barriers.size(), image_barriers.data());
+ }
+
+ void BindVertexBuffers2EXT(u32 first_binding, u32 binding_count, const VkBuffer* buffers,
+ const VkDeviceSize* offsets, const VkDeviceSize* sizes,
+ const VkDeviceSize* strides) const noexcept {
+ dld->vkCmdBindVertexBuffers2EXT(handle, first_binding, binding_count, buffers, offsets,
+ sizes, strides);
+ }
+
+ void SetCullModeEXT(VkCullModeFlags cull_mode) const noexcept {
+ dld->vkCmdSetCullModeEXT(handle, cull_mode);
+ }
+
+ void SetDepthBoundsTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthBoundsTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetDepthCompareOpEXT(VkCompareOp compare_op) const noexcept {
+ dld->vkCmdSetDepthCompareOpEXT(handle, compare_op);
+ }
+
+ void SetDepthTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetDepthWriteEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetDepthWriteEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void SetFrontFaceEXT(VkFrontFace front_face) const noexcept {
+ dld->vkCmdSetFrontFaceEXT(handle, front_face);
+ }
+
+ void SetPrimitiveTopologyEXT(VkPrimitiveTopology primitive_topology) const noexcept {
+ dld->vkCmdSetPrimitiveTopologyEXT(handle, primitive_topology);
+ }
+
+ void SetStencilOpEXT(VkStencilFaceFlags face_mask, VkStencilOp fail_op, VkStencilOp pass_op,
+ VkStencilOp depth_fail_op, VkCompareOp compare_op) const noexcept {
+ dld->vkCmdSetStencilOpEXT(handle, face_mask, fail_op, pass_op, depth_fail_op, compare_op);
+ }
+
+ void SetStencilTestEnableEXT(bool enable) const noexcept {
+ dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
+ }
+
+ void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers,
+ const VkDeviceSize* offsets,
+ const VkDeviceSize* sizes) const noexcept {
+ dld->vkCmdBindTransformFeedbackBuffersEXT(handle, first, count, buffers, offsets, sizes);
+ }
+
+ void BeginTransformFeedbackEXT(u32 first_counter_buffer, u32 counter_buffers_count,
+ const VkBuffer* counter_buffers,
+ const VkDeviceSize* counter_buffer_offsets) const noexcept {
+ dld->vkCmdBeginTransformFeedbackEXT(handle, first_counter_buffer, counter_buffers_count,
+ counter_buffers, counter_buffer_offsets);
+ }
+
+ void EndTransformFeedbackEXT(u32 first_counter_buffer, u32 counter_buffers_count,
+ const VkBuffer* counter_buffers,
+ const VkDeviceSize* counter_buffer_offsets) const noexcept {
+ dld->vkCmdEndTransformFeedbackEXT(handle, first_counter_buffer, counter_buffers_count,
+ counter_buffers, counter_buffer_offsets);
+ }
+
+ void BeginDebugUtilsLabelEXT(const char* label, std::span<float, 4> color) const noexcept {
+ const VkDebugUtilsLabelEXT label_info{
+ .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
+ .pNext = nullptr,
+ .pLabelName = label,
+ .color{color[0], color[1], color[2], color[3]},
+ };
+ dld->vkCmdBeginDebugUtilsLabelEXT(handle, &label_info);
+ }
+
+ void EndDebugUtilsLabelEXT() const noexcept {
+ dld->vkCmdEndDebugUtilsLabelEXT(handle);
+ }
+
+private:
+ VkCommandBuffer handle;
+ const DeviceDispatch* dld;
+};
+
+u32 AvailableVersion(const InstanceDispatch& dld) noexcept;
+
+std::optional<std::vector<VkExtensionProperties>> EnumerateInstanceExtensionProperties(
+ const InstanceDispatch& dld);
+
+std::optional<std::vector<VkLayerProperties>> EnumerateInstanceLayerProperties(
+ const InstanceDispatch& dld);
+
+} // namespace Vulkan::vk
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
index 7e484b906..ae85a72ea 100644
--- a/src/web_service/CMakeLists.txt
+++ b/src/web_service/CMakeLists.txt
@@ -9,4 +9,4 @@ add_library(web_service STATIC
)
create_target_directory_groups(web_service)
-target_link_libraries(web_service PRIVATE common nlohmann_json::nlohmann_json httplib lurlparser)
+target_link_libraries(web_service PRIVATE common nlohmann_json::nlohmann_json httplib)
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 74e287045..67183e64c 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -7,7 +7,6 @@
#include <mutex>
#include <string>
-#include <LUrlParser.h>
#include <fmt/format.h>
#include <httplib.h>
@@ -19,9 +18,6 @@ namespace WebService {
constexpr std::array<const char, 1> API_VERSION{'1'};
-constexpr int HTTP_PORT = 80;
-constexpr int HTTPS_PORT = 443;
-
constexpr std::size_t TIMEOUT_SECONDS = 30;
struct Client::Impl {
@@ -67,28 +63,17 @@ struct Client::Impl {
const std::string& jwt = "", const std::string& username = "",
const std::string& token = "") {
if (cli == nullptr) {
- auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
- int port;
- if (parsedUrl.m_Scheme == "http") {
- if (!parsedUrl.GetPort(&port)) {
- port = HTTP_PORT;
- }
- cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port);
- } else if (parsedUrl.m_Scheme == "https") {
- if (!parsedUrl.GetPort(&port)) {
- port = HTTPS_PORT;
- }
- cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port);
- } else {
- LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
- return WebResult{WebResult::Code::InvalidURL, "Bad URL scheme", ""};
- }
+ cli = std::make_unique<httplib::Client>(host.c_str());
}
- if (cli == nullptr) {
- LOG_ERROR(WebService, "Invalid URL {}", host + path);
- return WebResult{WebResult::Code::InvalidURL, "Invalid URL", ""};
+
+ if (!cli->is_valid()) {
+ LOG_ERROR(WebService, "Client is invalid, skipping request!");
+ return {};
}
- cli->set_timeout_sec(TIMEOUT_SECONDS);
+
+ cli->set_connection_timeout(TIMEOUT_SECONDS);
+ cli->set_read_timeout(TIMEOUT_SECONDS);
+ cli->set_write_timeout(TIMEOUT_SECONDS);
httplib::Headers params;
if (!jwt.empty()) {
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 6987e85e1..b025ced1c 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -9,6 +9,9 @@ add_executable(yuzu
about_dialog.cpp
about_dialog.h
aboutdialog.ui
+ applets/controller.cpp
+ applets/controller.h
+ applets/controller.ui
applets/error.cpp
applets/error.h
applets/profile_select.cpp
@@ -62,12 +65,20 @@ add_executable(yuzu
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input.ui
- configuration/configure_input_player.cpp
- configuration/configure_input_player.h
- configuration/configure_input_player.ui
configuration/configure_input_advanced.cpp
configuration/configure_input_advanced.h
configuration/configure_input_advanced.ui
+ configuration/configure_input_player.cpp
+ configuration/configure_input_player.h
+ configuration/configure_input_player.ui
+ configuration/configure_input_player_widget.cpp
+ configuration/configure_input_player_widget.h
+ configuration/configure_input_profile_dialog.cpp
+ configuration/configure_input_profile_dialog.h
+ configuration/configure_input_profile_dialog.ui
+ configuration/configure_motion_touch.cpp
+ configuration/configure_motion_touch.h
+ configuration/configure_motion_touch.ui
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
configuration/configure_mouse_advanced.ui
@@ -86,17 +97,28 @@ add_executable(yuzu
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_system.ui
+ configuration/configure_touch_from_button.cpp
+ configuration/configure_touch_from_button.h
+ configuration/configure_touch_from_button.ui
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_touchscreen_advanced.ui
+ configuration/configure_touch_widget.h
configuration/configure_ui.cpp
configuration/configure_ui.h
configuration/configure_ui.ui
+ configuration/configure_vibration.cpp
+ configuration/configure_vibration.h
+ configuration/configure_vibration.ui
configuration/configure_web.cpp
configuration/configure_web.h
configuration/configure_web.ui
+ configuration/input_profiles.cpp
+ configuration/input_profiles.h
debugger/console.cpp
debugger/console.h
+ debugger/controller.cpp
+ debugger/controller.h
debugger/profiler.cpp
debugger/profiler.h
debugger/wait_tree.cpp
@@ -123,10 +145,13 @@ add_executable(yuzu
util/limitable_input_dialog.h
util/sequence_dialog/sequence_dialog.cpp
util/sequence_dialog/sequence_dialog.h
+ util/url_request_interceptor.cpp
+ util/url_request_interceptor.h
util/util.cpp
util/util.h
compatdb.cpp
compatdb.h
+ yuzu.qrc
yuzu.rc
)
@@ -199,7 +224,8 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
-if (ENABLE_VULKAN AND NOT WIN32)
+target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
+if (NOT WIN32)
target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
@@ -251,17 +277,12 @@ endif()
if (MSVC)
include(CopyYuzuQt5Deps)
include(CopyYuzuSDLDeps)
- include(CopyYuzuUnicornDeps)
+ include(CopyYuzuFFmpegDeps)
copy_yuzu_Qt5_deps(yuzu)
copy_yuzu_SDL_deps(yuzu)
- copy_yuzu_unicorn_deps(yuzu)
+ copy_yuzu_FFmpeg_deps(yuzu)
endif()
if (NOT APPLE)
target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
endif()
-
-if (ENABLE_VULKAN)
- target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
- target_compile_definitions(yuzu PRIVATE HAS_VULKAN)
-endif()
diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui
index f122ba39d..1b320630c 100644
--- a/src/yuzu/aboutdialog.ui
+++ b/src/yuzu/aboutdialog.ui
@@ -160,32 +160,12 @@ p, li { white-space: pre-wrap; }
<signal>accepted()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>248</x>
- <y>254</y>
- </hint>
- <hint type="destinationlabel">
- <x>157</x>
- <y>274</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AboutDialog</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>316</x>
- <y>260</y>
- </hint>
- <hint type="destinationlabel">
- <x>286</x>
- <y>274</y>
- </hint>
- </hints>
</connection>
</connections>
</ui>
diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp
new file mode 100644
index 000000000..b92cd6886
--- /dev/null
+++ b/src/yuzu/applets/controller.cpp
@@ -0,0 +1,679 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <thread>
+
+#include "common/assert.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/lock.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+#include "ui_controller.h"
+#include "yuzu/applets/controller.h"
+#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_input_profile_dialog.h"
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
+#include "yuzu/main.h"
+
+namespace {
+
+constexpr std::size_t HANDHELD_INDEX = 8;
+
+constexpr std::array<std::array<bool, 4>, 8> led_patterns{{
+ {true, false, false, false},
+ {true, true, false, false},
+ {true, true, true, false},
+ {true, true, true, true},
+ {true, false, false, true},
+ {true, false, true, false},
+ {true, false, true, true},
+ {false, true, true, false},
+}};
+
+void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
+ bool connected) {
+ Core::System& system{Core::System::GetInstance()};
+
+ if (!system.IsPoweredOn()) {
+ return;
+ }
+
+ Service::SM::ServiceManager& sm = system.ServiceManager();
+
+ auto& npad =
+ sm.GetService<Service::HID::Hid>("hid")
+ ->GetAppletResource()
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+
+ npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
+}
+
+// Returns true if the given controller type is compatible with the given parameters.
+bool IsControllerCompatible(Settings::ControllerType controller_type,
+ Core::Frontend::ControllerParameters parameters) {
+ switch (controller_type) {
+ case Settings::ControllerType::ProController:
+ return parameters.allow_pro_controller;
+ case Settings::ControllerType::DualJoyconDetached:
+ return parameters.allow_dual_joycons;
+ case Settings::ControllerType::LeftJoycon:
+ return parameters.allow_left_joycon;
+ case Settings::ControllerType::RightJoycon:
+ return parameters.allow_right_joycon;
+ case Settings::ControllerType::Handheld:
+ return parameters.enable_single_mode && parameters.allow_handheld;
+ case Settings::ControllerType::GameCube:
+ return parameters.allow_gamecube_controller;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+QtControllerSelectorDialog::QtControllerSelectorDialog(
+ QWidget* parent, Core::Frontend::ControllerParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
+ parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
+ input_profiles(std::make_unique<InputProfiles>()) {
+ ui->setupUi(this);
+
+ player_widgets = {
+ ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
+ ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
+ };
+
+ player_groupboxes = {
+ ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
+ ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
+ ui->groupPlayer7Connected, ui->groupPlayer8Connected,
+ };
+
+ connected_controller_icons = {
+ ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
+ ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
+ };
+
+ led_patterns_boxes = {{
+ {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
+ ui->checkboxPlayer1LED4},
+ {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
+ ui->checkboxPlayer2LED4},
+ {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
+ ui->checkboxPlayer3LED4},
+ {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
+ ui->checkboxPlayer4LED4},
+ {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
+ ui->checkboxPlayer5LED4},
+ {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
+ ui->checkboxPlayer6LED4},
+ {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
+ ui->checkboxPlayer7LED4},
+ {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
+ ui->checkboxPlayer8LED4},
+ }};
+
+ explain_text_labels = {
+ ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain,
+ ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain,
+ ui->labelPlayer7Explain, ui->labelPlayer8Explain,
+ };
+
+ emulated_controllers = {
+ ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
+ ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
+ ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
+ };
+
+ player_labels = {
+ ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
+ ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
+ };
+
+ connected_controller_labels = {
+ ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
+ ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
+ ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
+ };
+
+ connected_controller_checkboxes = {
+ ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
+ ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
+ ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
+ };
+
+ // Setup/load everything prior to setting up connections.
+ // This avoids unintentionally changing the states of elements while loading them in.
+ SetSupportedControllers();
+ DisableUnsupportedPlayers();
+
+ for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) {
+ SetEmulatedControllers(player_index);
+ }
+
+ LoadConfiguration();
+
+ for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+ SetExplainText(i);
+ UpdateControllerIcon(i);
+ UpdateLEDPattern(i);
+ UpdateBorderColor(i);
+
+ connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
+ if (checked) {
+ for (std::size_t index = 0; index <= i; ++index) {
+ connected_controller_checkboxes[index]->setChecked(checked);
+ }
+ } else {
+ for (std::size_t index = i; index < NUM_PLAYERS; ++index) {
+ connected_controller_checkboxes[index]->setChecked(checked);
+ }
+ }
+ });
+
+ connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
+ [this, i](int) {
+ UpdateControllerIcon(i);
+ UpdateControllerState(i);
+ UpdateLEDPattern(i);
+ CheckIfParametersMet();
+ });
+
+ connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
+ player_groupboxes[i]->setChecked(state == Qt::Checked);
+ UpdateControllerIcon(i);
+ UpdateControllerState(i);
+ UpdateLEDPattern(i);
+ UpdateBorderColor(i);
+ CheckIfParametersMet();
+ });
+
+ if (i == 0) {
+ connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
+ [this, i](int index) {
+ UpdateDockedState(GetControllerTypeFromIndex(index, i) ==
+ Settings::ControllerType::Handheld);
+ });
+ }
+ }
+
+ connect(ui->vibrationButton, &QPushButton::clicked, this,
+ &QtControllerSelectorDialog::CallConfigureVibrationDialog);
+
+ connect(ui->inputConfigButton, &QPushButton::clicked, this,
+ &QtControllerSelectorDialog::CallConfigureInputProfileDialog);
+
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
+ &QtControllerSelectorDialog::ApplyConfiguration);
+
+ // Enhancement: Check if the parameters have already been met before disconnecting controllers.
+ // If all the parameters are met AND only allows a single player,
+ // stop the constructor here as we do not need to continue.
+ if (CheckIfParametersMet() && parameters.enable_single_mode) {
+ return;
+ }
+
+ // If keep_controllers_connected is false, forcefully disconnect all controllers
+ if (!parameters.keep_controllers_connected) {
+ for (auto player : player_groupboxes) {
+ player->setChecked(false);
+ }
+ }
+
+ resize(0, 0);
+}
+
+QtControllerSelectorDialog::~QtControllerSelectorDialog() = default;
+
+int QtControllerSelectorDialog::exec() {
+ if (parameters_met && parameters.enable_single_mode) {
+ return QDialog::Accepted;
+ }
+ return QDialog::exec();
+}
+
+void QtControllerSelectorDialog::ApplyConfiguration() {
+ const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
+ Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
+ OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
+
+ Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
+ Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
+}
+
+void QtControllerSelectorDialog::LoadConfiguration() {
+ for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
+ const auto connected =
+ Settings::values.players.GetValue()[index].connected ||
+ (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
+ player_groupboxes[index]->setChecked(connected);
+ connected_controller_checkboxes[index]->setChecked(connected);
+ emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType(
+ Settings::values.players.GetValue()[index].controller_type, index));
+ }
+
+ UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
+
+ ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
+ ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
+}
+
+void QtControllerSelectorDialog::CallConfigureVibrationDialog() {
+ ConfigureVibration dialog(this);
+
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ dialog.ApplyConfiguration();
+ }
+}
+
+void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
+ ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get());
+
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+ Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+}
+
+bool QtControllerSelectorDialog::CheckIfParametersMet() {
+ // Here, we check and validate the current configuration against all applicable parameters.
+ const auto num_connected_players = static_cast<int>(
+ std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
+ [this](const QGroupBox* player) { return player->isChecked(); }));
+
+ const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ // First, check against the number of connected players.
+ if (num_connected_players < min_supported_players ||
+ num_connected_players > max_supported_players) {
+ parameters_met = false;
+ ui->buttonBox->setEnabled(parameters_met);
+ return parameters_met;
+ }
+
+ // Next, check against all connected controllers.
+ const auto all_controllers_compatible = [this] {
+ for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
+ // Skip controllers that are not used, we only care about the currently connected ones.
+ if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
+ continue;
+ }
+
+ const auto compatible = IsControllerCompatible(
+ GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index),
+ parameters);
+
+ // If any controller is found to be incompatible, return false early.
+ if (!compatible) {
+ return false;
+ }
+ }
+
+ // Reaching here means all currently connected controllers are compatible.
+ return true;
+ }();
+
+ parameters_met = all_controllers_compatible;
+ ui->buttonBox->setEnabled(parameters_met);
+ return parameters_met;
+}
+
+void QtControllerSelectorDialog::SetSupportedControllers() {
+ const QString theme = [] {
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ return QStringLiteral("_dark");
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_midnight");
+ } else {
+ return QString{};
+ }
+ }();
+
+ if (parameters.enable_single_mode && parameters.allow_handheld) {
+ ui->controllerSupported1->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
+ } else {
+ ui->controllerSupported1->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_dual_joycons) {
+ ui->controllerSupported2->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
+ } else {
+ ui->controllerSupported2->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_left_joycon) {
+ ui->controllerSupported3->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
+ } else {
+ ui->controllerSupported3->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_right_joycon) {
+ ui->controllerSupported4->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
+ } else {
+ ui->controllerSupported4->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
+ }
+
+ if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) {
+ ui->controllerSupported5->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
+ } else {
+ ui->controllerSupported5->setStyleSheet(
+ QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
+ .arg(theme));
+ }
+
+ // enable_single_mode overrides min_players and max_players.
+ if (parameters.enable_single_mode) {
+ ui->numberSupportedLabel->setText(QStringLiteral("1"));
+ return;
+ }
+
+ if (parameters.min_players == parameters.max_players) {
+ ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
+ } else {
+ ui->numberSupportedLabel->setText(
+ QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
+ }
+}
+
+void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) {
+ auto& pairs = index_controller_type_pairs[player_index];
+
+ pairs.clear();
+ emulated_controllers[player_index]->clear();
+
+ pairs.emplace_back(emulated_controllers[player_index]->count(),
+ Settings::ControllerType::ProController);
+ emulated_controllers[player_index]->addItem(tr("Pro Controller"));
+
+ pairs.emplace_back(emulated_controllers[player_index]->count(),
+ Settings::ControllerType::DualJoyconDetached);
+ emulated_controllers[player_index]->addItem(tr("Dual Joycons"));
+
+ pairs.emplace_back(emulated_controllers[player_index]->count(),
+ Settings::ControllerType::LeftJoycon);
+ emulated_controllers[player_index]->addItem(tr("Left Joycon"));
+
+ pairs.emplace_back(emulated_controllers[player_index]->count(),
+ Settings::ControllerType::RightJoycon);
+ emulated_controllers[player_index]->addItem(tr("Right Joycon"));
+
+ if (player_index == 0) {
+ pairs.emplace_back(emulated_controllers[player_index]->count(),
+ Settings::ControllerType::Handheld);
+ emulated_controllers[player_index]->addItem(tr("Handheld"));
+ }
+
+ pairs.emplace_back(emulated_controllers[player_index]->count(),
+ Settings::ControllerType::GameCube);
+ emulated_controllers[player_index]->addItem(tr("GameCube Controller"));
+}
+
+Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex(
+ int index, std::size_t player_index) const {
+ const auto& pairs = index_controller_type_pairs[player_index];
+
+ const auto it = std::find_if(pairs.begin(), pairs.end(),
+ [index](const auto& pair) { return pair.first == index; });
+
+ if (it == pairs.end()) {
+ return Settings::ControllerType::ProController;
+ }
+
+ return it->second;
+}
+
+int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type,
+ std::size_t player_index) const {
+ const auto& pairs = index_controller_type_pairs[player_index];
+
+ const auto it = std::find_if(pairs.begin(), pairs.end(),
+ [type](const auto& pair) { return pair.second == type; });
+
+ if (it == pairs.end()) {
+ return 0;
+ }
+
+ return it->first;
+}
+
+void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
+ if (!player_groupboxes[player_index]->isChecked()) {
+ connected_controller_icons[player_index]->setStyleSheet(QString{});
+ player_labels[player_index]->show();
+ return;
+ }
+
+ const QString stylesheet = [this, player_index] {
+ switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
+ player_index)) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::GameCube:
+ return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
+ case Settings::ControllerType::DualJoyconDetached:
+ return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
+ case Settings::ControllerType::LeftJoycon:
+ return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
+ case Settings::ControllerType::RightJoycon:
+ return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
+ case Settings::ControllerType::Handheld:
+ return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
+ default:
+ return QString{};
+ }
+ }();
+
+ if (stylesheet.isEmpty()) {
+ connected_controller_icons[player_index]->setStyleSheet(QString{});
+ player_labels[player_index]->show();
+ return;
+ }
+
+ const QString theme = [] {
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ return QStringLiteral("_dark");
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ return QStringLiteral("_midnight");
+ } else {
+ return QString{};
+ }
+ }();
+
+ connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
+ player_labels[player_index]->hide();
+}
+
+void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
+ auto& player = Settings::values.players.GetValue()[player_index];
+
+ const auto controller_type = GetControllerTypeFromIndex(
+ emulated_controllers[player_index]->currentIndex(), player_index);
+ const auto player_connected = player_groupboxes[player_index]->isChecked() &&
+ controller_type != Settings::ControllerType::Handheld;
+
+ if (player.controller_type == controller_type && player.connected == player_connected) {
+ // Set vibration devices in the event that the input device has changed.
+ ConfigureVibration::SetVibrationDevices(player_index);
+ return;
+ }
+
+ // Disconnect the controller first.
+ UpdateController(controller_type, player_index, false);
+
+ player.controller_type = controller_type;
+ player.connected = player_connected;
+
+ ConfigureVibration::SetVibrationDevices(player_index);
+
+ // Handheld
+ if (player_index == 0) {
+ auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
+ if (controller_type == Settings::ControllerType::Handheld) {
+ handheld = player;
+ }
+ handheld.connected = player_groupboxes[player_index]->isChecked() &&
+ controller_type == Settings::ControllerType::Handheld;
+ UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
+ }
+
+ if (!player.connected) {
+ return;
+ }
+
+ // This emulates a delay between disconnecting and reconnecting controllers as some games
+ // do not respond to a change in controller type if it was instantaneous.
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(60ms);
+
+ UpdateController(controller_type, player_index, player_connected);
+}
+
+void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
+ if (!player_groupboxes[player_index]->isChecked() ||
+ GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
+ player_index) == Settings::ControllerType::Handheld) {
+ led_patterns_boxes[player_index][0]->setChecked(false);
+ led_patterns_boxes[player_index][1]->setChecked(false);
+ led_patterns_boxes[player_index][2]->setChecked(false);
+ led_patterns_boxes[player_index][3]->setChecked(false);
+ return;
+ }
+
+ led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]);
+ led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]);
+ led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]);
+ led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]);
+}
+
+void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
+ if (!parameters.enable_border_color ||
+ player_index >= static_cast<std::size_t>(parameters.max_players) ||
+ player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
+ return;
+ }
+
+ player_groupboxes[player_index]->setStyleSheet(
+ player_groupboxes[player_index]->styleSheet().append(
+ QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
+ "{ border: 1px solid rgba(%2, %3, %4, %5); }")
+ .arg(player_index + 1)
+ .arg(parameters.border_colors[player_index][0])
+ .arg(parameters.border_colors[player_index][1])
+ .arg(parameters.border_colors[player_index][2])
+ .arg(parameters.border_colors[player_index][3])));
+}
+
+void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) {
+ if (!parameters.enable_explain_text ||
+ player_index >= static_cast<std::size_t>(parameters.max_players)) {
+ return;
+ }
+
+ explain_text_labels[player_index]->setText(QString::fromStdString(
+ Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(),
+ parameters.explain_text[player_index].size())));
+}
+
+void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
+ // Disallow changing the console mode if the controller type is handheld.
+ ui->radioDocked->setEnabled(!is_handheld);
+ ui->radioUndocked->setEnabled(!is_handheld);
+
+ ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
+ ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
+
+ // Also force into undocked mode if the controller type is handheld.
+ if (is_handheld) {
+ ui->radioUndocked->setChecked(true);
+ }
+}
+
+void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ switch (max_supported_players) {
+ case 0:
+ default:
+ UNREACHABLE();
+ return;
+ case 1:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ ui->widgetSpacer3->hide();
+ ui->widgetSpacer4->hide();
+ break;
+ case 2:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ ui->widgetSpacer3->hide();
+ break;
+ case 3:
+ ui->widgetSpacer->hide();
+ ui->widgetSpacer2->hide();
+ break;
+ case 4:
+ ui->widgetSpacer->hide();
+ break;
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ break;
+ }
+
+ for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
+ // Disconnect any unsupported players here and disable or hide them if applicable.
+ Settings::values.players.GetValue()[index].connected = false;
+ UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false);
+ // Hide the player widgets when max_supported_controllers is less than or equal to 4.
+ if (max_supported_players <= 4) {
+ player_widgets[index]->hide();
+ }
+
+ // Disable and hide the following to prevent these from interaction.
+ player_widgets[index]->setDisabled(true);
+ connected_controller_checkboxes[index]->setDisabled(true);
+ connected_controller_labels[index]->hide();
+ connected_controller_checkboxes[index]->hide();
+ }
+}
+
+QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
+ connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
+ &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
+ connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
+ &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
+}
+
+QtControllerSelector::~QtControllerSelector() = default;
+
+void QtControllerSelector::ReconfigureControllers(
+ std::function<void()> callback_, const Core::Frontend::ControllerParameters& parameters) const {
+ callback = std::move(callback_);
+ emit MainWindowReconfigureControllers(parameters);
+}
+
+void QtControllerSelector::MainWindowReconfigureFinished() {
+ // Acquire the HLE mutex
+ std::lock_guard lock(HLE::g_hle_lock);
+ callback();
+}
diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h
new file mode 100644
index 000000000..3518eed56
--- /dev/null
+++ b/src/yuzu/applets/controller.h
@@ -0,0 +1,161 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+#include "core/frontend/applets/controller.h"
+
+class GMainWindow;
+class QCheckBox;
+class QComboBox;
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Settings {
+enum class ControllerType;
+}
+
+namespace Ui {
+class QtControllerSelectorDialog;
+}
+
+class QtControllerSelectorDialog final : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit QtControllerSelectorDialog(QWidget* parent,
+ Core::Frontend::ControllerParameters parameters_,
+ InputCommon::InputSubsystem* input_subsystem_);
+ ~QtControllerSelectorDialog() override;
+
+ int exec() override;
+
+private:
+ // Applies the current configuration.
+ void ApplyConfiguration();
+
+ // Loads the current input configuration into the frontend applet.
+ void LoadConfiguration();
+
+ // Initializes the "Configure Vibration" Dialog.
+ void CallConfigureVibrationDialog();
+
+ // Initializes the "Create Input Profile" Dialog.
+ void CallConfigureInputProfileDialog();
+
+ // Checks the current configuration against the given parameters.
+ // This sets and returns the value of parameters_met.
+ bool CheckIfParametersMet();
+
+ // Sets the controller icons for "Supported Controller Types".
+ void SetSupportedControllers();
+
+ // Sets the emulated controllers per player.
+ void SetEmulatedControllers(std::size_t player_index);
+
+ // Gets the Controller Type for a given controller combobox index per player.
+ Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const;
+
+ // Gets the controller combobox index for a given Controller Type per player.
+ int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const;
+
+ // Updates the controller icons per player.
+ void UpdateControllerIcon(std::size_t player_index);
+
+ // Updates the controller state (type and connection status) per player.
+ void UpdateControllerState(std::size_t player_index);
+
+ // Updates the LED pattern per player.
+ void UpdateLEDPattern(std::size_t player_index);
+
+ // Updates the border color per player.
+ void UpdateBorderColor(std::size_t player_index);
+
+ // Sets the "Explain Text" per player.
+ void SetExplainText(std::size_t player_index);
+
+ // Updates the console mode.
+ void UpdateDockedState(bool is_handheld);
+
+ // Disables and disconnects unsupported players based on the given parameters.
+ void DisableUnsupportedPlayers();
+
+ std::unique_ptr<Ui::QtControllerSelectorDialog> ui;
+
+ // Parameters sent in from the backend HLE applet.
+ Core::Frontend::ControllerParameters parameters;
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ std::unique_ptr<InputProfiles> input_profiles;
+
+ // This is true if and only if all parameters are met. Otherwise, this is false.
+ // This determines whether the "OK" button can be clicked to exit the applet.
+ bool parameters_met{false};
+
+ static constexpr std::size_t NUM_PLAYERS = 8;
+
+ // Widgets encapsulating the groupboxes and comboboxes per player.
+ std::array<QWidget*, NUM_PLAYERS> player_widgets;
+
+ // Groupboxes encapsulating the controller icons and LED patterns per player.
+ std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes;
+
+ // Icons for currently connected controllers/players.
+ std::array<QWidget*, NUM_PLAYERS> connected_controller_icons;
+
+ // Labels that represent the player numbers in place of the controller icons.
+ std::array<QLabel*, NUM_PLAYERS> player_labels;
+
+ // LED patterns for currently connected controllers/players.
+ std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes;
+
+ // Labels representing additional information known as "Explain Text" per player.
+ std::array<QLabel*, NUM_PLAYERS> explain_text_labels;
+
+ // Comboboxes with a list of emulated controllers per player.
+ std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
+
+ /// Pairs of emulated controller index and Controller Type enum per player.
+ std::array<std::vector<std::pair<int, Settings::ControllerType>>, NUM_PLAYERS>
+ index_controller_type_pairs;
+
+ // Labels representing the number of connected controllers
+ // above the "Connected Controllers" checkboxes.
+ std::array<QLabel*, NUM_PLAYERS> connected_controller_labels;
+
+ // Checkboxes representing the "Connected Controllers".
+ std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes;
+};
+
+class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet {
+ Q_OBJECT
+
+public:
+ explicit QtControllerSelector(GMainWindow& parent);
+ ~QtControllerSelector() override;
+
+ void ReconfigureControllers(
+ std::function<void()> callback_,
+ const Core::Frontend::ControllerParameters& parameters) const override;
+
+signals:
+ void MainWindowReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters) const;
+
+private:
+ void MainWindowReconfigureFinished();
+
+ mutable std::function<void()> callback;
+};
diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui
new file mode 100644
index 000000000..c8cb6bcf3
--- /dev/null
+++ b/src/yuzu/applets/controller.ui
@@ -0,0 +1,2653 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtControllerSelectorDialog</class>
+ <widget class="QDialog" name="QtControllerSelectorDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>839</width>
+ <height>630</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Controller Applet</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="mainControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,3,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="topControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>10</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>10</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>10</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllersSupported" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="controllersSupportedLabel">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Supported Controller Types:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported1" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported3" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported4" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="controllerSupported5" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="playersSupported" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>70</width>
+ <height>70</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_20">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>16</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="maxSupportedLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Players:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="numberSupportedLabel">
+ <property name="font">
+ <font>
+ <pointsize>14</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>1 - 8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="middleControllerApplet" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <item row="1" column="7">
+ <widget class="QWidget" name="widgetPlayer4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_27">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer4Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer4" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer4">
+ <property name="text">
+ <string>P4</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player4LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer4LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player4Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_39">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer4Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer4Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer4Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QWidget" name="widgetPlayer2" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_29">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer2Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer2" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_13">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer2">
+ <property name="text">
+ <string>P2</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player2LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer2LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player2Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_37">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer2Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer2Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer2Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QWidget" name="widgetPlayer1" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_30">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer1Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer1" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer1">
+ <property name="text">
+ <string>P1</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player1LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED1">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer1LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player1Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_36">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer1Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer1Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Handheld</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer1Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QWidget" name="widgetSpacer2" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>25</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_31">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>25</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QWidget" name="widgetSpacer4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_33">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QWidget" name="widgetSpacer3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_32">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer7">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QWidget" name="widgetPlayer3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_28">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer3Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer3" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer3">
+ <property name="text">
+ <string>P3</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player3LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer3LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player3Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_38">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer3Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer3Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer3Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QWidget" name="widgetSpacer5" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_34">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="5">
+ <widget class="QWidget" name="widgetPlayer7" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_25">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer7Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer7" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_18">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer7">
+ <property name="text">
+ <string>P7</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player7LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer7LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player7Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_42">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer7Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer7Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer7Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="7">
+ <widget class="QWidget" name="widgetPlayer8" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_26">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer8Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer8" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_19">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer8">
+ <property name="text">
+ <string>P8</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player8LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer8LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player8Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_35">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer8Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer8Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer8Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QWidget" name="widgetPlayer5" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_23">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer5Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer5" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_16">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer5">
+ <property name="text">
+ <string>P5</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player5LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED1">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer5LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player5Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_40">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer5Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer5Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer5Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="6" column="3">
+ <widget class="QWidget" name="widgetPlayer6" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_24">
+ <property name="spacing">
+ <number>5</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="groupPlayer6Connected">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="title">
+ <string/>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_9" stretch="1,0">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>14</number>
+ </property>
+ <property name="topMargin">
+ <number>7</number>
+ </property>
+ <property name="rightMargin">
+ <number>14</number>
+ </property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="controllerPlayer6" native="true">
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_17">
+ <property name="topMargin">
+ <number>16</number>
+ </property>
+ <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
+ <widget class="QLabel" name="labelPlayer6">
+ <property name="text">
+ <string>P6</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QWidget" name="Player6LEDs" native="true">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED1"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED2"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED3"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkboxPlayer6LED4"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="Player6Explain" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>10</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>150</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_41">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelPlayer6Explain">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer6Emulated">
+ <item>
+ <property name="text">
+ <string>Pro Controller</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Dual Joycons</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Left Joycon</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right Joycon</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboPlayer6Profile">
+ <item>
+ <property name="text">
+ <string>Use Current Config</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="10" column="1">
+ <widget class="QWidget" name="widgetSpacer" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_22">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QWidget" name="widgetSpacer6" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QWidget" name="widgetSpacer7" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>25</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_16">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>25</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QWidget" name="widgetSpacer9" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_17">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="controllerAppletVerticalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>25</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="bottomControllerApplet" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="spacing">
+ <number>15</number>
+ </property>
+ <property name="leftMargin">
+ <number>15</number>
+ </property>
+ <property name="topMargin">
+ <number>8</number>
+ </property>
+ <property name="rightMargin">
+ <number>15</number>
+ </property>
+ <property name="bottomMargin">
+ <number>15</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="handheldGroup">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Console Mode</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>8</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="radioDocked">
+ <property name="text">
+ <string>Docked</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="radioUndocked">
+ <property name="text">
+ <string>Undocked</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroup">
+ <property name="title">
+ <string>Vibration</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="vibrationButton">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="motionGroup">
+ <property name="title">
+ <string>Motion</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="motionButton">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="inputConfigGroup">
+ <property name="title">
+ <string>Profiles</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="inputConfigButton">
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Create</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="connectedControllers" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>5</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="1" column="4">
+ <widget class="QCheckBox" name="checkboxPlayer4Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelControllers">
+ <property name="text">
+ <string>Controllers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="checkboxPlayer2Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="labelConnectedPlayer1">
+ <property name="text">
+ <string>1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QCheckBox" name="checkboxPlayer3Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="checkboxPlayer1Connected">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="labelConnectedPlayer2">
+ <property name="text">
+ <string>2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="labelConnectedPlayer4">
+ <property name="text">
+ <string>4</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="labelConnectedPlayer3">
+ <property name="text">
+ <string>3</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelConnected">
+ <property name="text">
+ <string>Connected</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QCheckBox" name="checkboxPlayer7Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QLabel" name="labelConnectedPlayer5">
+ <property name="text">
+ <string>5</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QCheckBox" name="checkboxPlayer6Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="7">
+ <widget class="QLabel" name="labelConnectedPlayer7">
+ <property name="text">
+ <string>7</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QCheckBox" name="checkboxPlayer5Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QLabel" name="labelConnectedPlayer6">
+ <property name="text">
+ <string>6</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="8">
+ <widget class="QLabel" name="labelConnectedPlayer8">
+ <property name="text">
+ <string>8</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QCheckBox" name="checkboxPlayer8Connected">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="controllerAppletHorizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item alignment="Qt::AlignBottom">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QtControllerSelectorDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/applets/error.cpp b/src/yuzu/applets/error.cpp
index 08ed57355..8ee03ddb3 100644
--- a/src/yuzu/applets/error.cpp
+++ b/src/yuzu/applets/error.cpp
@@ -17,9 +17,9 @@ QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) {
QtErrorDisplay::~QtErrorDisplay() = default;
void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) const {
- this->callback = std::move(finished);
+ callback = std::move(finished);
emit MainWindowDisplayError(
- tr("An error has occured.\nPlease try again or contact the developer of the "
+ tr("An error has occurred.\nPlease try again or contact the developer of the "
"software.\n\nError Code: %1-%2 (0x%3)")
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
@@ -28,11 +28,11 @@ void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished)
void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
std::function<void()> finished) const {
- this->callback = std::move(finished);
+ callback = std::move(finished);
const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count());
emit MainWindowDisplayError(
- tr("An error occured on %1 at %2.\nPlease try again or contact the "
+ tr("An error occurred on %1 at %2.\nPlease try again or contact the "
"developer of the software.\n\nError Code: %3-%4 (0x%5)")
.arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy")))
.arg(date_time.toString(QStringLiteral("h:mm:ss A")))
@@ -44,9 +44,9 @@ void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::secon
void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text,
std::string fullscreen_text,
std::function<void()> finished) const {
- this->callback = std::move(finished);
+ callback = std::move(finished);
emit MainWindowDisplayError(
- tr("An error has occured.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5")
+ tr("An error has occurred.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5")
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
.arg(error.raw, 8, 16, QChar::fromLatin1('0'))
diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp
index dca8835ed..0a4c48b3d 100644
--- a/src/yuzu/applets/profile_select.cpp
+++ b/src/yuzu/applets/profile_select.cpp
@@ -93,7 +93,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
const auto& profiles = profile_manager->GetAllUsers();
for (const auto& user : profiles) {
- Service::Account::ProfileBase profile;
+ Service::Account::ProfileBase profile{};
if (!profile_manager->GetProfileBase(user, profile))
continue;
@@ -114,6 +114,15 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
+int QtProfileSelectionDialog::exec() {
+ // Skip profile selection when there's only one.
+ if (profile_manager->GetUserCount() == 1) {
+ user_index = 0;
+ return QDialog::Accepted;
+ }
+ return QDialog::exec();
+}
+
void QtProfileSelectionDialog::accept() {
QDialog::accept();
}
@@ -141,8 +150,8 @@ QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
QtProfileSelector::~QtProfileSelector() = default;
void QtProfileSelector::SelectProfile(
- std::function<void(std::optional<Common::UUID>)> callback) const {
- this->callback = std::move(callback);
+ std::function<void(std::optional<Common::UUID>)> callback_) const {
+ callback = std::move(callback_);
emit MainWindowSelectProfile();
}
diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h
index cee886a77..4e9037488 100644
--- a/src/yuzu/applets/profile_select.h
+++ b/src/yuzu/applets/profile_select.h
@@ -27,6 +27,7 @@ public:
explicit QtProfileSelectionDialog(QWidget* parent);
~QtProfileSelectionDialog() override;
+ int exec() override;
void accept() override;
void reject() override;
@@ -59,7 +60,7 @@ public:
explicit QtProfileSelector(GMainWindow& parent);
~QtProfileSelector() override;
- void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override;
+ void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback_) const override;
signals:
void MainWindowSelectProfile() const;
diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp
index af36f07c6..ab8cfd8ee 100644
--- a/src/yuzu/applets/software_keyboard.cpp
+++ b/src/yuzu/applets/software_keyboard.cpp
@@ -135,8 +135,8 @@ void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16st
}
void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message,
- std::function<void()> finished_check) const {
- this->finished_check = std::move(finished_check);
+ std::function<void()> finished_check_) const {
+ finished_check = std::move(finished_check_);
emit MainWindowTextCheckDialog(error_message);
}
diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h
index 44bcece75..9e1094cce 100644
--- a/src/yuzu/applets/software_keyboard.h
+++ b/src/yuzu/applets/software_keyboard.h
@@ -61,7 +61,7 @@ public:
void RequestText(std::function<void(std::optional<std::u16string>)> out,
Core::Frontend::SoftwareKeyboardParameters parameters) const override;
void SendTextCheckDialog(std::u16string error_message,
- std::function<void()> finished_check) const override;
+ std::function<void()> finished_check_) const override;
signals:
void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const;
diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp
index 33f1c385d..e482ba029 100644
--- a/src/yuzu/applets/web_browser.cpp
+++ b/src/yuzu/applets/web_browser.cpp
@@ -1,115 +1,414 @@
-// Copyright 2018 yuzu Emulator Project
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <mutex>
-
+#ifdef YUZU_USE_QT_WEB_ENGINE
#include <QKeyEvent>
-#include "core/hle/lock.h"
+#include <QWebEngineProfile>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebEngineUrlScheme>
+#endif
+
+#include "common/file_util.h"
+#include "core/core.h"
+#include "core/frontend/input_interpreter.h"
+#include "input_common/keyboard.h"
+#include "input_common/main.h"
#include "yuzu/applets/web_browser.h"
+#include "yuzu/applets/web_browser_scripts.h"
#include "yuzu/main.h"
+#include "yuzu/util/url_request_interceptor.h"
#ifdef YUZU_USE_QT_WEB_ENGINE
-constexpr char NX_SHIM_INJECT_SCRIPT[] = R"(
- window.nx = {};
- window.nx.playReport = {};
- window.nx.playReport.setCounterSetIdentifier = function () {
- console.log("nx.playReport.setCounterSetIdentifier called - unimplemented");
- };
+namespace {
- window.nx.playReport.incrementCounter = function () {
- console.log("nx.playReport.incrementCounter called - unimplemented");
- };
+constexpr int HIDButtonToKey(HIDButton button) {
+ switch (button) {
+ case HIDButton::DLeft:
+ case HIDButton::LStickLeft:
+ return Qt::Key_Left;
+ case HIDButton::DUp:
+ case HIDButton::LStickUp:
+ return Qt::Key_Up;
+ case HIDButton::DRight:
+ case HIDButton::LStickRight:
+ return Qt::Key_Right;
+ case HIDButton::DDown:
+ case HIDButton::LStickDown:
+ return Qt::Key_Down;
+ default:
+ return 0;
+ }
+}
+
+} // Anonymous namespace
+
+QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QWebEngineView(parent), input_subsystem{input_subsystem_},
+ url_interceptor(std::make_unique<UrlRequestInterceptor>()),
+ input_interpreter(std::make_unique<InputInterpreter>(system)),
+ default_profile{QWebEngineProfile::defaultProfile()},
+ global_settings{QWebEngineSettings::globalSettings()} {
+ QWebEngineScript gamepad;
+ QWebEngineScript window_nx;
+
+ gamepad.setName(QStringLiteral("gamepad_script.js"));
+ window_nx.setName(QStringLiteral("window_nx_script.js"));
+
+ gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT));
+ window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT));
+
+ gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation);
+ window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation);
+
+ gamepad.setWorldId(QWebEngineScript::MainWorld);
+ window_nx.setWorldId(QWebEngineScript::MainWorld);
+
+ gamepad.setRunsOnSubFrames(true);
+ window_nx.setRunsOnSubFrames(true);
+
+ default_profile->scripts()->insert(gamepad);
+ default_profile->scripts()->insert(window_nx);
+
+ default_profile->setRequestInterceptor(url_interceptor.get());
+
+ global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
+ global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
+ global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
+ global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
+ global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true);
+ global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false);
+
+ global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto"));
+
+ connect(
+ page(), &QWebEnginePage::windowCloseRequested, page(),
+ [this] {
+ if (page()->url() == url_interceptor->GetRequestedURL()) {
+ SetFinished(true);
+ SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed);
+ }
+ },
+ Qt::QueuedConnection);
+}
+
+QtNXWebEngineView::~QtNXWebEngineView() {
+ SetFinished(true);
+ StopInputThread();
+}
+
+void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url,
+ std::string_view additional_args) {
+ is_local = true;
+
+ LoadExtractedFonts();
+ SetUserAgent(UserAgent::WebApplet);
+ SetFinished(false);
+ SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
+ SetLastURL("http://localhost/");
+ StartInputThread();
+
+ load(QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(main_url))).toString() +
+ QString::fromStdString(std::string(additional_args))));
+}
+
+void QtNXWebEngineView::LoadExternalWebPage(std::string_view main_url,
+ std::string_view additional_args) {
+ is_local = false;
+
+ SetUserAgent(UserAgent::WebApplet);
+ SetFinished(false);
+ SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
+ SetLastURL("http://localhost/");
+ StartInputThread();
+
+ load(QUrl(QString::fromStdString(std::string(main_url)) +
+ QString::fromStdString(std::string(additional_args))));
+}
+
+void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
+ const QString user_agent_str = [user_agent] {
+ switch (user_agent) {
+ case UserAgent::WebApplet:
+ default:
+ return QStringLiteral("WebApplet");
+ case UserAgent::ShopN:
+ return QStringLiteral("ShopN");
+ case UserAgent::LoginApplet:
+ return QStringLiteral("LoginApplet");
+ case UserAgent::ShareApplet:
+ return QStringLiteral("ShareApplet");
+ case UserAgent::LobbyApplet:
+ return QStringLiteral("LobbyApplet");
+ case UserAgent::WifiWebAuthApplet:
+ return QStringLiteral("WifiWebAuthApplet");
+ }
+ }();
+
+ QWebEngineProfile::defaultProfile()->setHttpUserAgent(
+ QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 "
+ "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389")
+ .arg(user_agent_str));
+}
+
+bool QtNXWebEngineView::IsFinished() const {
+ return finished;
+}
+
+void QtNXWebEngineView::SetFinished(bool finished_) {
+ finished = finished_;
+}
+
+Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const {
+ return exit_reason;
+}
+
+void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) {
+ exit_reason = exit_reason_;
+}
+
+const std::string& QtNXWebEngineView::GetLastURL() const {
+ return last_url;
+}
+
+void QtNXWebEngineView::SetLastURL(std::string last_url_) {
+ last_url = std::move(last_url_);
+}
+
+QString QtNXWebEngineView::GetCurrentURL() const {
+ return url_interceptor->GetRequestedURL().toString();
+}
+
+void QtNXWebEngineView::hide() {
+ SetFinished(true);
+ StopInputThread();
- window.nx.footer = {};
- window.nx.footer.unsetAssign = function () {
- console.log("nx.footer.unsetAssign called - unimplemented");
+ QWidget::hide();
+}
+
+void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
+ if (is_local) {
+ input_subsystem->GetKeyboard()->PressKey(event->key());
+ }
+}
+
+void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
+ if (is_local) {
+ input_subsystem->GetKeyboard()->ReleaseKey(event->key());
+ }
+}
+
+template <HIDButton... T>
+void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
+ const auto f = [this](HIDButton button) {
+ if (input_interpreter->IsButtonPressedOnce(button)) {
+ page()->runJavaScript(
+ QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)),
+ [&](const QVariant& variant) {
+ if (variant.toBool()) {
+ switch (button) {
+ case HIDButton::A:
+ SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
+ break;
+ case HIDButton::B:
+ SendKeyPressEvent(Qt::Key_B);
+ break;
+ case HIDButton::X:
+ SendKeyPressEvent(Qt::Key_X);
+ break;
+ case HIDButton::Y:
+ SendKeyPressEvent(Qt::Key_Y);
+ break;
+ default:
+ break;
+ }
+ }
+ });
+
+ page()->runJavaScript(
+ QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }")
+ .arg(static_cast<u8>(button)));
+ }
};
- var yuzu_key_callbacks = [];
- window.nx.footer.setAssign = function(key, discard1, func, discard2) {
- switch (key) {
- case 'A':
- yuzu_key_callbacks[0] = func;
- break;
- case 'B':
- yuzu_key_callbacks[1] = func;
- break;
- case 'X':
- yuzu_key_callbacks[2] = func;
- break;
- case 'Y':
- yuzu_key_callbacks[3] = func;
- break;
- case 'L':
- yuzu_key_callbacks[6] = func;
- break;
- case 'R':
- yuzu_key_callbacks[7] = func;
- break;
+ (f(T), ...);
+}
+
+template <HIDButton... T>
+void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
+ const auto f = [this](HIDButton button) {
+ if (input_interpreter->IsButtonPressedOnce(button)) {
+ SendKeyPressEvent(HIDButtonToKey(button));
}
};
- var applet_done = false;
- window.nx.endApplet = function() {
- applet_done = true;
+ (f(T), ...);
+}
+
+template <HIDButton... T>
+void QtNXWebEngineView::HandleWindowKeyButtonHold() {
+ const auto f = [this](HIDButton button) {
+ if (input_interpreter->IsButtonHeld(button)) {
+ SendKeyPressEvent(HIDButtonToKey(button));
+ }
};
- window.onkeypress = function(e) { if (e.keyCode === 13) { applet_done = true; } };
-)";
+ (f(T), ...);
+}
+
+void QtNXWebEngineView::SendKeyPressEvent(int key) {
+ if (key == 0) {
+ return;
+ }
+
+ QCoreApplication::postEvent(focusProxy(),
+ new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier));
+ QCoreApplication::postEvent(focusProxy(),
+ new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier));
+}
+
+void QtNXWebEngineView::StartInputThread() {
+ if (input_thread_running) {
+ return;
+ }
+
+ input_thread_running = true;
+ input_thread = std::thread(&QtNXWebEngineView::InputThread, this);
+}
+
+void QtNXWebEngineView::StopInputThread() {
+ if (is_local) {
+ QWidget::releaseKeyboard();
+ }
-QString GetNXShimInjectionScript() {
- return QString::fromStdString(NX_SHIM_INJECT_SCRIPT);
+ input_thread_running = false;
+ if (input_thread.joinable()) {
+ input_thread.join();
+ }
}
-NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {}
+void QtNXWebEngineView::InputThread() {
+ // Wait for 1 second before allowing any inputs to be processed.
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ if (is_local) {
+ QWidget::grabKeyboard();
+ }
+
+ while (input_thread_running) {
+ input_interpreter->PollInput();
+
+ HandleWindowFooterButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y,
+ HIDButton::L, HIDButton::R>();
+
+ HandleWindowKeyButtonPressedOnce<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
+ HIDButton::DDown, HIDButton::LStickLeft,
+ HIDButton::LStickUp, HIDButton::LStickRight,
+ HIDButton::LStickDown>();
-void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) {
- parent()->event(event);
+ HandleWindowKeyButtonHold<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
+ HIDButton::DDown, HIDButton::LStickLeft, HIDButton::LStickUp,
+ HIDButton::LStickRight, HIDButton::LStickDown>();
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
}
-void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) {
- parent()->event(event);
+void QtNXWebEngineView::LoadExtractedFonts() {
+ QWebEngineScript nx_font_css;
+ QWebEngineScript load_nx_font;
+
+ const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath(
+ fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir))));
+
+ nx_font_css.setName(QStringLiteral("nx_font_css.js"));
+ load_nx_font.setName(QStringLiteral("load_nx_font.js"));
+
+ nx_font_css.setSourceCode(
+ QString::fromStdString(NX_FONT_CSS)
+ .arg(fonts_dir + QStringLiteral("/FontStandard.ttf"))
+ .arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf"))
+ .arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf"))
+ .arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf"))
+ .arg(fonts_dir + QStringLiteral("/FontKorean.ttf"))
+ .arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf"))
+ .arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf")));
+ load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT));
+
+ nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
+ load_nx_font.setInjectionPoint(QWebEngineScript::Deferred);
+
+ nx_font_css.setWorldId(QWebEngineScript::MainWorld);
+ load_nx_font.setWorldId(QWebEngineScript::MainWorld);
+
+ nx_font_css.setRunsOnSubFrames(true);
+ load_nx_font.setRunsOnSubFrames(true);
+
+ default_profile->scripts()->insert(nx_font_css);
+ default_profile->scripts()->insert(load_nx_font);
+
+ connect(
+ url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(),
+ [this] {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT));
+ },
+ Qt::QueuedConnection);
}
#endif
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
- connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage,
- Qt::QueuedConnection);
- connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this,
- &QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection);
- connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this,
- &QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection);
+ connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
+ &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
+ connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
+ &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
+ connect(&main_window, &GMainWindow::WebBrowserClosed, this,
+ &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
}
QtWebBrowser::~QtWebBrowser() = default;
-void QtWebBrowser::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) {
- this->unpack_romfs_callback = std::move(unpack_romfs_callback);
- this->finished_callback = std::move(finished_callback);
+void QtWebBrowser::OpenLocalWebPage(
+ std::string_view local_url, std::function<void()> extract_romfs_callback_,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
+ extract_romfs_callback = std::move(extract_romfs_callback_);
+ callback = std::move(callback_);
+
+ const auto index = local_url.find('?');
+
+ if (index == std::string::npos) {
+ emit MainWindowOpenWebPage(local_url, "", true);
+ } else {
+ emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
+ }
+}
+
+void QtWebBrowser::OpenExternalWebPage(
+ std::string_view external_url,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
+ callback = std::move(callback_);
+
+ const auto index = external_url.find('?');
- const auto index = url.find('?');
if (index == std::string::npos) {
- emit MainWindowOpenPage(url, "");
+ emit MainWindowOpenWebPage(external_url, "", false);
} else {
- const auto front = url.substr(0, index);
- const auto back = url.substr(index);
- emit MainWindowOpenPage(front, back);
+ emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index),
+ false);
}
}
-void QtWebBrowser::MainWindowUnpackRomFS() {
- // Acquire the HLE mutex
- std::lock_guard lock{HLE::g_hle_lock};
- unpack_romfs_callback();
+void QtWebBrowser::MainWindowExtractOfflineRomFS() {
+ extract_romfs_callback();
}
-void QtWebBrowser::MainWindowFinishedBrowsing() {
- // Acquire the HLE mutex
- std::lock_guard lock{HLE::g_hle_lock};
- finished_callback();
+void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
+ std::string last_url) {
+ callback(exit_reason, last_url);
}
diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h
index b38437e46..47f960d69 100644
--- a/src/yuzu/applets/web_browser.h
+++ b/src/yuzu/applets/web_browser.h
@@ -1,10 +1,13 @@
-// Copyright 2018 yuzu Emulator Project
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
-#include <functional>
+#include <atomic>
+#include <memory>
+#include <thread>
+
#include <QObject>
#ifdef YUZU_USE_QT_WEB_ENGINE
@@ -13,19 +16,172 @@
#include "core/frontend/applets/web_browser.h"
+enum class HIDButton : u8;
+
class GMainWindow;
+class InputInterpreter;
+class UrlRequestInterceptor;
+
+namespace Core {
+class System;
+}
+
+namespace InputCommon {
+class InputSubsystem;
+}
#ifdef YUZU_USE_QT_WEB_ENGINE
-QString GetNXShimInjectionScript();
+enum class UserAgent {
+ WebApplet,
+ ShopN,
+ LoginApplet,
+ ShareApplet,
+ LobbyApplet,
+ WifiWebAuthApplet,
+};
+
+class QWebEngineProfile;
+class QWebEngineSettings;
+
+class QtNXWebEngineView : public QWebEngineView {
+ Q_OBJECT
-class NXInputWebEngineView : public QWebEngineView {
public:
- explicit NXInputWebEngineView(QWidget* parent = nullptr);
+ explicit QtNXWebEngineView(QWidget* parent, Core::System& system,
+ InputCommon::InputSubsystem* input_subsystem_);
+ ~QtNXWebEngineView() override;
+
+ /**
+ * Loads a HTML document that exists locally. Cannot be used to load external websites.
+ *
+ * @param main_url The url to the file.
+ * @param additional_args Additional arguments appended to the main url.
+ */
+ void LoadLocalWebPage(std::string_view main_url, std::string_view additional_args);
+
+ /**
+ * Loads an external website. Cannot be used to load local urls.
+ *
+ * @param main_url The url to the website.
+ * @param additional_args Additional arguments appended to the main url.
+ */
+ void LoadExternalWebPage(std::string_view main_url, std::string_view additional_args);
+
+ /**
+ * Sets the background color of the web page.
+ *
+ * @param color The color to set.
+ */
+ void SetBackgroundColor(QColor color);
+
+ /**
+ * Sets the user agent of the web browser.
+ *
+ * @param user_agent The user agent enum.
+ */
+ void SetUserAgent(UserAgent user_agent);
+
+ [[nodiscard]] bool IsFinished() const;
+ void SetFinished(bool finished_);
+
+ [[nodiscard]] Service::AM::Applets::WebExitReason GetExitReason() const;
+ void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_);
+
+ [[nodiscard]] const std::string& GetLastURL() const;
+ void SetLastURL(std::string last_url_);
+
+ /**
+ * This gets the current URL that has been requested by the webpage.
+ * This only applies to the main frame. Sub frames and other resources are ignored.
+ *
+ * @return Currently requested URL
+ */
+ [[nodiscard]] QString GetCurrentURL() const;
+
+public slots:
+ void hide();
protected:
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
+
+private:
+ /**
+ * Handles button presses to execute functions assigned in yuzu_key_callbacks.
+ * yuzu_key_callbacks contains specialized functions for the buttons in the window footer
+ * that can be overriden by games to achieve desired functionality.
+ *
+ * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks
+ */
+ template <HIDButton... T>
+ void HandleWindowFooterButtonPressedOnce();
+
+ /**
+ * Handles button presses and converts them into keyboard input.
+ * This should only be used to convert D-Pad or Analog Stick input into arrow keys.
+ *
+ * @tparam HIDButton The list of buttons that can be converted into keyboard input.
+ */
+ template <HIDButton... T>
+ void HandleWindowKeyButtonPressedOnce();
+
+ /**
+ * Handles button holds and converts them into keyboard input.
+ * This should only be used to convert D-Pad or Analog Stick input into arrow keys.
+ *
+ * @tparam HIDButton The list of buttons that can be converted into keyboard input.
+ */
+ template <HIDButton... T>
+ void HandleWindowKeyButtonHold();
+
+ /**
+ * Sends a key press event to QWebEngineView.
+ *
+ * @param key Qt key code.
+ */
+ void SendKeyPressEvent(int key);
+
+ /**
+ * Sends multiple key press events to QWebEngineView.
+ *
+ * @tparam int Qt key code.
+ */
+ template <int... T>
+ void SendMultipleKeyPressEvents() {
+ (SendKeyPressEvent(T), ...);
+ }
+
+ void StartInputThread();
+ void StopInputThread();
+
+ /// The thread where input is being polled and processed.
+ void InputThread();
+
+ /// Loads the extracted fonts using JavaScript.
+ void LoadExtractedFonts();
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ std::unique_ptr<UrlRequestInterceptor> url_interceptor;
+
+ std::unique_ptr<InputInterpreter> input_interpreter;
+
+ std::thread input_thread;
+
+ std::atomic<bool> input_thread_running{};
+
+ std::atomic<bool> finished{};
+
+ Service::AM::Applets::WebExitReason exit_reason{
+ Service::AM::Applets::WebExitReason::EndButtonPressed};
+
+ std::string last_url{"http://localhost/"};
+
+ bool is_local{};
+
+ QWebEngineProfile* default_profile;
+ QWebEngineSettings* global_settings;
};
#endif
@@ -34,19 +190,28 @@ class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserAppl
Q_OBJECT
public:
- explicit QtWebBrowser(GMainWindow& main_window);
+ explicit QtWebBrowser(GMainWindow& parent);
~QtWebBrowser() override;
- void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
- std::function<void()> finished_callback) override;
+ void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback_,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)>
+ callback_) const override;
+
+ void OpenExternalWebPage(std::string_view external_url,
+ std::function<void(Service::AM::Applets::WebExitReason, std::string)>
+ callback_) const override;
signals:
- void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const;
+ void MainWindowOpenWebPage(std::string_view main_url, std::string_view additional_args,
+ bool is_local) const;
private:
- void MainWindowUnpackRomFS();
- void MainWindowFinishedBrowsing();
+ void MainWindowExtractOfflineRomFS();
+
+ void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
+ std::string last_url);
+
+ mutable std::function<void()> extract_romfs_callback;
- std::function<void()> unpack_romfs_callback;
- std::function<void()> finished_callback;
+ mutable std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback;
};
diff --git a/src/yuzu/applets/web_browser_scripts.h b/src/yuzu/applets/web_browser_scripts.h
new file mode 100644
index 000000000..992837a85
--- /dev/null
+++ b/src/yuzu/applets/web_browser_scripts.h
@@ -0,0 +1,193 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+constexpr char NX_FONT_CSS[] = R"(
+(function() {
+ css = document.createElement('style');
+ css.type = 'text/css';
+ css.id = 'nx_font';
+ css.innerText = `
+/* FontStandard */
+@font-face {
+ font-family: 'FontStandard';
+ src: url('%1') format('truetype');
+}
+
+/* FontChineseSimplified */
+@font-face {
+ font-family: 'FontChineseSimplified';
+ src: url('%2') format('truetype');
+}
+
+/* FontExtendedChineseSimplified */
+@font-face {
+ font-family: 'FontExtendedChineseSimplified';
+ src: url('%3') format('truetype');
+}
+
+/* FontChineseTraditional */
+@font-face {
+ font-family: 'FontChineseTraditional';
+ src: url('%4') format('truetype');
+}
+
+/* FontKorean */
+@font-face {
+ font-family: 'FontKorean';
+ src: url('%5') format('truetype');
+}
+
+/* FontNintendoExtended */
+@font-face {
+ font-family: 'NintendoExt003';
+ src: url('%6') format('truetype');
+}
+
+/* FontNintendoExtended2 */
+@font-face {
+ font-family: 'NintendoExt003';
+ src: url('%7') format('truetype');
+}
+`;
+
+ document.head.appendChild(css);
+})();
+)";
+
+constexpr char LOAD_NX_FONT[] = R"(
+(function() {
+ var elements = document.querySelectorAll("*");
+
+ for (var i = 0; i < elements.length; i++) {
+ var style = window.getComputedStyle(elements[i], null);
+ if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") ||
+ style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) {
+ elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003";
+ } else {
+ elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003";
+ }
+ }
+})();
+)";
+
+constexpr char GAMEPAD_SCRIPT[] = R"(
+window.addEventListener("gamepadconnected", function(e) {
+ console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
+ e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length);
+});
+
+window.addEventListener("gamepaddisconnected", function(e) {
+ console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id);
+});
+)";
+
+constexpr char WINDOW_NX_SCRIPT[] = R"(
+var end_applet = false;
+var yuzu_key_callbacks = [];
+
+(function() {
+ class WindowNX {
+ constructor() {
+ yuzu_key_callbacks[1] = function() { window.history.back(); };
+ yuzu_key_callbacks[2] = function() { window.nx.endApplet(); };
+ }
+
+ addEventListener(type, listener, options) {
+ console.log("nx.addEventListener called, type=%s", type);
+
+ window.addEventListener(type, listener, options);
+ }
+
+ endApplet() {
+ console.log("nx.endApplet called");
+
+ end_applet = true;
+ }
+
+ playSystemSe(system_se) {
+ console.log("nx.playSystemSe is not implemented, system_se=%s", system_se);
+ }
+
+ sendMessage(message) {
+ console.log("nx.sendMessage is not implemented, message=%s", message);
+ }
+
+ setCursorScrollSpeed(scroll_speed) {
+ console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed);
+ }
+ }
+
+ class WindowNXFooter {
+ setAssign(key, label, func, option) {
+ console.log("nx.footer.setAssign called, key=%s", key);
+
+ switch (key) {
+ case "A":
+ yuzu_key_callbacks[0] = func;
+ break;
+ case "B":
+ yuzu_key_callbacks[1] = func;
+ break;
+ case "X":
+ yuzu_key_callbacks[2] = func;
+ break;
+ case "Y":
+ yuzu_key_callbacks[3] = func;
+ break;
+ case "L":
+ yuzu_key_callbacks[6] = func;
+ break;
+ case "R":
+ yuzu_key_callbacks[7] = func;
+ break;
+ }
+ }
+
+ setFixed(kind) {
+ console.log("nx.footer.setFixed is not implemented, kind=%s", kind);
+ }
+
+ unsetAssign(key) {
+ console.log("nx.footer.unsetAssign called, key=%s", key);
+
+ switch (key) {
+ case "A":
+ yuzu_key_callbacks[0] = function() {};
+ break;
+ case "B":
+ yuzu_key_callbacks[1] = function() {};
+ break;
+ case "X":
+ yuzu_key_callbacks[2] = function() {};
+ break;
+ case "Y":
+ yuzu_key_callbacks[3] = function() {};
+ break;
+ case "L":
+ yuzu_key_callbacks[6] = function() {};
+ break;
+ case "R":
+ yuzu_key_callbacks[7] = function() {};
+ break;
+ }
+ }
+ }
+
+ class WindowNXPlayReport {
+ incrementCounter(counter_id) {
+ console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id);
+ }
+
+ setCounterSetIdentifier(counter_id) {
+ console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id);
+ }
+ }
+
+ window.nx = new WindowNX();
+ window.nx.footer = new WindowNXFooter();
+ window.nx.playReport = new WindowNXPlayReport();
+})();
+)";
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 32b548c56..1c61d419d 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -10,6 +10,7 @@
#include <QMessageBox>
#include <QPainter>
#include <QScreen>
+#include <QString>
#include <QStringList>
#include <QWindow>
@@ -18,7 +19,7 @@
#include <QOpenGLContext>
#endif
-#if !defined(WIN32) && HAS_VULKAN
+#if !defined(WIN32)
#include <qpa/qplatformnativeinterface.h>
#endif
@@ -30,10 +31,11 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
+#include "core/hle/kernel/process.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
-#include "input_common/motion_emu.h"
+#include "input_common/mouse/mouse_input.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#include "yuzu/bootmanager.h"
@@ -62,8 +64,9 @@ void EmuThread::run() {
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
- system.Renderer().Rasterizer().LoadDiskResources(
- stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
+ system.Renderer().ReadRasterizer()->LoadDiskResources(
+ system.CurrentProcess()->GetTitleID(), stop_run,
+ [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
@@ -123,7 +126,7 @@ public:
/// Create the original context that should be shared from
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
QSurfaceFormat format;
- format.setVersion(4, 3);
+ format.setVersion(4, 6);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
if (Settings::values.renderer_debug) {
@@ -216,15 +219,6 @@ public:
virtual ~RenderWidget() = default;
- /// Called on the UI thread when this Widget is ready to draw
- /// Dervied classes can override this to draw the latest frame.
- virtual void Present() {}
-
- void paintEvent(QPaintEvent* event) override {
- Present();
- update();
- }
-
QPaintEngine* paintEngine() const override {
return nullptr;
}
@@ -243,30 +237,16 @@ public:
context = std::move(context_);
}
- void Present() override {
- if (!isVisible()) {
- return;
- }
-
- context->MakeCurrent();
- if (Core::System::GetInstance().Renderer().TryPresent(100)) {
- context->SwapBuffers();
- glFinish();
- }
- }
-
private:
- std::unique_ptr<Core::Frontend::GraphicsContext> context{};
+ std::unique_ptr<Core::Frontend::GraphicsContext> context;
};
-#ifdef HAS_VULKAN
class VulkanRenderWidget : public RenderWidget {
public:
explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
}
};
-#endif
static Core::Frontend::WindowSystemType GetWindowSystemType() {
// Determine WSI type based on Qt platform.
@@ -286,7 +266,6 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow*
Core::Frontend::EmuWindow::WindowSystemInfo wsi;
wsi.type = GetWindowSystemType();
-#ifdef HAS_VULKAN
// Our Win32 Qt external doesn't have the private API.
#if defined(WIN32) || defined(__APPLE__)
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
@@ -299,7 +278,6 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow*
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#endif
wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
-#endif
return wsi;
}
@@ -312,21 +290,27 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
QString::fromUtf8(Common::g_scm_branch),
QString::fromUtf8(Common::g_scm_desc)));
setAttribute(Qt::WA_AcceptTouchEvents);
- auto layout = new QHBoxLayout(this);
- layout->setMargin(0);
+ auto* layout = new QHBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
input_subsystem->Initialize();
this->setMouseTracking(true);
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
+ connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram,
+ Qt::QueuedConnection);
+}
+
+void GRenderWindow::ExecuteProgram(std::size_t program_index) {
+ emit ExecuteProgramSignal(program_index);
}
GRenderWindow::~GRenderWindow() {
input_subsystem->Shutdown();
}
-void GRenderWindow::PollEvents() {
+void GRenderWindow::OnFrameDisplayed() {
if (!first_frame) {
first_frame = true;
emit FirstFrameDisplayed();
@@ -400,73 +384,116 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
- // touch input is handled in TouchBeginEvent
+ // Touch input is handled in TouchBeginEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
auto pos = event->pos();
+ const auto [x, y] = ScaleTouch(pos);
+ input_subsystem->GetMouse()->PressButton(x, y, event->button());
+
if (event->button() == Qt::LeftButton) {
- const auto [x, y] = ScaleTouch(pos);
- this->TouchPressed(x, y);
- } else if (event->button() == Qt::RightButton) {
- input_subsystem->GetMotionEmu()->BeginTilt(pos.x(), pos.y());
+ this->TouchPressed(x, y, 0);
}
- QWidget::mousePressEvent(event);
+
+ emit MouseActivity();
}
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
- // touch input is handled in TouchUpdateEvent
+ // Touch input is handled in TouchUpdateEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
-
auto pos = event->pos();
const auto [x, y] = ScaleTouch(pos);
- this->TouchMoved(x, y);
- input_subsystem->GetMotionEmu()->Tilt(pos.x(), pos.y());
- QWidget::mouseMoveEvent(event);
+ const int center_x = width() / 2;
+ const int center_y = height() / 2;
+ input_subsystem->GetMouse()->MouseMove(x, y, center_x, center_y);
+ this->TouchMoved(x, y, 0);
+
+ if (Settings::values.mouse_panning) {
+ QCursor::setPos(mapToGlobal({center_x, center_y}));
+ }
+
+ emit MouseActivity();
}
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
- // touch input is handled in TouchEndEvent
+ // Touch input is handled in TouchEndEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
+ input_subsystem->GetMouse()->ReleaseButton(event->button());
+
if (event->button() == Qt::LeftButton) {
- this->TouchReleased();
- } else if (event->button() == Qt::RightButton) {
- input_subsystem->GetMotionEmu()->EndTilt();
+ this->TouchReleased(0);
}
}
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
- // TouchBegin always has exactly one touch point, so take the .first()
- const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
- this->TouchPressed(x, y);
+ QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints();
+ for (const auto& touch_point : touch_points) {
+ if (!TouchUpdate(touch_point)) {
+ TouchStart(touch_point);
+ }
+ }
}
void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
- QPointF pos;
- int active_points = 0;
-
- // average all active touch points
- for (const auto tp : event->touchPoints()) {
- if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
- active_points++;
- pos += tp.pos();
+ QList<QTouchEvent::TouchPoint> touch_points = event->touchPoints();
+ for (const auto& touch_point : touch_points) {
+ if (!TouchUpdate(touch_point)) {
+ TouchStart(touch_point);
}
}
+ // Release all inactive points
+ for (std::size_t id = 0; id < touch_ids.size(); ++id) {
+ if (!TouchExist(touch_ids[id], touch_points)) {
+ touch_ids[id] = 0;
+ this->TouchReleased(id + 1);
+ }
+ }
+}
- pos /= active_points;
+void GRenderWindow::TouchEndEvent() {
+ for (std::size_t id = 0; id < touch_ids.size(); ++id) {
+ if (touch_ids[id] != 0) {
+ touch_ids[id] = 0;
+ this->TouchReleased(id + 1);
+ }
+ }
+}
- const auto [x, y] = ScaleTouch(pos);
- this->TouchMoved(x, y);
+bool GRenderWindow::TouchStart(const QTouchEvent::TouchPoint& touch_point) {
+ for (std::size_t id = 0; id < touch_ids.size(); ++id) {
+ if (touch_ids[id] == 0) {
+ touch_ids[id] = touch_point.id() + 1;
+ const auto [x, y] = ScaleTouch(touch_point.pos());
+ this->TouchPressed(x, y, id + 1);
+ return true;
+ }
+ }
+ return false;
}
-void GRenderWindow::TouchEndEvent() {
- this->TouchReleased();
+bool GRenderWindow::TouchUpdate(const QTouchEvent::TouchPoint& touch_point) {
+ for (std::size_t id = 0; id < touch_ids.size(); ++id) {
+ if (touch_ids[id] == static_cast<std::size_t>(touch_point.id() + 1)) {
+ const auto [x, y] = ScaleTouch(touch_point.pos());
+ this->TouchMoved(x, y, id + 1);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool GRenderWindow::TouchExist(std::size_t id,
+ const QList<QTouchEvent::TouchPoint>& touch_points) const {
+ return std::any_of(touch_points.begin(), touch_points.end(), [id](const auto& point) {
+ return id == static_cast<std::size_t>(point.id() + 1);
+ });
}
bool GRenderWindow::event(QEvent* event) {
@@ -579,6 +606,10 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p
layout);
}
+bool GRenderWindow::IsLoadingComplete() const {
+ return first_frame;
+}
+
void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
setMinimumSize(minimal_size.first, minimal_size.second);
}
@@ -604,37 +635,45 @@ bool GRenderWindow::InitializeOpenGL() {
}
bool GRenderWindow::InitializeVulkan() {
-#ifdef HAS_VULKAN
auto child = new VulkanRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
main_context = std::make_unique<DummyContext>();
return true;
-#else
- QMessageBox::critical(this, tr("Vulkan not available!"),
- tr("yuzu has not been compiled with Vulkan support."));
- return false;
-#endif
}
bool GRenderWindow::LoadOpenGL() {
auto context = CreateSharedContext();
auto scope = context->Acquire();
if (!gladLoadGL()) {
- QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
- tr("Your GPU may not support OpenGL 4.3, or you do not have the "
- "latest graphics driver."));
+ QMessageBox::warning(
+ this, tr("Error while initializing OpenGL!"),
+ tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver."));
+ return false;
+ }
+
+ const QString renderer =
+ QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
+
+ if (!GLAD_GL_VERSION_4_6) {
+ LOG_ERROR(Frontend, "GPU does not support OpenGL 4.6: {}", renderer.toStdString());
+ QMessageBox::warning(this, tr("Error while initializing OpenGL 4.6!"),
+ tr("Your GPU may not support OpenGL 4.6, or you do not have the "
+ "latest graphics driver.<br><br>GL Renderer:<br>%1")
+ .arg(renderer));
return false;
}
QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
if (!unsupported_gl_extensions.empty()) {
- QMessageBox::critical(
+ QMessageBox::warning(
this, tr("Error while initializing OpenGL!"),
tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
- "have the latest graphics driver.<br><br>Unsupported extensions:<br>") +
- unsupported_gl_extensions.join(QStringLiteral("<br>")));
+ "have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported "
+ "extensions:<br>%2")
+ .arg(renderer)
+ .arg(unsupported_gl_extensions.join(QStringLiteral("<br>"))));
return false;
}
return true;
@@ -643,29 +682,21 @@ bool GRenderWindow::LoadOpenGL() {
QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
QStringList unsupported_ext;
- if (!GLAD_GL_ARB_buffer_storage)
- unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
- if (!GLAD_GL_ARB_direct_state_access)
- unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
- if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
- unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
- if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
- unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
- if (!GLAD_GL_ARB_multi_bind)
- unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
- if (!GLAD_GL_ARB_clip_control)
- unsupported_ext.append(QStringLiteral("ARB_clip_control"));
-
// Extensions required to support some texture formats.
- if (!GLAD_GL_EXT_texture_compression_s3tc)
+ if (!GLAD_GL_EXT_texture_compression_s3tc) {
unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
- if (!GLAD_GL_ARB_texture_compression_rgtc)
+ }
+ if (!GLAD_GL_ARB_texture_compression_rgtc) {
unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
- if (!GLAD_GL_ARB_depth_buffer_float)
- unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
+ }
- for (const QString& ext : unsupported_ext)
- LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
+ if (!unsupported_ext.empty()) {
+ LOG_ERROR(Frontend, "GPU does not support all required extensions: {}",
+ glGetString(GL_RENDERER));
+ }
+ for (const QString& ext : unsupported_ext) {
+ LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString());
+ }
return unsupported_ext;
}
@@ -685,3 +716,15 @@ void GRenderWindow::showEvent(QShowEvent* event) {
connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged,
Qt::UniqueConnection);
}
+
+bool GRenderWindow::eventFilter(QObject* object, QEvent* event) {
+ if (event->type() == QEvent::HoverMove) {
+ if (Settings::values.mouse_panning) {
+ auto* hover_event = static_cast<QMouseEvent*>(event);
+ mouseMoveEvent(hover_event);
+ return false;
+ }
+ emit MouseActivity();
+ }
+ return false;
+}
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index ca35cf831..b5ec7de07 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -11,6 +11,7 @@
#include <QImage>
#include <QThread>
+#include <QTouchEvent>
#include <QWidget>
#include <QWindow>
@@ -21,7 +22,6 @@
class GRenderWindow;
class GMainWindow;
class QKeyEvent;
-class QTouchEvent;
class QStringList;
namespace InputCommon {
@@ -131,7 +131,7 @@ public:
~GRenderWindow() override;
// EmuWindow implementation.
- void PollEvents() override;
+ void OnFrameDisplayed() override;
bool IsShown() const override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
@@ -162,10 +162,18 @@ public:
/// Destroy the previous run's child_widget which should also destroy the child_window
void ReleaseRenderTarget();
+ bool IsLoadingComplete() const;
+
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
+ /**
+ * Instructs the window to re-launch the application using the specified program_index.
+ * @param program_index Specifies the index within the application of the program to launch.
+ */
+ void ExecuteProgram(std::size_t program_index);
+
public slots:
void OnEmulationStarting(EmuThread* emu_thread);
void OnEmulationStopping();
@@ -175,12 +183,18 @@ signals:
/// Emitted when the window is closed
void Closed();
void FirstFrameDisplayed();
+ void ExecuteProgramSignal(std::size_t program_index);
+ void MouseActivity();
private:
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
+ bool TouchStart(const QTouchEvent::TouchPoint& touch_point);
+ bool TouchUpdate(const QTouchEvent::TouchPoint& touch_point);
+ bool TouchExist(std::size_t id, const QList<QTouchEvent::TouchPoint>& touch_points) const;
+
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
bool InitializeOpenGL();
@@ -205,6 +219,9 @@ private:
bool first_frame = false;
+ std::array<std::size_t, 16> touch_ids{};
+
protected:
void showEvent(QShowEvent* event) override;
+ bool eventFilter(QObject* object, QEvent* event) override;
};
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index 649912557..a470056ef 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -72,7 +72,7 @@ void CompatDB::Submit() {
void CompatDB::OnTestcaseSubmitted() {
if (!testcase_watcher.result()) {
QMessageBox::critical(this, tr("Communication error"),
- tr("An error occured while sending the Testcase"));
+ tr("An error occurred while sending the Testcase"));
button(NextButton)->setEnabled(true);
button(NextButton)->setText(tr("Next"));
button(CancelButton)->setVisible(true);
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 588bbd677..0635d13d0 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -5,7 +5,9 @@
#include <array>
#include <QKeySequence>
#include <QSettings>
+#include "common/common_paths.h"
#include "common/file_util.h"
+#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
@@ -14,14 +16,10 @@
namespace FS = Common::FS;
-Config::Config(const std::string& config_file, bool is_global) {
- // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file;
- FS::CreateFullPath(qt_config_loc);
- qt_config =
- std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
- global = is_global;
- Reload();
+Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
+ global = config_type == ConfigType::GlobalConfig;
+
+ Initialize(config_name);
}
Config::~Config() {
@@ -36,6 +34,11 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button
Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V,
};
+const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = {
+ Qt::Key_7,
+ Qt::Key_8,
+};
+
const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
{
Qt::Key_Up,
@@ -217,7 +220,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
-const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
+const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@@ -232,73 +235,158 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
+ {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
}};
// clang-format on
-void Config::ReadPlayerValues() {
- for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
- auto& player = Settings::values.players[p];
+void Config::Initialize(const std::string& config_name) {
+ switch (type) {
+ case ConfigType::GlobalConfig:
+ qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir),
+ config_name);
+ FS::CreateFullPath(qt_config_loc);
+ qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+ QSettings::IniFormat);
+ Reload();
+ break;
+ case ConfigType::PerGameConfig:
+ qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini",
+ FS::GetUserPath(FS::UserPath::ConfigDir), config_name);
+ FS::CreateFullPath(qt_config_loc);
+ qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+ QSettings::IniFormat);
+ Reload();
+ break;
+ case ConfigType::InputProfile:
+ qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini",
+ FS::GetUserPath(FS::UserPath::ConfigDir), config_name);
+ FS::CreateFullPath(qt_config_loc);
+ qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+ QSettings::IniFormat);
+ break;
+ }
+}
+
+void Config::ReadPlayerValue(std::size_t player_index) {
+ const QString player_prefix = [this, player_index] {
+ if (type == ConfigType::InputProfile) {
+ return QString{};
+ } else {
+ return QStringLiteral("player_%1_").arg(player_index);
+ }
+ }();
+
+ auto& player = Settings::values.players.GetValue()[player_index];
+
+ if (player_prefix.isEmpty()) {
+ const auto controller = static_cast<Settings::ControllerType>(
+ qt_config
+ ->value(QStringLiteral("%1type").arg(player_prefix),
+ static_cast<u8>(Settings::ControllerType::ProController))
+ .toUInt());
+ if (controller == Settings::ControllerType::LeftJoycon ||
+ controller == Settings::ControllerType::RightJoycon) {
+ player.controller_type = controller;
+ }
+ } else {
player.connected =
- ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool();
+ ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0)
+ .toBool();
player.controller_type = static_cast<Settings::ControllerType>(
qt_config
- ->value(QStringLiteral("player_%1_type").arg(p),
+ ->value(QStringLiteral("%1type").arg(player_prefix),
static_cast<u8>(Settings::ControllerType::ProController))
.toUInt());
+ player.vibration_enabled =
+ qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true)
+ .toBool();
+
+ player.vibration_strength =
+ qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100)
+ .toInt();
+
player.body_color_left = qt_config
- ->value(QStringLiteral("player_%1_body_color_left").arg(p),
+ ->value(QStringLiteral("%1body_color_left").arg(player_prefix),
Settings::JOYCON_BODY_NEON_BLUE)
.toUInt();
- player.body_color_right = qt_config
- ->value(QStringLiteral("player_%1_body_color_right").arg(p),
- Settings::JOYCON_BODY_NEON_RED)
- .toUInt();
- player.button_color_left = qt_config
- ->value(QStringLiteral("player_%1_button_color_left").arg(p),
- Settings::JOYCON_BUTTONS_NEON_BLUE)
- .toUInt();
+ player.body_color_right =
+ qt_config
+ ->value(QStringLiteral("%1body_color_right").arg(player_prefix),
+ Settings::JOYCON_BODY_NEON_RED)
+ .toUInt();
+ player.button_color_left =
+ qt_config
+ ->value(QStringLiteral("%1button_color_left").arg(player_prefix),
+ Settings::JOYCON_BUTTONS_NEON_BLUE)
+ .toUInt();
player.button_color_right =
qt_config
- ->value(QStringLiteral("player_%1_button_color_right").arg(p),
+ ->value(QStringLiteral("%1button_color_right").arg(player_prefix),
Settings::JOYCON_BUTTONS_NEON_RED)
.toUInt();
+ }
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- const std::string default_param =
- InputCommon::GenerateKeyboardParam(default_buttons[i]);
- auto& player_buttons = player.buttons[i];
-
- player_buttons = qt_config
- ->value(QStringLiteral("player_%1_").arg(p) +
- QString::fromUtf8(Settings::NativeButton::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player_buttons.empty()) {
- player_buttons = default_param;
- }
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ auto& player_buttons = player.buttons[i];
+
+ player_buttons = qt_config
+ ->value(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromUtf8(Settings::NativeButton::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (player_buttons.empty()) {
+ player_buttons = default_param;
+ }
+ }
+
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ auto& player_analogs = player.analogs[i];
+
+ player_analogs = qt_config
+ ->value(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (player_analogs.empty()) {
+ player_analogs = default_param;
}
+ }
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_stick_mod[i], 0.5f);
- auto& player_analogs = player.analogs[i];
-
- player_analogs = qt_config
- ->value(QStringLiteral("player_%1_").arg(p) +
- QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(default_param))
- .toString()
- .toStdString();
- if (player_analogs.empty()) {
- player_analogs = default_param;
- }
+ for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) {
+ auto& player_vibrations = player.vibrations[i];
+
+ player_vibrations =
+ qt_config
+ ->value(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromUtf8(Settings::NativeVibration::mapping[i]),
+ QString{})
+ .toString()
+ .toStdString();
+ }
+
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+ auto& player_motions = player.motions[i];
+
+ player_motions = qt_config
+ ->value(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromUtf8(Settings::NativeMotion::mapping[i]),
+ QString::fromStdString(default_param))
+ .toString()
+ .toStdString();
+ if (player_motions.empty()) {
+ player_motions = default_param;
}
}
}
@@ -377,13 +465,7 @@ void Config::ReadMouseValues() {
void Config::ReadTouchscreenValues() {
Settings::values.touchscreen.enabled =
ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool();
- Settings::values.touchscreen.device =
- ReadSetting(QStringLiteral("touchscreen_device"), QStringLiteral("engine:emu_window"))
- .toString()
- .toStdString();
- Settings::values.touchscreen.finger =
- ReadSetting(QStringLiteral("touchscreen_finger"), 0).toUInt();
Settings::values.touchscreen.rotation_angle =
ReadSetting(QStringLiteral("touchscreen_angle"), 0).toUInt();
Settings::values.touchscreen.diameter_x =
@@ -415,39 +497,91 @@ void Config::ReadAudioValues() {
void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
- ReadPlayerValues();
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ ReadPlayerValue(p);
+ }
ReadDebugValues();
ReadKeyboardValues();
ReadMouseValues();
ReadTouchscreenValues();
+ ReadMotionTouchValues();
+
+ Settings::values.emulate_analog_keyboard =
+ ReadSetting(QStringLiteral("emulate_analog_keyboard"), false).toBool();
+ Settings::values.mouse_panning = ReadSetting(QStringLiteral("mouse_panning"), false).toBool();
+ Settings::values.mouse_panning_sensitivity =
+ ReadSetting(QStringLiteral("mouse_panning_sensitivity"), 1).toFloat();
+
+ ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), true);
+ ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"),
+ true);
+ ReadSettingGlobal(Settings::values.enable_accurate_vibrations,
+ QStringLiteral("enable_accurate_vibrations"), false);
+ ReadSettingGlobal(Settings::values.motion_enabled, QStringLiteral("motion_enabled"), true);
+
+ qt_config->endGroup();
+}
+
+void Config::ReadMotionTouchValues() {
+ int num_touch_from_button_maps =
+ qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
+
+ if (num_touch_from_button_maps > 0) {
+ const auto append_touch_from_button_map = [this] {
+ Settings::TouchFromButtonMap map;
+ map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default"))
+ .toString()
+ .toStdString();
+ const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries"));
+ map.buttons.reserve(num_touch_maps);
+ for (int i = 0; i < num_touch_maps; i++) {
+ qt_config->setArrayIndex(i);
+ std::string touch_mapping =
+ ReadSetting(QStringLiteral("bind")).toString().toStdString();
+ map.buttons.emplace_back(std::move(touch_mapping));
+ }
+ qt_config->endArray(); // entries
+ Settings::values.touch_from_button_maps.emplace_back(std::move(map));
+ };
+
+ for (int i = 0; i < num_touch_from_button_maps; ++i) {
+ qt_config->setArrayIndex(i);
+ append_touch_from_button_map();
+ }
+ } else {
+ Settings::values.touch_from_button_maps.emplace_back(
+ Settings::TouchFromButtonMap{"default", {}});
+ num_touch_from_button_maps = 1;
+ }
+ qt_config->endArray();
- Settings::values.vibration_enabled =
- ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
Settings::values.motion_device =
ReadSetting(QStringLiteral("motion_device"),
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
.toString()
.toStdString();
- Settings::values.udp_input_address =
- ReadSetting(QStringLiteral("udp_input_address"),
- QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
+ Settings::values.touch_device =
+ ReadSetting(QStringLiteral("touch_device"),
+ QStringLiteral("min_x:100,min_y:50,max_x:1800,max_y:850"))
+ .toString()
+ .toStdString();
+ Settings::values.use_touch_from_button =
+ ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool();
+ Settings::values.touch_from_button_map_index =
+ ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt();
+ Settings::values.touch_from_button_map_index =
+ std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1);
+ Settings::values.udp_input_servers =
+ ReadSetting(QStringLiteral("udp_input_servers"),
+ QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_SRV))
.toString()
.toStdString();
- Settings::values.udp_input_port = static_cast<u16>(
- ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT)
- .toInt());
- Settings::values.udp_pad_index =
- static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
- Settings::values.use_docked_mode =
- ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
-
- qt_config->endGroup();
}
void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), false);
+ ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), true);
qt_config->endGroup();
}
@@ -502,8 +636,6 @@ void Config::ReadDebuggingValues() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
Settings::values.record_frame_times =
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
- Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
- Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
Settings::values.program_args =
ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString();
Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool();
@@ -513,6 +645,8 @@ void Config::ReadDebuggingValues() {
Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool();
Settings::values.disable_macro_jit =
ReadSetting(QStringLiteral("disable_macro_jit"), false).toBool();
+ Settings::values.extended_logging =
+ ReadSetting(QStringLiteral("extended_logging"), false).toBool();
qt_config->endGroup();
}
@@ -629,6 +763,8 @@ void Config::ReadCpuValues() {
ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool();
Settings::values.cpuopt_unsafe_reduce_fp_error =
ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool();
+ Settings::values.cpuopt_unsafe_inaccurate_nan =
+ ReadSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true).toBool();
}
qt_config->endGroup();
@@ -646,9 +782,11 @@ void Config::ReadRendererValues() {
ReadSettingGlobal(Settings::values.frame_limit, QStringLiteral("frame_limit"), 100);
ReadSettingGlobal(Settings::values.use_disk_shader_cache,
QStringLiteral("use_disk_shader_cache"), true);
- ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 0);
+ ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 1);
ReadSettingGlobal(Settings::values.use_asynchronous_gpu_emulation,
- QStringLiteral("use_asynchronous_gpu_emulation"), false);
+ QStringLiteral("use_asynchronous_gpu_emulation"), true);
+ ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"),
+ true);
ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true);
ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"),
false);
@@ -850,41 +988,65 @@ void Config::ReadValues() {
ReadSystemValues();
}
-void Config::SavePlayerValues() {
- for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
- const auto& player = Settings::values.players[p];
-
- WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false);
- WriteSetting(QStringLiteral("player_%1_type").arg(p),
- static_cast<u8>(player.controller_type),
- static_cast<u8>(Settings::ControllerType::ProController));
-
- WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left,
+void Config::SavePlayerValue(std::size_t player_index) {
+ const QString player_prefix = [this, player_index] {
+ if (type == ConfigType::InputProfile) {
+ return QString{};
+ } else {
+ return QStringLiteral("player_%1_").arg(player_index);
+ }
+ }();
+
+ const auto& player = Settings::values.players.GetValue()[player_index];
+
+ WriteSetting(QStringLiteral("%1type").arg(player_prefix),
+ static_cast<u8>(player.controller_type),
+ static_cast<u8>(Settings::ControllerType::ProController));
+
+ if (!player_prefix.isEmpty()) {
+ WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected,
+ player_index == 0);
+ WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix),
+ player.vibration_enabled, true);
+ WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix),
+ player.vibration_strength, 100);
+ WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left,
Settings::JOYCON_BODY_NEON_BLUE);
- WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right,
- Settings::JOYCON_BODY_NEON_RED);
- WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left,
- Settings::JOYCON_BUTTONS_NEON_BLUE);
- WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p),
+ WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix),
+ player.body_color_right, Settings::JOYCON_BODY_NEON_RED);
+ WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix),
+ player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE);
+ WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix),
player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
+ }
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- const std::string default_param =
- InputCommon::GenerateKeyboardParam(default_buttons[i]);
- WriteSetting(QStringLiteral("player_%1_").arg(p) +
- QString::fromStdString(Settings::NativeButton::mapping[i]),
- QString::fromStdString(player.buttons[i]),
- QString::fromStdString(default_param));
- }
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
- default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
- default_analogs[i][3], default_stick_mod[i], 0.5f);
- WriteSetting(QStringLiteral("player_%1_").arg(p) +
- QString::fromStdString(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(player.analogs[i]),
- QString::fromStdString(default_param));
- }
+ for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromStdString(Settings::NativeButton::mapping[i]),
+ QString::fromStdString(player.buttons[i]),
+ QString::fromStdString(default_param));
+ }
+ for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+ const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_stick_mod[i], 0.5f);
+ WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromStdString(Settings::NativeAnalog::mapping[i]),
+ QString::fromStdString(player.analogs[i]),
+ QString::fromStdString(default_param));
+ }
+ for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) {
+ WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromStdString(Settings::NativeVibration::mapping[i]),
+ QString::fromStdString(player.vibrations[i]), QString{});
+ }
+ for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+ const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+ WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+ QString::fromStdString(Settings::NativeMotion::mapping[i]),
+ QString::fromStdString(player.motions[i]),
+ QString::fromStdString(default_param));
}
}
@@ -925,15 +1087,46 @@ void Config::SaveTouchscreenValues() {
const auto& touchscreen = Settings::values.touchscreen;
WriteSetting(QStringLiteral("touchscreen_enabled"), touchscreen.enabled, true);
- WriteSetting(QStringLiteral("touchscreen_device"), QString::fromStdString(touchscreen.device),
- QStringLiteral("engine:emu_window"));
- WriteSetting(QStringLiteral("touchscreen_finger"), touchscreen.finger, 0);
WriteSetting(QStringLiteral("touchscreen_angle"), touchscreen.rotation_angle, 0);
WriteSetting(QStringLiteral("touchscreen_diameter_x"), touchscreen.diameter_x, 15);
WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
}
+void Config::SaveMotionTouchValues() {
+ WriteSetting(QStringLiteral("motion_device"),
+ QString::fromStdString(Settings::values.motion_device),
+ QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
+ WriteSetting(QStringLiteral("touch_device"),
+ QString::fromStdString(Settings::values.touch_device),
+ QStringLiteral("engine:emu_window"));
+ WriteSetting(QStringLiteral("use_touch_from_button"), Settings::values.use_touch_from_button,
+ false);
+ WriteSetting(QStringLiteral("touch_from_button_map"),
+ Settings::values.touch_from_button_map_index, 0);
+ WriteSetting(QStringLiteral("udp_input_servers"),
+ QString::fromStdString(Settings::values.udp_input_servers),
+ QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_SRV));
+
+ qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
+ for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
+ qt_config->setArrayIndex(static_cast<int>(p));
+ WriteSetting(QStringLiteral("name"),
+ QString::fromStdString(Settings::values.touch_from_button_maps[p].name),
+ QStringLiteral("default"));
+ qt_config->beginWriteArray(QStringLiteral("entries"));
+ for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
+ ++q) {
+ qt_config->setArrayIndex(static_cast<int>(q));
+ WriteSetting(
+ QStringLiteral("bind"),
+ QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q]));
+ }
+ qt_config->endArray();
+ }
+ qt_config->endArray();
+}
+
void Config::SaveValues() {
if (global) {
SaveControlValues();
@@ -972,31 +1165,39 @@ void Config::SaveAudioValues() {
void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
- SavePlayerValues();
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+ SavePlayerValue(p);
+ }
SaveDebugValues();
SaveMouseValues();
SaveTouchscreenValues();
+ SaveMotionTouchValues();
- WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
+ WriteSettingGlobal(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, true);
+ WriteSettingGlobal(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled,
+ true);
+ WriteSettingGlobal(QStringLiteral("enable_accurate_vibrations"),
+ Settings::values.enable_accurate_vibrations, false);
+ WriteSettingGlobal(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);
WriteSetting(QStringLiteral("motion_device"),
QString::fromStdString(Settings::values.motion_device),
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
+ WriteSetting(QStringLiteral("touch_device"),
+ QString::fromStdString(Settings::values.touch_device),
+ QStringLiteral("engine:emu_window"));
WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
- WriteSetting(QStringLiteral("udp_input_address"),
- QString::fromStdString(Settings::values.udp_input_address),
- QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
- WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
- InputCommon::CemuhookUDP::DEFAULT_PORT);
- WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
- WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
-
+ WriteSetting(QStringLiteral("emulate_analog_keyboard"),
+ Settings::values.emulate_analog_keyboard, false);
+ WriteSetting(QStringLiteral("mouse_panning"), Settings::values.mouse_panning, false);
+ WriteSetting(QStringLiteral("mouse_panning_sensitivity"),
+ Settings::values.mouse_panning_sensitivity, 1.0f);
qt_config->endGroup();
}
void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
- WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false);
+ WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, true);
qt_config->endGroup();
}
@@ -1034,8 +1235,6 @@ void Config::SaveDebuggingValues() {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
- WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
- WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
WriteSetting(QStringLiteral("program_args"),
QString::fromStdString(Settings::values.program_args), QString{});
WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false);
@@ -1129,6 +1328,8 @@ void Config::SaveCpuValues() {
Settings::values.cpuopt_unsafe_unfuse_fma, true);
WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"),
Settings::values.cpuopt_unsafe_reduce_fp_error, true);
+ WriteSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"),
+ Settings::values.cpuopt_unsafe_inaccurate_nan, true);
}
qt_config->endGroup();
@@ -1150,9 +1351,11 @@ void Config::SaveRendererValues() {
Settings::values.use_disk_shader_cache, true);
WriteSettingGlobal(QStringLiteral("gpu_accuracy"),
static_cast<int>(Settings::values.gpu_accuracy.GetValue(global)),
- Settings::values.gpu_accuracy.UsingGlobal(), 0);
+ Settings::values.gpu_accuracy.UsingGlobal(), 1);
WriteSettingGlobal(QStringLiteral("use_asynchronous_gpu_emulation"),
- Settings::values.use_asynchronous_gpu_emulation, false);
+ Settings::values.use_asynchronous_gpu_emulation, true);
+ WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation,
+ true);
WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true);
WriteSettingGlobal(QStringLiteral("use_assembly_shaders"),
Settings::values.use_assembly_shaders, false);
@@ -1389,13 +1592,27 @@ void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool
void Config::Reload() {
ReadValues();
- Settings::Sanitize();
// To apply default value changes
SaveValues();
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
}
void Config::Save() {
- Settings::Sanitize();
SaveValues();
}
+
+void Config::ReadControlPlayerValue(std::size_t player_index) {
+ qt_config->beginGroup(QStringLiteral("Controls"));
+ ReadPlayerValue(player_index);
+ qt_config->endGroup();
+}
+
+void Config::SaveControlPlayerValue(std::size_t player_index) {
+ qt_config->beginGroup(QStringLiteral("Controls"));
+ SavePlayerValue(player_index);
+ qt_config->endGroup();
+}
+
+const std::string& Config::GetConfigFilePath() const {
+ return qt_config_loc;
+}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index aa929d134..949c4eb13 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -16,28 +16,44 @@ class QSettings;
class Config {
public:
- explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true);
+ enum class ConfigType {
+ GlobalConfig,
+ PerGameConfig,
+ InputProfile,
+ };
+
+ explicit Config(const std::string& config_name = "qt-config",
+ ConfigType config_type = ConfigType::GlobalConfig);
~Config();
void Reload();
void Save();
+ void ReadControlPlayerValue(std::size_t player_index);
+ void SaveControlPlayerValue(std::size_t player_index);
+
+ const std::string& GetConfigFilePath() const;
+
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
+ static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<int, 2> default_stick_mod;
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
- static const std::array<UISettings::Shortcut, 16> default_hotkeys;
+ static const std::array<UISettings::Shortcut, 17> default_hotkeys;
private:
+ void Initialize(const std::string& config_name);
+
void ReadValues();
- void ReadPlayerValues();
+ void ReadPlayerValue(std::size_t player_index);
void ReadDebugValues();
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
+ void ReadMotionTouchValues();
// Read functions bases off the respective config section names.
void ReadAudioValues();
@@ -60,10 +76,11 @@ private:
void ReadWebServiceValues();
void SaveValues();
- void SavePlayerValues();
+ void SavePlayerValue(std::size_t player_index);
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
+ void SaveMotionTouchValues();
// Save functions based off the respective config section names.
void SaveAudioValues();
@@ -108,9 +125,9 @@ private:
void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
const QVariant& default_value);
+ ConfigType type;
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
-
bool global;
};
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index fcf42cdcb..f92c3aff3 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -275,32 +275,12 @@
<signal>accepted()</signal>
<receiver>ConfigureDialog</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>220</x>
- <y>380</y>
- </hint>
- <hint type="destinationlabel">
- <x>220</x>
- <y>200</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureDialog</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>220</x>
- <y>380</y>
- </hint>
- <hint type="destinationlabel">
- <x>220</x>
- <y>200</y>
- </hint>
- </hints>
</connection>
</connections>
</ui>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index fa9124ecf..db9518798 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -25,8 +25,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureAudio::UpdateAudioDevices);
- ui->volume_label->setVisible(Settings::configuring_global);
- ui->volume_combo_box->setVisible(!Settings::configuring_global);
+ ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
+ ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal());
SetupPerGameUI();
@@ -51,7 +51,7 @@ void ConfigureAudio::SetConfiguration() {
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue());
- if (!Settings::configuring_global) {
+ if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.volume.UsingGlobal()) {
ui->volume_combo_box->setCurrentIndex(0);
ui->volume_slider->setEnabled(false);
@@ -99,7 +99,7 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
}
void ConfigureAudio::ApplyConfiguration() {
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
Settings::values.sink_id =
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
.toStdString();
@@ -165,7 +165,7 @@ void ConfigureAudio::RetranslateUI() {
}
void ConfigureAudio::SetupPerGameUI() {
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
ui->toggle_audio_stretching->setEnabled(
Settings::values.enable_audio_stretching.UsingGlobal());
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index 37fcd6adc..d055cbd60 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -36,6 +36,8 @@ void ConfigureCpu::SetConfiguration() {
ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma);
ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error);
+ ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock);
+ ui->cpuopt_unsafe_inaccurate_nan->setChecked(Settings::values.cpuopt_unsafe_inaccurate_nan);
}
void ConfigureCpu::AccuracyUpdated(int index) {
@@ -61,6 +63,7 @@ void ConfigureCpu::ApplyConfiguration() {
static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex());
Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked();
Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked();
+ Settings::values.cpuopt_unsafe_inaccurate_nan = ui->cpuopt_unsafe_inaccurate_nan->isChecked();
}
void ConfigureCpu::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index ebdd2e6e9..bcd0962e9 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -109,6 +109,18 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan">
+ <property name="text">
+ <string>Inaccurate NaN handling</string>
+ </property>
+ <property name="toolTip">
+ <string>
+ &lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
+ </string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 2bfe2c306..121873f95 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -28,9 +28,6 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co
ConfigureDebug::~ConfigureDebug() = default;
void ConfigureDebug::SetConfiguration() {
- ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub);
- ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub);
- ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port);
ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->toggle_console->setChecked(UISettings::values.show_console);
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
@@ -41,11 +38,10 @@ void ConfigureDebug::SetConfiguration() {
ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug);
ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit);
+ ui->extended_logging->setChecked(Settings::values.extended_logging);
}
void ConfigureDebug::ApplyConfiguration() {
- Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();
- Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
UISettings::values.show_console = ui->toggle_console->isChecked();
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
@@ -53,6 +49,7 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.quest_flag = ui->quest_flag->isChecked();
Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
+ Settings::values.extended_logging = ui->extended_logging->isChecked();
Debugger::ToggleConsole();
Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter);
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 9d6feb9f7..9186aa732 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>400</width>
- <height>467</height>
+ <height>486</height>
</rect>
</property>
<property name="windowTitle">
@@ -15,57 +15,6 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>GDB</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_1">
- <item>
- <widget class="QCheckBox" name="toggle_gdbstub">
- <property name="text">
- <string>Enable GDB Stub</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="label_1">
- <property name="text">
- <string>Port:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QSpinBox" name="gdbport_spinbox">
- <property name="maximum">
- <number>65536</number>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </item>
- <item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Logging</string>
@@ -90,7 +39,7 @@
<item>
<widget class="QCheckBox" name="toggle_console">
<property name="text">
- <string>Show Log Console (Windows Only)</string>
+ <string>Show Log in Console</string>
</property>
</widget>
</item>
@@ -103,6 +52,34 @@
</item>
</layout>
</item>
+ <item>
+ <widget class="QCheckBox" name="extended_logging">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
+ </property>
+ <property name="text">
+ <string>Enable Extended Logging</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>This will be reset automatically when yuzu closes.</string>
+ </property>
+ <property name="indent">
+ <number>20</number>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -115,7 +92,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
- <widget class="QLabel" name="label_3">
+ <widget class="QLabel" name="label_4">
<property name="text">
<string>Arguments String</string>
</property>
@@ -140,8 +117,8 @@
<property name="enabled">
<bool>true</bool>
</property>
- <property name="whatsThis">
- <string>When checked, the graphics API enters in a slower debugging mode</string>
+ <property name="toolTip">
+ <string>When checked, the graphics API enters a slower debugging mode</string>
</property>
<property name="text">
<string>Enable Graphics Debugging</string>
@@ -153,8 +130,8 @@
<property name="enabled">
<bool>true</bool>
</property>
- <property name="whatsThis">
- <string>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</string>
+ <property name="toolTip">
+ <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string>
</property>
<property name="text">
<string>Disable Macro JIT</string>
@@ -169,7 +146,7 @@
<property name="title">
<string>Dump</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
+ <layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QCheckBox" name="reporting_services">
<property name="text">
@@ -178,7 +155,7 @@
</widget>
</item>
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="label_5">
<property name="font">
<font>
<italic>true</italic>
@@ -200,7 +177,7 @@
<property name="title">
<string>Advanced</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_7">
+ <layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QCheckBox" name="quest_flag">
<property name="text">
@@ -230,8 +207,6 @@
</layout>
</widget>
<tabstops>
- <tabstop>toggle_gdbstub</tabstop>
- <tabstop>gdbport_spinbox</tabstop>
<tabstop>log_filter_edit</tabstop>
<tabstop>toggle_console</tabstop>
<tabstop>open_log_button</tabstop>
@@ -241,22 +216,5 @@
<tabstop>quest_flag</tabstop>
</tabstops>
<resources/>
- <connections>
- <connection>
- <sender>toggle_gdbstub</sender>
- <signal>toggled(bool)</signal>
- <receiver>gdbport_spinbox</receiver>
- <slot>setEnabled(bool)</slot>
- <hints>
- <hint type="sourcelabel">
- <x>84</x>
- <y>157</y>
- </hint>
- <hint type="destinationlabel">
- <x>342</x>
- <y>158</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <connections/>
</ui>
diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp
index 0097c9a29..a878ef9c6 100644
--- a/src/yuzu/configuration/configure_debug_controller.cpp
+++ b/src/yuzu/configuration/configure_debug_controller.cpp
@@ -4,11 +4,14 @@
#include "ui_configure_debug_controller.h"
#include "yuzu/configuration/configure_debug_controller.h"
+#include "yuzu/configuration/configure_input_player.h"
ConfigureDebugController::ConfigureDebugController(QWidget* parent,
- InputCommon::InputSubsystem* input_subsystem)
+ InputCommon::InputSubsystem* input_subsystem,
+ InputProfiles* profiles)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()),
- debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) {
+ debug_controller(
+ new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) {
ui->setupUi(this);
ui->controllerLayout->addWidget(debug_controller);
diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h
index 34dcf705f..b4f53fad5 100644
--- a/src/yuzu/configuration/configure_debug_controller.h
+++ b/src/yuzu/configuration/configure_debug_controller.h
@@ -6,10 +6,13 @@
#include <memory>
#include <QDialog>
-#include "yuzu/configuration/configure_input_player.h"
class QPushButton;
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
namespace InputCommon {
class InputSubsystem;
}
@@ -22,8 +25,8 @@ class ConfigureDebugController : public QDialog {
Q_OBJECT
public:
- explicit ConfigureDebugController(QWidget* parent,
- InputCommon::InputSubsystem* input_subsystem);
+ explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem,
+ InputProfiles* profiles);
~ConfigureDebugController() override;
void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_debug_controller.ui b/src/yuzu/configuration/configure_debug_controller.ui
index a95ed50ff..7b7e6582c 100644
--- a/src/yuzu/configuration/configure_debug_controller.ui
+++ b/src/yuzu/configuration/configure_debug_controller.ui
@@ -66,32 +66,12 @@
<signal>accepted()</signal>
<receiver>ConfigureDebugController</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>140</x>
- <y>318</y>
- </hint>
- <hint type="destinationlabel">
- <x>140</x>
- <y>169</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureDebugController</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>140</x>
- <y>318</y>
- </hint>
- <hint type="destinationlabel">
- <x>140</x>
- <y>169</y>
- </hint>
- </hints>
</connection>
</connections>
</ui>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 8186929a6..d6b17a28d 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -5,6 +5,7 @@
#include <QHash>
#include <QListWidgetItem>
#include <QSignalBlocker>
+#include "core/core.h"
#include "core/settings.h"
#include "ui_configure.h"
#include "yuzu/configuration/config.h"
@@ -15,7 +16,7 @@
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
InputCommon::InputSubsystem* input_subsystem)
: QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
- Settings::configuring_global = true;
+ Settings::SetConfiguringGlobal(true);
ui->setupUi(this);
ui->hotkeysTab->Populate(registry);
@@ -54,7 +55,7 @@ void ConfigureDialog::ApplyConfiguration() {
ui->debugTab->ApplyConfiguration();
ui->webTab->ApplyConfiguration();
ui->serviceTab->ApplyConfiguration();
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
Settings::LogSettings();
}
@@ -116,31 +117,13 @@ void ConfigureDialog::UpdateVisibleTabs() {
return;
}
- const std::map<QWidget*, QString> widgets = {
- {ui->generalTab, tr("General")},
- {ui->systemTab, tr("System")},
- {ui->profileManagerTab, tr("Profiles")},
- {ui->inputTab, tr("Controls")},
- {ui->hotkeysTab, tr("Hotkeys")},
- {ui->cpuTab, tr("CPU")},
- {ui->cpuDebugTab, tr("Debug")},
- {ui->graphicsTab, tr("Graphics")},
- {ui->graphicsAdvancedTab, tr("Advanced")},
- {ui->audioTab, tr("Audio")},
- {ui->debugTab, tr("Debug")},
- {ui->webTab, tr("Web")},
- {ui->uiTab, tr("UI")},
- {ui->filesystemTab, tr("Filesystem")},
- {ui->serviceTab, tr("Services")},
- };
-
[[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
ui->tabWidget->clear();
- const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
+ const auto tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
- for (const auto tab : tabs) {
+ for (auto* const tab : tabs) {
ui->tabWidget->addTab(tab, tab->accessibleName());
}
}
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 830096ea0..d4d29d422 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -19,7 +19,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
SetConfiguration();
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit,
[this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); });
}
@@ -41,7 +41,7 @@ void ConfigureGeneral::SetConfiguration() {
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue());
ui->frame_limit->setValue(Settings::values.frame_limit.GetValue());
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue());
} else {
ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue() &&
@@ -50,7 +50,7 @@ void ConfigureGeneral::SetConfiguration() {
}
void ConfigureGeneral::ApplyConfiguration() {
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
@@ -93,7 +93,7 @@ void ConfigureGeneral::RetranslateUI() {
}
void ConfigureGeneral::SetupPerGameUI() {
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal());
ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal());
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 07d818548..9ff32aec4 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -2,24 +2,23 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+// Include this early to include Vulkan headers how we want to
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
#include <QColorDialog>
#include <QComboBox>
-#ifdef HAS_VULKAN
#include <QVulkanInstance>
-#endif
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_graphics.h"
+#include "video_core/vulkan_common/vulkan_instance.h"
+#include "video_core/vulkan_common/vulkan_library.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
-#ifdef HAS_VULKAN
-#include "video_core/renderer_vulkan/renderer_vulkan.h"
-#endif
-
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
vulkan_device = Settings::values.vulkan_device.GetValue();
@@ -33,7 +32,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] {
UpdateDeviceComboBox();
- if (!Settings::configuring_global) {
+ if (!Settings::IsConfiguringGlobal()) {
ConfigurationShared::SetHighlight(
ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX);
}
@@ -49,8 +48,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
UpdateBackgroundColorButton(new_bg_color);
});
- ui->bg_label->setVisible(Settings::configuring_global);
- ui->bg_combobox->setVisible(!Settings::configuring_global);
+ ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
+ ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
}
void ConfigureGraphics::UpdateDeviceSelection(int device) {
@@ -70,11 +69,13 @@ void ConfigureGraphics::SetConfiguration() {
ui->api->setEnabled(runtime_lock);
ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setEnabled(runtime_lock);
+ ui->use_nvdec_emulation->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
ui->use_asynchronous_gpu_emulation->setChecked(
Settings::values.use_asynchronous_gpu_emulation.GetValue());
+ ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue());
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue()));
ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
} else {
@@ -98,7 +99,7 @@ void ConfigureGraphics::SetConfiguration() {
}
void ConfigureGraphics::ApplyConfiguration() {
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
// Guard if during game and set to game-specific value
if (Settings::values.renderer_backend.UsingGlobal()) {
Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
@@ -116,6 +117,9 @@ void ConfigureGraphics::ApplyConfiguration() {
Settings::values.use_asynchronous_gpu_emulation.SetValue(
ui->use_asynchronous_gpu_emulation->isChecked());
}
+ if (Settings::values.use_nvdec_emulation.UsingGlobal()) {
+ Settings::values.use_nvdec_emulation.SetValue(ui->use_nvdec_emulation->isChecked());
+ }
if (Settings::values.bg_red.UsingGlobal()) {
Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF()));
Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF()));
@@ -144,6 +148,8 @@ void ConfigureGraphics::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation,
ui->use_asynchronous_gpu_emulation,
use_asynchronous_gpu_emulation);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation,
+ ui->use_nvdec_emulation, use_nvdec_emulation);
if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.bg_red.SetGlobal(true);
@@ -187,7 +193,7 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
bool enabled = false;
- if (!Settings::configuring_global &&
+ if (!Settings::IsConfiguringGlobal() &&
ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
vulkan_device = Settings::values.vulkan_device.GetValue();
}
@@ -205,22 +211,32 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
break;
}
// If in per-game config and use global is selected, don't enable.
- enabled &= !(!Settings::configuring_global &&
+ enabled &= !(!Settings::IsConfiguringGlobal() &&
ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX);
ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn());
}
-void ConfigureGraphics::RetrieveVulkanDevices() {
-#ifdef HAS_VULKAN
+void ConfigureGraphics::RetrieveVulkanDevices() try {
+ using namespace Vulkan;
+
+ vk::InstanceDispatch dld;
+ const Common::DynamicLibrary library = OpenLibrary();
+ const vk::Instance instance = CreateInstance(library, dld, VK_API_VERSION_1_0);
+ const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices();
+
vulkan_devices.clear();
- for (auto& name : Vulkan::RendererVulkan::EnumerateDevices()) {
+ vulkan_devices.reserve(physical_devices.size());
+ for (const VkPhysicalDevice device : physical_devices) {
+ const char* const name = vk::PhysicalDevice(device, dld).GetProperties().deviceName;
vulkan_devices.push_back(QString::fromStdString(name));
}
-#endif
+
+} catch (const Vulkan::vk::Exception& exception) {
+ LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
}
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
}
@@ -234,12 +250,13 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
}
void ConfigureGraphics::SetupPerGameUI() {
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal());
ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal());
ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
ui->use_asynchronous_gpu_emulation->setEnabled(
Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
+ ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal());
ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
@@ -253,6 +270,8 @@ void ConfigureGraphics::SetupPerGameUI() {
ConfigurationShared::SetColoredTristate(
ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
+ ConfigurationShared::SetColoredTristate(
+ ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation);
ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
Settings::values.use_asynchronous_gpu_emulation,
use_asynchronous_gpu_emulation);
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index b4961f719..1fefc88eb 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -46,6 +46,7 @@ private:
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
+ ConfigurationShared::CheckState use_nvdec_emulation;
ConfigurationShared::CheckState use_disk_shader_cache;
ConfigurationShared::CheckState use_asynchronous_gpu_emulation;
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 62aa337e7..58486eb1e 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -98,6 +98,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="use_nvdec_emulation">
+ <property name="text">
+ <string>Use NVDEC emulation</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QWidget" name="aspect_ratio_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index 73f276949..383c7bac8 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -32,7 +32,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setCurrentIndex(
static_cast<int>(Settings::values.gpu_accuracy.GetValue()));
ui->anisotropic_filtering_combobox->setCurrentIndex(
@@ -52,9 +52,9 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
// Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots)
const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>(
ui->gpu_accuracy->currentIndex() -
- ((Settings::configuring_global) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
+ ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
// Must guard in case of a during-game configuration when set to be game-specific.
if (Settings::values.gpu_accuracy.UsingGlobal()) {
Settings::values.gpu_accuracy.SetValue(gpu_accuracy);
@@ -118,7 +118,7 @@ void ConfigureGraphicsAdvanced::RetranslateUI() {
void ConfigureGraphicsAdvanced::SetupPerGameUI() {
// Disable if not global (only happens during game)
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());
ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal());
ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal());
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 5223eed1d..422022d02 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -4,6 +4,7 @@
#include <algorithm>
#include <memory>
+#include <thread>
#include <QSignalBlocker>
#include <QTimer>
@@ -20,8 +21,11 @@
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_input_advanced.h"
#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
namespace {
template <typename Dialog, typename... Args>
@@ -63,22 +67,32 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
}
ConfigureInput::ConfigureInput(QWidget* parent)
- : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
+ profiles(std::make_unique<InputProfiles>()) {
ui->setupUi(this);
}
ConfigureInput::~ConfigureInput() = default;
-void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
+void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
+ std::size_t max_players) {
player_controllers = {
- new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem),
- new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem),
- new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem),
- new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem),
- new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem),
- new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem),
- new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem),
- new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem),
+ new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
+ new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem,
+ profiles.get()),
};
player_tabs = {
@@ -92,6 +106,11 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
};
+ std::array<QLabel*, 8> player_connected_labels = {
+ ui->label, ui->label_3, ui->label_4, ui->label_5,
+ ui->label_6, ui->label_7, ui->label_8, ui->label_9,
+ };
+
for (std::size_t i = 0; i < player_tabs.size(); ++i) {
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
player_tabs[i]->layout()->addWidget(player_controllers[i]);
@@ -106,11 +125,20 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
}
}
});
- connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices,
- [this] { UpdateAllInputDevices(); });
+ connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
+ &ConfigureInput::UpdateAllInputDevices);
+ connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
+ &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
player_controllers[i]->ConnectPlayer(state == Qt::Checked);
});
+
+ // Remove/hide all the elements that exceed max_players, if applicable.
+ if (i >= max_players) {
+ ui->tabWidget->removeTab(static_cast<int>(max_players));
+ player_connected[i]->hide();
+ player_connected_labels[i]->hide();
+ }
}
// Only the first player can choose handheld mode so connect the signal just to player 1
connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged,
@@ -120,13 +148,24 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
ui->tabAdvanced->layout()->addWidget(advanced);
connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
- CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem);
+ CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get());
});
connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
});
connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
[this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
+ connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog,
+ [this, input_subsystem] {
+ CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
+ });
+
+ connect(ui->vibrationButton, &QPushButton::clicked,
+ [this] { CallConfigureDialog<ConfigureVibration>(*this); });
+
+ connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
+ CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
+ });
connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
@@ -143,17 +182,32 @@ QList<QWidget*> ConfigureInput::GetSubTabs() const {
}
void ConfigureInput::ApplyConfiguration() {
- for (auto controller : player_controllers) {
+ for (auto* controller : player_controllers) {
controller->ApplyConfiguration();
+ controller->TryDisconnectSelectedController();
+ }
+
+ // This emulates a delay between disconnecting and reconnecting controllers as some games
+ // do not respond to a change in controller type if it was instantaneous.
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(150ms);
+
+ for (auto* controller : player_controllers) {
+ controller->TryConnectSelectedController();
}
+ // This emulates a delay between disconnecting and reconnecting controllers as some games
+ // do not respond to a change in controller type if it was instantaneous.
+ std::this_thread::sleep_for(150ms);
+
advanced->ApplyConfiguration();
- const bool pre_docked_mode = Settings::values.use_docked_mode;
- Settings::values.use_docked_mode = ui->radioDocked->isChecked();
- OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
+ const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
+ Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
+ OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
- Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
+ Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
+ Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
}
void ConfigureInput::changeEvent(QEvent* event) {
@@ -170,16 +224,16 @@ void ConfigureInput::RetranslateUI() {
void ConfigureInput::LoadConfiguration() {
LoadPlayerControllerIndices();
- UpdateDockedState(Settings::values.players[0].controller_type ==
- Settings::ControllerType::Handheld);
+ UpdateDockedState(Settings::values.players.GetValue()[8].connected);
- ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
+ ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
+ ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
}
void ConfigureInput::LoadPlayerControllerIndices() {
for (std::size_t i = 0; i < player_connected.size(); ++i) {
- const auto connected = Settings::values.players[i].connected ||
- (i == 0 && Settings::values.players[8].connected);
+ const auto connected = Settings::values.players.GetValue()[i].connected ||
+ (i == 0 && Settings::values.players.GetValue()[8].connected);
player_connected[i]->setChecked(connected);
}
}
@@ -200,17 +254,18 @@ void ConfigureInput::RestoreDefaults() {
ui->radioDocked->setChecked(true);
ui->radioUndocked->setChecked(false);
ui->vibrationGroup->setChecked(true);
+ ui->motionGroup->setChecked(true);
}
void ConfigureInput::UpdateDockedState(bool is_handheld) {
- // If the controller type is handheld only, disallow changing docked mode
+ // Disallow changing the console mode if the controller type is handheld.
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
- ui->radioDocked->setChecked(Settings::values.use_docked_mode);
- ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
+ ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
+ ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
- // If its handheld only, force docked mode off (since you can't play handheld in a dock)
+ // Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
ui->radioUndocked->setChecked(true);
}
@@ -218,6 +273,16 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
void ConfigureInput::UpdateAllInputDevices() {
for (const auto& player : player_controllers) {
- player->UpdateInputDevices();
+ player->UpdateInputDeviceCombobox();
+ }
+}
+
+void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) {
+ for (std::size_t i = 0; i < player_controllers.size(); ++i) {
+ if (i == player_index) {
+ continue;
+ }
+
+ player_controllers[i]->UpdateInputProfiles();
}
}
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index d08a24f96..f4eb0d78b 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -8,17 +8,18 @@
#include <memory>
#include <QKeyEvent>
+#include <QList>
#include <QWidget>
-#include "yuzu/configuration/configure_input_advanced.h"
-#include "yuzu/configuration/configure_input_player.h"
-
-#include "ui_configure_input.h"
-
class QCheckBox;
class QString;
class QTimer;
+class ConfigureInputAdvanced;
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
namespace InputCommon {
class InputSubsystem;
}
@@ -37,7 +38,7 @@ public:
~ConfigureInput() override;
/// Initializes the input dialog with the given input subsystem.
- void Initialize(InputCommon::InputSubsystem* input_subsystem_);
+ void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8);
/// Save all button configurations to settings file.
void ApplyConfiguration();
@@ -51,6 +52,7 @@ private:
void UpdateDockedState(bool is_handheld);
void UpdateAllInputDevices();
+ void UpdateAllInputProfiles(std::size_t player_index);
/// Load configuration settings.
void LoadConfiguration();
@@ -61,6 +63,8 @@ private:
std::unique_ptr<Ui::ConfigureInput> ui;
+ std::unique_ptr<InputProfiles> profiles;
+
std::array<ConfigureInputPlayer*, 8> player_controllers;
std::array<QWidget*, 8> player_tabs;
std::array<QCheckBox*, 8> player_connected;
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index 136955224..2707025e7 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>700</width>
+ <width>680</width>
<height>540</height>
</rect>
</property>
@@ -142,7 +142,7 @@
<number>6</number>
</property>
<property name="leftMargin">
- <number>3</number>
+ <number>8</number>
</property>
<property name="topMargin">
<number>6</number>
@@ -195,30 +195,24 @@
<number>3</number>
</property>
<item>
- <widget class="QSpinBox" name="vibrationSpin">
+ <widget class="QPushButton" name="vibrationButton">
<property name="minimumSize">
<size>
- <width>65</width>
- <height>21</height>
+ <width>68</width>
+ <height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>65</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
- <property name="suffix">
- <string>%</string>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>200</number>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
</property>
- <property name="value">
- <number>100</number>
+ <property name="text">
+ <string>Configure</string>
</property>
</widget>
</item>
@@ -250,18 +244,18 @@
<widget class="QPushButton" name="motionButton">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Configure</string>
@@ -272,7 +266,7 @@
</widget>
</item>
<item alignment="Qt::AlignVCenter">
- <widget class="QWidget" name="widget" native="true">
+ <widget class="QWidget" name="connectedControllers" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>5</number>
@@ -468,13 +462,13 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -494,7 +488,7 @@
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Defaults</string>
@@ -511,13 +505,13 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -537,7 +531,7 @@
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Clear</string>
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index db42b826b..a1a0eb676 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -68,8 +68,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) {
connect(color_buttons[button_idx], &QPushButton::clicked, this,
[this, player_idx, button_idx] {
- OnControllerButtonClick(static_cast<int>(player_idx),
- static_cast<int>(button_idx));
+ OnControllerButtonClick(player_idx, button_idx);
});
}
}
@@ -86,26 +85,29 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); });
connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
[this] { CallTouchscreenConfigDialog(); });
+ connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
+ &ConfigureInputAdvanced::CallMotionTouchConfigDialog);
LoadConfiguration();
}
ConfigureInputAdvanced::~ConfigureInputAdvanced() = default;
-void ConfigureInputAdvanced::OnControllerButtonClick(int player_idx, int button_idx) {
+void ConfigureInputAdvanced::OnControllerButtonClick(std::size_t player_idx,
+ std::size_t button_idx) {
const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]);
if (!new_bg_color.isValid()) {
return;
}
controllers_colors[player_idx][button_idx] = new_bg_color;
controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
- QStringLiteral("background-color: %1; min-width: 55px;")
+ QStringLiteral("background-color: %1; min-width: 60px;")
.arg(controllers_colors[player_idx][button_idx].name()));
}
void ConfigureInputAdvanced::ApplyConfiguration() {
for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
- auto& player = Settings::values.players[player_idx];
+ auto& player = Settings::values.players.GetValue()[player_idx];
std::array<u32, 4> colors{};
std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(),
colors.begin(), [](QColor color) { return color.rgb(); });
@@ -119,12 +121,16 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked();
Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
+ Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked();
+ Settings::values.mouse_panning = ui->mouse_panning->isChecked();
+ Settings::values.mouse_panning_sensitivity =
+ static_cast<float>(ui->mouse_panning_sensitivity->value());
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
- auto& player = Settings::values.players[player_idx];
+ auto& player = Settings::values.players.GetValue()[player_idx];
std::array<u32, 4> colors = {
player.body_color_left,
player.button_color_left,
@@ -137,7 +143,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) {
controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
- QStringLiteral("background-color: %1; min-width: 55px;")
+ QStringLiteral("background-color: %1; min-width: 60px;")
.arg(controllers_colors[player_idx][button_idx].name()));
}
}
@@ -145,6 +151,9 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled);
ui->mouse_enabled->setChecked(Settings::values.mouse_enabled);
ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
+ ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard);
+ ui->mouse_panning->setChecked(Settings::values.mouse_panning);
+ ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity);
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
UpdateUIEnabled();
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
index d8fcec52d..3083d55c1 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -28,13 +28,14 @@ signals:
void CallDebugControllerDialog();
void CallMouseConfigDialog();
void CallTouchscreenConfigDialog();
+ void CallMotionTouchConfigDialog();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void UpdateUIEnabled();
- void OnControllerButtonClick(int player_idx, int button_idx);
+ void OnControllerButtonClick(std::size_t player_idx, std::size_t button_idx);
void LoadConfiguration();
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index 5958435fc..173130d8d 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -192,18 +192,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -247,18 +247,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -323,18 +323,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -378,18 +378,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -478,18 +478,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -533,18 +533,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -609,18 +609,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -664,18 +664,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -782,18 +782,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -837,18 +837,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -913,18 +913,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -968,18 +968,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1068,18 +1068,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1123,18 +1123,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1199,18 +1199,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1254,18 +1254,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1393,18 +1393,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1448,18 +1448,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1524,18 +1524,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1579,18 +1579,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1679,18 +1679,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1734,18 +1734,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1810,18 +1810,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1865,18 +1865,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -1983,18 +1983,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -2038,18 +2038,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -2114,18 +2114,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -2169,18 +2169,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -2269,18 +2269,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -2324,18 +2324,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -2400,18 +2400,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -2455,18 +2455,18 @@
</property>
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@@ -2546,14 +2546,65 @@
</property>
</widget>
</item>
- <item row="4" column="2">
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="emulate_analog_keyboard">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>23</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Emulate Analog with Keyboard Input</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="mouse_panning">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>23</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Enable mouse panning</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QDoubleSpinBox" name="mouse_panning_sensitivity">
+ <property name="toolTip">
+ <string>Mouse sensitivity</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="decimals">
+ <number>2</number>
+ </property>
+ <property name="minimum">
+ <double>0.100000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>16.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.010000000000000</double>
+ </property>
+ <property name="value">
+ <double>1.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="2">
<widget class="QPushButton" name="touchscreen_advanced">
<property name="text">
<string>Advanced</string>
</property>
</widget>
</item>
- <item row="1" column="1">
+ <item row="3" column="1">
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -2569,21 +2620,21 @@
</property>
</spacer>
</item>
- <item row="1" column="2">
+ <item row="3" column="2">
<widget class="QPushButton" name="mouse_advanced">
<property name="text">
<string>Advanced</string>
</property>
</widget>
</item>
- <item row="4" column="0">
+ <item row="6" column="0">
<widget class="QCheckBox" name="touchscreen_enabled">
<property name="text">
<string>Touchscreen</string>
</property>
</widget>
</item>
- <item row="1" column="0">
+ <item row="3" column="0">
<widget class="QCheckBox" name="mouse_enabled">
<property name="minimumSize">
<size>
@@ -2596,28 +2647,28 @@
</property>
</widget>
</item>
- <item row="6" column="0">
+ <item row="8" column="0">
<widget class="QLabel" name="motion_touch">
<property name="text">
<string>Motion / Touch</string>
</property>
</widget>
</item>
- <item row="6" column="2">
+ <item row="8" column="2">
<widget class="QPushButton" name="buttonMotionTouch">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
- <item row="5" column="0">
+ <item row="7" column="0">
<widget class="QCheckBox" name="debug_enabled">
<property name="text">
<string>Debug Controller</string>
</property>
</widget>
</item>
- <item row="5" column="2">
+ <item row="7" column="2">
<widget class="QPushButton" name="debug_configure">
<property name="text">
<string>Configure</string>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 13ecb3dc5..21d0d3449 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -18,11 +18,17 @@
#include "core/hle/service/sm/sm.h"
#include "input_common/gcadapter/gc_poller.h"
#include "input_common/main.h"
+#include "input_common/mouse/mouse_poller.h"
+#include "input_common/udp/udp.h"
#include "ui_configure_input_player.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_input_player_widget.h"
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
+#include "yuzu/util/limitable_input_dialog.h"
-constexpr std::size_t HANDHELD_INDEX = 8;
+using namespace Service::HID;
const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
ConfigureInputPlayer::analog_sub_buttons{{
@@ -34,6 +40,8 @@ const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
namespace {
+constexpr std::size_t HANDHELD_INDEX = 8;
+
void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
bool connected) {
Core::System& system{Core::System::GetInstance()};
@@ -42,48 +50,12 @@ void UpdateController(Settings::ControllerType controller_type, std::size_t npad
}
Service::SM::ServiceManager& sm = system.ServiceManager();
- auto& npad =
- sm.GetService<Service::HID::Hid>("hid")
- ->GetAppletResource()
- ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
+ auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>(
+ HidController::NPad);
npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
}
-/// Maps the controller type combobox index to Controller Type enum
-constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) {
- switch (index) {
- case 0:
- default:
- return Settings::ControllerType::ProController;
- case 1:
- return Settings::ControllerType::DualJoyconDetached;
- case 2:
- return Settings::ControllerType::LeftJoycon;
- case 3:
- return Settings::ControllerType::RightJoycon;
- case 4:
- return Settings::ControllerType::Handheld;
- }
-}
-
-/// Maps the Controller Type enum to controller type combobox index
-constexpr int GetIndexFromControllerType(Settings::ControllerType type) {
- switch (type) {
- case Settings::ControllerType::ProController:
- default:
- return 0;
- case Settings::ControllerType::DualJoyconDetached:
- return 1;
- case Settings::ControllerType::LeftJoycon:
- return 2;
- case Settings::ControllerType::RightJoycon:
- return 3;
- case Settings::ControllerType::Handheld:
- return 4;
- }
-}
-
QString GetKeyName(int key_code) {
switch (key_code) {
case Qt::LeftButton:
@@ -149,6 +121,14 @@ QString ButtonToText(const Common::ParamPackage& param) {
return GetKeyName(param.Get("code", 0));
}
+ if (param.Get("engine", "") == "cemuhookudp") {
+ if (param.Has("pad_index")) {
+ const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
+ return QObject::tr("Motion %1").arg(motion_str);
+ }
+ return GetKeyName(param.Get("code", 0));
+ }
+
if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
const QString hat_str = QString::fromStdString(param.Get("hat", ""));
@@ -173,6 +153,14 @@ QString ButtonToText(const Common::ParamPackage& param) {
return {};
}
+ if (param.Get("engine", "") == "mouse") {
+ if (param.Has("button")) {
+ const QString button_str = QString::number(int(param.Get("button", 0)));
+ return QObject::tr("Click %1").arg(button_str);
+ }
+ return GetKeyName(param.Get("code", 0));
+ }
+
return QObject::tr("[unknown]");
}
@@ -185,41 +173,31 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
}
- if (param.Get("engine", "") == "sdl") {
+ const auto engine_str = param.Get("engine", "");
+ const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
+ const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
+ const bool invert_x = param.Get("invert_x", "+") == "-";
+ const bool invert_y = param.Get("invert_y", "+") == "-";
+ if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") {
if (dir == "modifier") {
return QObject::tr("[unused]");
}
- if (dir == "left" || dir == "right") {
- const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
-
- return QObject::tr("Axis %1").arg(axis_x_str);
+ if (dir == "left") {
+ const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-");
+ return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
}
-
- if (dir == "up" || dir == "down") {
- const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
-
- return QObject::tr("Axis %1").arg(axis_y_str);
+ if (dir == "right") {
+ const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+");
+ return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str);
}
-
- return {};
- }
-
- if (param.Get("engine", "") == "gcpad") {
- if (dir == "modifier") {
- return QObject::tr("[unused]");
- }
-
- if (dir == "left" || dir == "right") {
- const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
-
- return QObject::tr("GC Axis %1").arg(axis_x_str);
+ if (dir == "up") {
+ const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+");
+ return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
}
-
- if (dir == "up" || dir == "down") {
- const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
-
- return QObject::tr("GC Axis %1").arg(axis_y_str);
+ if (dir == "down") {
+ const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-");
+ return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str);
}
return {};
@@ -231,10 +209,11 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
QWidget* bottom_row,
InputCommon::InputSubsystem* input_subsystem_,
- bool debug)
+ InputProfiles* profiles_, bool debug)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
- debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()),
- poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) {
+ debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_),
+ timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
+ bottom_row(bottom_row) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
@@ -262,19 +241,26 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
},
}};
+ motion_map = {
+ ui->buttonMotionLeft,
+ ui->buttonMotionRight,
+ };
+
analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup};
+ analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod};
analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange};
analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange};
analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup};
analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
- const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param,
- int default_val) {
+ const auto ConfigureButtonClick = [&](QPushButton* button, std::size_t button_id,
+ Common::ParamPackage* param, int default_val,
+ InputCommon::Polling::DeviceType type) {
connect(button, &QPushButton::clicked, [=, this] {
HandleClick(
- button,
+ button, button_id,
[=, this](Common::ParamPackage params) {
// Workaround for ZL & ZR for analog triggers like on XBOX
// controllers. Analog triggers (from controllers like the XBOX
@@ -291,22 +277,56 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
*param = std::move(params);
},
- InputCommon::Polling::DeviceType::Button);
+ type);
});
};
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
auto* const button = button_map[button_id];
+
if (button == nullptr) {
continue;
}
- ConfigureButtonClick(button_map[button_id], &buttons_param[button_id],
- Config::default_buttons[button_id]);
+
+ ConfigureButtonClick(button_map[button_id], button_id, &buttons_param[button_id],
+ Config::default_buttons[button_id],
+ InputCommon::Polling::DeviceType::Button);
+
+ button->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(button, &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ buttons_param[button_id].Clear();
+ button_map[button_id]->setText(tr("[not set]"));
+ });
+ context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
+ ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
+ });
}
- // Handle clicks for the modifier buttons as well.
- ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]);
- ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]);
+ for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+ auto* const button = motion_map[motion_id];
+ if (button == nullptr) {
+ continue;
+ }
+
+ ConfigureButtonClick(motion_map[motion_id], motion_id, &motions_param[motion_id],
+ Config::default_motions[motion_id],
+ InputCommon::Polling::DeviceType::Motion);
+
+ button->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ connect(button, &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ motions_param[motion_id].Clear();
+ motion_map[motion_id]->setText(tr("[not set]"));
+ });
+ context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
+ });
+ }
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
@@ -317,26 +337,97 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
connect(analog_button, &QPushButton::clicked, [=, this] {
+ if (!map_analog_stick_accepted) {
+ map_analog_stick_accepted =
+ QMessageBox::information(
+ this, tr("Map Analog Stick"),
+ tr("After pressing OK, first move your joystick horizontally, and then "
+ "vertically.\nTo invert the axes, first move your joystick "
+ "vertically, and then horizontally."),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok;
+ if (!map_analog_stick_accepted) {
+ return;
+ }
+ }
HandleClick(
- analog_map_buttons[analog_id][sub_button_id],
+ analog_map_buttons[analog_id][sub_button_id], analog_id,
[=, this](const Common::ParamPackage& params) {
SetAnalogParam(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
},
InputCommon::Polling::DeviceType::AnalogPreferred);
});
+
+ analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ connect(
+ analog_button, &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ analogs_param[analog_id].Clear();
+ analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
+ });
+ context_menu.addAction(tr("Invert axis"), [&] {
+ if (sub_button_id == 2 || sub_button_id == 3) {
+ const bool invert_value =
+ analogs_param[analog_id].Get("invert_x", "+") == "-";
+ const std::string invert_str = invert_value ? "+" : "-";
+ analogs_param[analog_id].Set("invert_x", invert_str);
+ }
+ if (sub_button_id == 0 || sub_button_id == 1) {
+ const bool invert_value =
+ analogs_param[analog_id].Get("invert_y", "+") == "-";
+ const std::string invert_str = invert_value ? "+" : "-";
+ analogs_param[analog_id].Set("invert_y", invert_str);
+ }
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM;
+ ++sub_button_id) {
+ analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
+ analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
+ }
+ });
+ context_menu.exec(
+ analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(menu_location));
+ ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
+ });
}
+ // Handle clicks for the modifier buttons as well.
+ connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] {
+ HandleClick(
+ analog_map_modifier_button[analog_id], analog_id,
+ [=, this](const Common::ParamPackage& params) {
+ analogs_param[analog_id].Set("modifier", params.Serialize());
+ },
+ InputCommon::Polling::DeviceType::Button);
+ });
+
+ analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested,
+ [=, this](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ analogs_param[analog_id].Set("modifier", "");
+ analog_map_modifier_button[analog_id]->setText(tr("[not set]"));
+ });
+ context_menu.exec(
+ analog_map_modifier_button[analog_id]->mapToGlobal(menu_location));
+ });
+
connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
[=, this] {
const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
analogs_param[analog_id].Set("range", spinbox_value / 100.0f);
+ ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
});
connect(analog_map_deadzone_slider[analog_id], &QSlider::valueChanged, [=, this] {
const auto slider_value = analog_map_deadzone_slider[analog_id]->value();
analog_map_deadzone_label[analog_id]->setText(tr("Deadzone: %1%").arg(slider_value));
analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
+ ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
});
connect(analog_map_modifier_slider[analog_id], &QSlider::valueChanged, [=, this] {
@@ -348,21 +439,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
// Player Connected checkbox
- connect(ui->groupConnectedController, &QGroupBox::toggled,
- [this](bool checked) { emit Connected(checked); });
-
- // Set up controller type. Only Player 1 can choose Handheld.
- ui->comboControllerType->clear();
-
- QStringList controller_types = {
- tr("Pro Controller"),
- tr("Dual Joycons"),
- tr("Left Joycon"),
- tr("Right Joycon"),
- };
+ connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) {
+ emit Connected(checked);
+ ui->controllerFrame->SetConnectedStatus(checked);
+ });
if (player_index == 0) {
- controller_types.append(tr("Handheld"));
connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged),
[this](int index) {
emit HandheldStateChanged(GetControllerTypeFromIndex(index) ==
@@ -370,31 +452,38 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
});
}
+ if (debug || player_index == 9) {
+ ui->groupConnectedController->setCheckable(false);
+ }
+
// The Debug Controller can only choose the Pro Controller.
if (debug) {
ui->buttonScreenshot->setEnabled(false);
ui->buttonHome->setEnabled(false);
- ui->groupConnectedController->setCheckable(false);
- QStringList debug_controller_types = {
- tr("Pro Controller"),
- };
- ui->comboControllerType->addItems(debug_controller_types);
+ ui->comboControllerType->addItem(tr("Pro Controller"));
} else {
- ui->comboControllerType->addItems(controller_types);
+ SetConnectableControllers();
}
UpdateControllerIcon();
UpdateControllerAvailableButtons();
+ UpdateControllerEnabledButtons();
+ UpdateControllerButtonNames();
+ UpdateMotionButtons();
connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
UpdateControllerIcon();
UpdateControllerAvailableButtons();
+ UpdateControllerEnabledButtons();
+ UpdateControllerButtonNames();
+ UpdateMotionButtons();
});
- connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
&ConfigureInputPlayer::UpdateMappingWithDefaults);
+ ui->comboDevices->setCurrentIndex(-1);
+
ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
- UpdateInputDevices();
connect(ui->buttonRefreshDevices, &QPushButton::clicked,
[this] { emit RefreshInputDevices(); });
@@ -405,37 +494,82 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
Common::ParamPackage params;
if (input_subsystem->GetGCButtons()->IsPolling()) {
params = input_subsystem->GetGCButtons()->GetNextInput();
- if (params.Has("engine")) {
+ if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false);
return;
}
}
if (input_subsystem->GetGCAnalogs()->IsPolling()) {
params = input_subsystem->GetGCAnalogs()->GetNextInput();
+ if (params.Has("engine") && IsInputAcceptable(params)) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ if (input_subsystem->GetUDPMotions()->IsPolling()) {
+ params = input_subsystem->GetUDPMotions()->GetNextInput();
if (params.Has("engine")) {
SetPollingResult(params, false);
return;
}
}
+ if (input_subsystem->GetMouseButtons()->IsPolling()) {
+ params = input_subsystem->GetMouseButtons()->GetNextInput();
+ if (params.Has("engine") && IsInputAcceptable(params)) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ if (input_subsystem->GetMouseAnalogs()->IsPolling()) {
+ params = input_subsystem->GetMouseAnalogs()->GetNextInput();
+ if (params.Has("engine") && IsInputAcceptable(params)) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ if (input_subsystem->GetMouseMotions()->IsPolling()) {
+ params = input_subsystem->GetMouseMotions()->GetNextInput();
+ if (params.Has("engine") && IsInputAcceptable(params)) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ if (input_subsystem->GetMouseTouch()->IsPolling()) {
+ params = input_subsystem->GetMouseTouch()->GetNextInput();
+ if (params.Has("engine") && IsInputAcceptable(params)) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
- if (params.Has("engine")) {
+ if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false);
return;
}
}
});
- LoadConfiguration();
+ UpdateInputProfiles();
- // TODO(wwylele): enable this when we actually emulate it
- ui->buttonHome->setEnabled(false);
+ connect(ui->buttonProfilesNew, &QPushButton::clicked, this,
+ &ConfigureInputPlayer::CreateProfile);
+ connect(ui->buttonProfilesDelete, &QPushButton::clicked, this,
+ &ConfigureInputPlayer::DeleteProfile);
+ connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this,
+ &ConfigureInputPlayer::LoadProfile);
+ connect(ui->buttonProfilesSave, &QPushButton::clicked, this,
+ &ConfigureInputPlayer::SaveProfile);
+
+ LoadConfiguration();
+ ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
+ ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked());
}
ConfigureInputPlayer::~ConfigureInputPlayer() = default;
void ConfigureInputPlayer::ApplyConfiguration() {
- auto& player = Settings::values.players[player_index];
+ auto& player = Settings::values.players.GetValue()[player_index];
auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons;
auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;
@@ -448,30 +582,98 @@ void ConfigureInputPlayer::ApplyConfiguration() {
return;
}
- player.controller_type =
- static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex());
- player.connected = ui->groupConnectedController->isChecked();
+ auto& motions = player.motions;
+
+ std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
+ [](const Common::ParamPackage& param) { return param.Serialize(); });
+
+ // Apply configuration for handheld
+ if (player_index == 0) {
+ auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
+ const auto handheld_connected = handheld.connected;
+ handheld = player;
+ handheld.connected = handheld_connected;
+ }
+}
+
+void ConfigureInputPlayer::TryConnectSelectedController() {
+ auto& player = Settings::values.players.GetValue()[player_index];
- // Player 2-8
- if (player_index != 0) {
- UpdateController(player.controller_type, player_index, player.connected);
+ const auto controller_type =
+ GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ const auto player_connected = ui->groupConnectedController->isChecked() &&
+ controller_type != Settings::ControllerType::Handheld;
+
+ // Connect Handheld depending on Player 1's controller configuration.
+ if (player_index == 0) {
+ auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
+ const auto handheld_connected = ui->groupConnectedController->isChecked() &&
+ controller_type == Settings::ControllerType::Handheld;
+ // Connect only if handheld is going from disconnected to connected
+ if (!handheld.connected && handheld_connected) {
+ UpdateController(controller_type, HANDHELD_INDEX, true);
+ }
+ handheld.connected = handheld_connected;
+ }
+
+ if (player.controller_type == controller_type && player.connected == player_connected) {
+ // Set vibration devices in the event that the input device has changed.
+ ConfigureVibration::SetVibrationDevices(player_index);
return;
}
- // Player 1 and Handheld
- auto& handheld = Settings::values.players[HANDHELD_INDEX];
- // If Handheld is selected, copy all the settings from Player 1 to Handheld.
- if (player.controller_type == Settings::ControllerType::Handheld) {
- handheld = player;
- handheld.connected = ui->groupConnectedController->isChecked();
- player.connected = false; // Disconnect Player 1
- } else {
- player.connected = ui->groupConnectedController->isChecked();
- handheld.connected = false; // Disconnect Handheld
+ player.controller_type = controller_type;
+ player.connected = player_connected;
+
+ ConfigureVibration::SetVibrationDevices(player_index);
+
+ if (!player.connected) {
+ return;
}
- UpdateController(player.controller_type, player_index, player.connected);
- UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
+ UpdateController(controller_type, player_index, true);
+}
+
+void ConfigureInputPlayer::TryDisconnectSelectedController() {
+ const auto& player = Settings::values.players.GetValue()[player_index];
+
+ const auto controller_type =
+ GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ const auto player_connected = ui->groupConnectedController->isChecked() &&
+ controller_type != Settings::ControllerType::Handheld;
+
+ // Disconnect Handheld depending on Player 1's controller configuration.
+ if (player_index == 0 && player.controller_type == Settings::ControllerType::Handheld) {
+ const auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
+ const auto handheld_connected = ui->groupConnectedController->isChecked() &&
+ controller_type == Settings::ControllerType::Handheld;
+ // Disconnect only if handheld is going from connected to disconnected
+ if (handheld.connected && !handheld_connected) {
+ UpdateController(controller_type, HANDHELD_INDEX, false);
+ }
+ return;
+ }
+
+ // Do not do anything if the controller configuration has not changed.
+ if (player.controller_type == controller_type && player.connected == player_connected) {
+ return;
+ }
+
+ // Do not disconnect if the controller is already disconnected
+ if (!player.connected) {
+ return;
+ }
+
+ // Disconnect the controller first.
+ UpdateController(controller_type, player_index, false);
+}
+
+void ConfigureInputPlayer::showEvent(QShowEvent* event) {
+ if (bottom_row == nullptr) {
+ return;
+ }
+ QWidget::showEvent(event);
+ ui->main->addWidget(bottom_row);
}
void ConfigureInputPlayer::changeEvent(QEvent* event) {
@@ -488,7 +690,7 @@ void ConfigureInputPlayer::RetranslateUI() {
}
void ConfigureInputPlayer::LoadConfiguration() {
- auto& player = Settings::values.players[player_index];
+ auto& player = Settings::values.players.GetValue()[player_index];
if (debug) {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
@@ -501,71 +703,105 @@ void ConfigureInputPlayer::LoadConfiguration() {
[](const std::string& str) { return Common::ParamPackage(str); });
std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
+ std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(),
+ [](const std::string& str) { return Common::ParamPackage(str); });
}
UpdateUI();
+ UpdateInputDeviceCombobox();
if (debug) {
return;
}
- ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type));
+ ui->comboControllerType->setCurrentIndex(GetIndexFromControllerType(player.controller_type));
ui->groupConnectedController->setChecked(
player.connected ||
- (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected));
+ (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected));
}
-void ConfigureInputPlayer::UpdateInputDevices() {
- input_devices = input_subsystem->GetInputDevices();
- ui->comboDevices->clear();
- for (auto device : input_devices) {
- ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
- }
+void ConfigureInputPlayer::ConnectPlayer(bool connected) {
+ ui->groupConnectedController->setChecked(connected);
}
-void ConfigureInputPlayer::RestoreDefaults() {
- // Reset Buttons
- for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
- buttons_param[button_id] = Common::ParamPackage{
- InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
+ // Skip input device persistence if "Input Devices" is set to "Any".
+ if (ui->comboDevices->currentIndex() == 0) {
+ UpdateInputDevices();
+ return;
}
- // Reset Modifier Buttons
- lstick_mod =
- Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[0]));
- rstick_mod =
- Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[1]));
+ // Find the first button that isn't empty.
+ const auto button_param =
+ std::find_if(buttons_param.begin(), buttons_param.end(),
+ [](const Common::ParamPackage param) { return param.Has("engine"); });
+ const bool buttons_empty = button_param == buttons_param.end();
- // Reset Analogs
- for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
- for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
- Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
- Config::default_analogs[analog_id][sub_button_id])};
- SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
+ const auto current_engine = button_param->Get("engine", "");
+ const auto current_guid = button_param->Get("guid", "");
+ const auto current_port = button_param->Get("port", "");
+
+ const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse";
+
+ UpdateInputDevices();
+
+ if (buttons_empty) {
+ return;
+ }
+
+ const bool all_one_device =
+ std::all_of(buttons_param.begin(), buttons_param.end(),
+ [current_engine, current_guid, current_port,
+ is_keyboard_mouse](const Common::ParamPackage param) {
+ if (is_keyboard_mouse) {
+ return !param.Has("engine") || param.Get("engine", "") == "keyboard" ||
+ param.Get("engine", "") == "mouse";
+ }
+ return !param.Has("engine") || (param.Get("engine", "") == current_engine &&
+ param.Get("guid", "") == current_guid &&
+ param.Get("port", "") == current_port);
+ });
+
+ if (all_one_device) {
+ if (is_keyboard_mouse) {
+ ui->comboDevices->setCurrentIndex(1);
+ return;
}
+ const auto devices_it = std::find_if(
+ input_devices.begin(), input_devices.end(),
+ [current_engine, current_guid, current_port](const Common::ParamPackage param) {
+ return param.Get("class", "") == current_engine &&
+ param.Get("guid", "") == current_guid &&
+ param.Get("port", "") == current_port;
+ });
+ const int device_index =
+ devices_it != input_devices.end()
+ ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
+ : 0;
+ ui->comboDevices->setCurrentIndex(device_index);
+ } else {
+ ui->comboDevices->setCurrentIndex(0);
}
- UpdateUI();
- UpdateInputDevices();
- ui->comboControllerType->setCurrentIndex(0);
+}
+
+void ConfigureInputPlayer::RestoreDefaults() {
+ UpdateMappingWithDefaults();
}
void ConfigureInputPlayer::ClearAll() {
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
const auto* const button = button_map[button_id];
- if (button == nullptr || !button->isEnabled()) {
+ if (button == nullptr) {
continue;
}
buttons_param[button_id].Clear();
}
- lstick_mod.Clear();
- rstick_mod.Clear();
-
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
- if (analog_button == nullptr || !analog_button->isEnabled()) {
+ if (analog_button == nullptr) {
continue;
}
@@ -573,6 +809,15 @@ void ConfigureInputPlayer::ClearAll() {
}
}
+ for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+ const auto* const motion_button = motion_map[motion_id];
+ if (motion_button == nullptr) {
+ continue;
+ }
+
+ motions_param[motion_id].Clear();
+ }
+
UpdateUI();
UpdateInputDevices();
}
@@ -582,8 +827,9 @@ void ConfigureInputPlayer::UpdateUI() {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
- ui->buttonLStickMod->setText(ButtonToText(lstick_mod));
- ui->buttonRStickMod->setText(ButtonToText(rstick_mod));
+ for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+ motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id]));
+ }
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
@@ -597,6 +843,9 @@ void ConfigureInputPlayer::UpdateUI() {
AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
}
+ analog_map_modifier_button[analog_id]->setText(
+ ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")}));
+
const auto deadzone_label = analog_map_deadzone_label[analog_id];
const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id];
@@ -607,8 +856,9 @@ void ConfigureInputPlayer::UpdateUI() {
int slider_value;
auto& param = analogs_param[analog_id];
- const bool is_controller =
- param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad";
+ const bool is_controller = param.Get("engine", "") == "sdl" ||
+ param.Get("engine", "") == "gcpad" ||
+ param.Get("engine", "") == "mouse";
if (is_controller) {
if (!param.Has("deadzone")) {
@@ -636,110 +886,98 @@ void ConfigureInputPlayer::UpdateUI() {
modifier_label->setVisible(!is_controller);
modifier_slider->setVisible(!is_controller);
range_groupbox->setVisible(is_controller);
+ ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
}
}
-void ConfigureInputPlayer::UpdateMappingWithDefaults() {
- if (ui->comboDevices->currentIndex() < 2) {
- return;
- }
- const auto& device = input_devices[ui->comboDevices->currentIndex()];
- auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
- auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
- for (int i = 0; i < buttons_param.size(); ++i) {
- buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
- }
- for (int i = 0; i < analogs_param.size(); ++i) {
- analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
- }
+void ConfigureInputPlayer::SetConnectableControllers() {
+ const auto add_controllers = [this](bool enable_all,
+ Controller_NPad::NpadStyleSet npad_style_set = {}) {
+ index_controller_type_pairs.clear();
+ ui->comboControllerType->clear();
- UpdateUI();
-}
+ if (enable_all || npad_style_set.fullkey == 1) {
+ index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
+ Settings::ControllerType::ProController);
+ ui->comboControllerType->addItem(tr("Pro Controller"));
+ }
-void ConfigureInputPlayer::HandleClick(
- QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
- InputCommon::Polling::DeviceType type) {
- button->setText(tr("[waiting]"));
- button->setFocus();
+ if (enable_all || npad_style_set.joycon_dual == 1) {
+ index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
+ Settings::ControllerType::DualJoyconDetached);
+ ui->comboControllerType->addItem(tr("Dual Joycons"));
+ }
- // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
- // controller, then they don't want keyboard/mouse input
- want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
+ if (enable_all || npad_style_set.joycon_left == 1) {
+ index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
+ Settings::ControllerType::LeftJoycon);
+ ui->comboControllerType->addItem(tr("Left Joycon"));
+ }
- input_setter = new_input_setter;
+ if (enable_all || npad_style_set.joycon_right == 1) {
+ index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
+ Settings::ControllerType::RightJoycon);
+ ui->comboControllerType->addItem(tr("Right Joycon"));
+ }
- device_pollers = input_subsystem->GetPollers(type);
+ if (player_index == 0 && (enable_all || npad_style_set.handheld == 1)) {
+ index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
+ Settings::ControllerType::Handheld);
+ ui->comboControllerType->addItem(tr("Handheld"));
+ }
- for (auto& poller : device_pollers) {
- poller->Start();
- }
+ if (enable_all || npad_style_set.gamecube == 1) {
+ index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
+ Settings::ControllerType::GameCube);
+ ui->comboControllerType->addItem(tr("GameCube Controller"));
+ }
+ };
- QWidget::grabMouse();
- QWidget::grabKeyboard();
+ Core::System& system{Core::System::GetInstance()};
- if (type == InputCommon::Polling::DeviceType::Button) {
- input_subsystem->GetGCButtons()->BeginConfiguration();
- } else {
- input_subsystem->GetGCAnalogs()->BeginConfiguration();
+ if (!system.IsPoweredOn()) {
+ add_controllers(true);
+ return;
}
- timeout_timer->start(2500); // Cancel after 2.5 seconds
- poll_timer->start(50); // Check for new inputs every 50ms
-}
+ Service::SM::ServiceManager& sm = system.ServiceManager();
-void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
- timeout_timer->stop();
- poll_timer->stop();
- for (auto& poller : device_pollers) {
- poller->Stop();
- }
+ auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>(
+ HidController::NPad);
- QWidget::releaseMouse();
- QWidget::releaseKeyboard();
+ add_controllers(false, npad.GetSupportedStyleSet());
+}
- input_subsystem->GetGCButtons()->EndConfiguration();
- input_subsystem->GetGCAnalogs()->EndConfiguration();
+Settings::ControllerType ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const {
+ const auto it =
+ std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(),
+ [index](const auto& pair) { return pair.first == index; });
- if (!abort) {
- (*input_setter)(params);
+ if (it == index_controller_type_pairs.end()) {
+ return Settings::ControllerType::ProController;
}
- UpdateUI();
- input_setter = std::nullopt;
+ return it->second;
}
-void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
- if (!input_setter || !event) {
- return;
- }
+int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType type) const {
+ const auto it =
+ std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(),
+ [type](const auto& pair) { return pair.second == type; });
- if (want_keyboard_mouse) {
- SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
- false);
- } else {
- // We don't want any mouse buttons, so don't stop polling
- return;
+ if (it == index_controller_type_pairs.end()) {
+ return -1;
}
- SetPollingResult({}, true);
+ return it->first;
}
-void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
- if (!input_setter || !event) {
- return;
- }
-
- if (event->key() != Qt::Key_Escape) {
- if (want_keyboard_mouse) {
- SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
- false);
- } else {
- // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
- return;
- }
+void ConfigureInputPlayer::UpdateInputDevices() {
+ input_devices = input_subsystem->GetInputDevices();
+ ui->comboDevices->clear();
+ for (auto device : input_devices) {
+ ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
}
-
- SetPollingResult({}, true);
}
void ConfigureInputPlayer::UpdateControllerIcon() {
@@ -762,7 +1000,7 @@ void ConfigureInputPlayer::UpdateControllerIcon() {
}
}();
- const QString theme = [this] {
+ const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
return QStringLiteral("_dark");
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
@@ -771,8 +1009,8 @@ void ConfigureInputPlayer::UpdateControllerIcon() {
return QString{};
}
}();
-
- ui->controllerFrame->setStyleSheet(stylesheet.arg(theme));
+ ui->controllerFrame->SetControllerType(
+ GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()));
}
void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
@@ -783,7 +1021,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
// List of all the widgets that will be hidden by any of the following layouts that need
// "unhidden" after the controller type changes
- const std::array<QWidget*, 9> layout_show = {
+ const std::array<QWidget*, 11> layout_show = {
ui->buttonShoulderButtonsSLSR,
ui->horizontalSpacerShoulderButtonsWidget,
ui->horizontalSpacerShoulderButtonsWidget2,
@@ -793,6 +1031,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
ui->buttonShoulderButtonsRight,
ui->buttonMiscButtonsPlusHome,
ui->bottomRight,
+ ui->buttonMiscButtonsMinusGroup,
+ ui->buttonMiscButtonsScreenshotGroup,
};
for (auto* widget : layout_show) {
@@ -825,6 +1065,14 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
ui->bottomLeft,
};
break;
+ case Settings::ControllerType::GameCube:
+ layout_hidden = {
+ ui->buttonShoulderButtonsSLSR,
+ ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->buttonMiscButtonsMinusGroup,
+ ui->buttonMiscButtonsScreenshotGroup,
+ };
+ break;
}
for (auto* widget : layout_hidden) {
@@ -832,14 +1080,387 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
}
}
-void ConfigureInputPlayer::showEvent(QShowEvent* event) {
- if (bottom_row == nullptr) {
+void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
+ auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ if (debug) {
+ layout = Settings::ControllerType::ProController;
+ }
+
+ // List of all the widgets that will be disabled by any of the following layouts that need
+ // "enabled" after the controller type changes
+ const std::array<QWidget*, 4> layout_enable = {
+ ui->buttonHome,
+ ui->buttonLStickPressedGroup,
+ ui->groupRStickPressed,
+ ui->buttonShoulderButtonsButtonLGroup,
+ };
+
+ for (auto* widget : layout_enable) {
+ widget->setEnabled(true);
+ }
+
+ std::vector<QWidget*> layout_disable;
+ switch (layout) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::DualJoyconDetached:
+ case Settings::ControllerType::Handheld:
+ case Settings::ControllerType::LeftJoycon:
+ case Settings::ControllerType::RightJoycon:
+ // TODO(wwylele): enable this when we actually emulate it
+ layout_disable = {
+ ui->buttonHome,
+ };
+ break;
+ case Settings::ControllerType::GameCube:
+ layout_disable = {
+ ui->buttonHome,
+ ui->buttonLStickPressedGroup,
+ ui->groupRStickPressed,
+ ui->buttonShoulderButtonsButtonLGroup,
+ };
+ break;
+ }
+
+ for (auto* widget : layout_disable) {
+ widget->setEnabled(false);
+ }
+}
+
+void ConfigureInputPlayer::UpdateMotionButtons() {
+ if (debug) {
+ // Motion isn't used with the debug controller, hide both groupboxes.
+ ui->buttonMotionLeftGroup->hide();
+ ui->buttonMotionRightGroup->hide();
return;
}
- QWidget::showEvent(event);
- ui->main->addWidget(bottom_row);
+
+ // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
+ switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::LeftJoycon:
+ case Settings::ControllerType::Handheld:
+ // Show "Motion 1" and hide "Motion 2".
+ ui->buttonMotionLeftGroup->show();
+ ui->buttonMotionRightGroup->hide();
+ break;
+ case Settings::ControllerType::RightJoycon:
+ // Show "Motion 2" and hide "Motion 1".
+ ui->buttonMotionLeftGroup->hide();
+ ui->buttonMotionRightGroup->show();
+ break;
+ case Settings::ControllerType::GameCube:
+ // Hide both "Motion 1/2".
+ ui->buttonMotionLeftGroup->hide();
+ ui->buttonMotionRightGroup->hide();
+ break;
+ case Settings::ControllerType::DualJoyconDetached:
+ default:
+ // Show both "Motion 1/2".
+ ui->buttonMotionLeftGroup->show();
+ ui->buttonMotionRightGroup->show();
+ break;
+ }
}
-void ConfigureInputPlayer::ConnectPlayer(bool connected) {
- ui->groupConnectedController->setChecked(connected);
+void ConfigureInputPlayer::UpdateControllerButtonNames() {
+ auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ if (debug) {
+ layout = Settings::ControllerType::ProController;
+ }
+
+ switch (layout) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::DualJoyconDetached:
+ case Settings::ControllerType::Handheld:
+ case Settings::ControllerType::LeftJoycon:
+ case Settings::ControllerType::RightJoycon:
+ ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus"));
+ ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL"));
+ ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR"));
+ ui->buttonShoulderButtonsRGroup->setTitle(tr("R"));
+ ui->LStick->setTitle(tr("Left Stick"));
+ ui->RStick->setTitle(tr("Right Stick"));
+ break;
+ case Settings::ControllerType::GameCube:
+ ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause"));
+ ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L"));
+ ui->buttonShoulderButtonsZRGroup->setTitle(tr("R"));
+ ui->buttonShoulderButtonsRGroup->setTitle(tr("Z"));
+ ui->LStick->setTitle(tr("Control Stick"));
+ ui->RStick->setTitle(tr("C-Stick"));
+ break;
+ }
+}
+
+void ConfigureInputPlayer::UpdateMappingWithDefaults() {
+ if (ui->comboDevices->currentIndex() == 0) {
+ return;
+ }
+
+ if (ui->comboDevices->currentIndex() == 1) {
+ // Reset keyboard bindings
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
+ buttons_param[button_id] = Common::ParamPackage{
+ InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+ }
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
+ Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
+ Config::default_analogs[analog_id][sub_button_id])};
+ SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
+ }
+
+ analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam(
+ Config::default_stick_mod[analog_id]));
+ }
+
+ for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+ motions_param[motion_id] = Common::ParamPackage{
+ InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
+ }
+
+ UpdateUI();
+ return;
+ }
+
+ // Reset controller bindings
+ const auto& device = input_devices[ui->comboDevices->currentIndex()];
+ auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
+ auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
+ for (std::size_t i = 0; i < buttons_param.size(); ++i) {
+ buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
+ }
+ for (std::size_t i = 0; i < analogs_param.size(); ++i) {
+ analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
+ }
+
+ UpdateUI();
+}
+
+void ConfigureInputPlayer::HandleClick(
+ QPushButton* button, std::size_t button_id,
+ std::function<void(const Common::ParamPackage&)> new_input_setter,
+ InputCommon::Polling::DeviceType type) {
+ if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
+ button->setText(tr("Shake!"));
+ } else {
+ button->setText(tr("[waiting]"));
+ }
+ button->setFocus();
+
+ // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
+ // controller, then they don't want keyboard/mouse input
+ want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
+
+ input_setter = new_input_setter;
+
+ device_pollers = input_subsystem->GetPollers(type);
+
+ for (auto& poller : device_pollers) {
+ poller->Start();
+ }
+
+ QWidget::grabMouse();
+ QWidget::grabKeyboard();
+
+ if (type == InputCommon::Polling::DeviceType::Button) {
+ input_subsystem->GetGCButtons()->BeginConfiguration();
+ } else {
+ input_subsystem->GetGCAnalogs()->BeginConfiguration();
+ }
+
+ if (type == InputCommon::Polling::DeviceType::Motion) {
+ input_subsystem->GetUDPMotions()->BeginConfiguration();
+ }
+
+ if (type == InputCommon::Polling::DeviceType::Button) {
+ input_subsystem->GetMouseButtons()->BeginConfiguration();
+ } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) {
+ input_subsystem->GetMouseAnalogs()->BeginConfiguration();
+ } else if (type == InputCommon::Polling::DeviceType::Motion) {
+ input_subsystem->GetMouseMotions()->BeginConfiguration();
+ } else {
+ input_subsystem->GetMouseTouch()->BeginConfiguration();
+ }
+
+ if (type == InputCommon::Polling::DeviceType::Button) {
+ ui->controllerFrame->BeginMappingButton(button_id);
+ } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) {
+ ui->controllerFrame->BeginMappingAnalog(button_id);
+ }
+
+ timeout_timer->start(2500); // Cancel after 2.5 seconds
+ poll_timer->start(50); // Check for new inputs every 50ms
+}
+
+void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
+ timeout_timer->stop();
+ poll_timer->stop();
+ for (auto& poller : device_pollers) {
+ poller->Stop();
+ }
+
+ QWidget::releaseMouse();
+ QWidget::releaseKeyboard();
+
+ input_subsystem->GetGCButtons()->EndConfiguration();
+ input_subsystem->GetGCAnalogs()->EndConfiguration();
+
+ input_subsystem->GetUDPMotions()->EndConfiguration();
+
+ input_subsystem->GetMouseButtons()->EndConfiguration();
+ input_subsystem->GetMouseAnalogs()->EndConfiguration();
+ input_subsystem->GetMouseMotions()->EndConfiguration();
+ input_subsystem->GetMouseTouch()->EndConfiguration();
+
+ if (!abort) {
+ (*input_setter)(params);
+ }
+
+ UpdateUI();
+ UpdateInputDeviceCombobox();
+ ui->controllerFrame->EndMapping();
+
+ input_setter = std::nullopt;
+}
+
+bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const {
+ if (ui->comboDevices->currentIndex() == 0) {
+ return true;
+ }
+
+ // Keyboard/Mouse
+ if (ui->comboDevices->currentIndex() == 1) {
+ return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse";
+ }
+
+ const auto current_input_device = input_devices[ui->comboDevices->currentIndex()];
+ return params.Get("engine", "") == current_input_device.Get("class", "") &&
+ params.Get("guid", "") == current_input_device.Get("guid", "") &&
+ params.Get("port", "") == current_input_device.Get("port", "");
+}
+
+void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
+ if (!input_setter || !event) {
+ return;
+ }
+
+ input_subsystem->GetMouse()->PressButton(0, 0, event->button());
+}
+
+void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
+ if (!input_setter || !event) {
+ return;
+ }
+
+ if (event->key() != Qt::Key_Escape) {
+ if (want_keyboard_mouse) {
+ SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
+ false);
+ } else {
+ // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
+ return;
+ }
+ }
+
+ SetPollingResult({}, true);
+}
+
+void ConfigureInputPlayer::CreateProfile() {
+ const auto profile_name =
+ LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20);
+
+ if (profile_name.isEmpty()) {
+ return;
+ }
+
+ if (!InputProfiles::IsProfileNameValid(profile_name.toStdString())) {
+ QMessageBox::critical(this, tr("Create Input Profile"),
+ tr("The given profile name is not valid!"));
+ return;
+ }
+
+ ApplyConfiguration();
+
+ if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) {
+ QMessageBox::critical(this, tr("Create Input Profile"),
+ tr("Failed to create the input profile \"%1\"").arg(profile_name));
+ UpdateInputProfiles();
+ emit RefreshInputProfiles(player_index);
+ return;
+ }
+
+ emit RefreshInputProfiles(player_index);
+
+ ui->comboProfiles->addItem(profile_name);
+ ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1);
+}
+
+void ConfigureInputPlayer::DeleteProfile() {
+ const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+ if (profile_name.isEmpty()) {
+ return;
+ }
+
+ if (!profiles->DeleteProfile(profile_name.toStdString())) {
+ QMessageBox::critical(this, tr("Delete Input Profile"),
+ tr("Failed to delete the input profile \"%1\"").arg(profile_name));
+ UpdateInputProfiles();
+ emit RefreshInputProfiles(player_index);
+ return;
+ }
+
+ emit RefreshInputProfiles(player_index);
+
+ ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex());
+ ui->comboProfiles->setCurrentIndex(-1);
+}
+
+void ConfigureInputPlayer::LoadProfile() {
+ const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+ if (profile_name.isEmpty()) {
+ return;
+ }
+
+ ApplyConfiguration();
+
+ if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) {
+ QMessageBox::critical(this, tr("Load Input Profile"),
+ tr("Failed to load the input profile \"%1\"").arg(profile_name));
+ UpdateInputProfiles();
+ emit RefreshInputProfiles(player_index);
+ return;
+ }
+
+ LoadConfiguration();
+}
+
+void ConfigureInputPlayer::SaveProfile() {
+ const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+ if (profile_name.isEmpty()) {
+ return;
+ }
+
+ ApplyConfiguration();
+
+ if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) {
+ QMessageBox::critical(this, tr("Save Input Profile"),
+ tr("Failed to save the input profile \"%1\"").arg(profile_name));
+ UpdateInputProfiles();
+ emit RefreshInputProfiles(player_index);
+ return;
+ }
+}
+
+void ConfigureInputPlayer::UpdateInputProfiles() {
+ ui->comboProfiles->clear();
+
+ for (const auto& profile_name : profiles->GetInputProfileNames()) {
+ ui->comboProfiles->addItem(QString::fromStdString(profile_name));
+ }
+
+ ui->comboProfiles->setCurrentIndex(-1);
}
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index a25bc3bd9..efe953fbc 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -9,6 +9,7 @@
#include <memory>
#include <optional>
#include <string>
+#include <vector>
#include <QWidget>
@@ -26,6 +27,8 @@ class QString;
class QTimer;
class QWidget;
+class InputProfiles;
+
namespace InputCommon {
class InputSubsystem;
}
@@ -45,14 +48,32 @@ class ConfigureInputPlayer : public QWidget {
public:
explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row,
InputCommon::InputSubsystem* input_subsystem_,
- bool debug = false);
+ InputProfiles* profiles_, bool debug = false);
~ConfigureInputPlayer() override;
/// Save all button configurations to settings file.
void ApplyConfiguration();
+ /**
+ * Attempts to connect the currently selected controller in the HID backend.
+ * This function will not do anything if it is not connected in the frontend.
+ */
+ void TryConnectSelectedController();
+
+ /**
+ * Attempts to disconnect the currently selected controller in the HID backend.
+ * This function will not do anything if the configuration has not changed.
+ */
+ void TryDisconnectSelectedController();
+
+ /// Set the connection state checkbox (used to sync state).
+ void ConnectPlayer(bool connected);
+
/// Update the input devices combobox.
- void UpdateInputDevices();
+ void UpdateInputDeviceCombobox();
+
+ /// Updates the list of controller profiles.
+ void UpdateInputProfiles();
/// Restore all buttons to their default values.
void RestoreDefaults();
@@ -60,9 +81,6 @@ public:
/// Clear all input configuration.
void ClearAll();
- /// Set the connection state checkbox (used to sync state).
- void ConnectPlayer(bool connected);
-
signals:
/// Emitted when this controller is connected by the user.
void Connected(bool connected);
@@ -70,6 +88,12 @@ signals:
void HandheldStateChanged(bool is_handheld);
/// Emitted when the input devices combobox is being refreshed.
void RefreshInputDevices();
+ /**
+ * Emitted when the input profiles combobox is being refreshed.
+ * The player_index represents the current player's index, and the profile combobox
+ * will not be updated for this index as they are already updated by other mechanisms.
+ */
+ void RefreshInputProfiles(std::size_t player_index);
protected:
void showEvent(QShowEvent* event) override;
@@ -82,13 +106,16 @@ private:
void LoadConfiguration();
/// Called when the button was pressed.
- void HandleClick(QPushButton* button,
+ void HandleClick(QPushButton* button, std::size_t button_id,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type);
/// Finish polling and configure input using the input_setter.
void SetPollingResult(const Common::ParamPackage& params, bool abort);
+ /// Checks whether a given input can be accepted.
+ bool IsInputAcceptable(const Common::ParamPackage& params) const;
+
/// Handle mouse button press events.
void mousePressEvent(QMouseEvent* event) override;
@@ -98,8 +125,17 @@ private:
/// Update UI to reflect current configuration.
void UpdateUI();
- /// Update the controller selection combobox
- void UpdateControllerCombobox();
+ /// Sets the available controllers.
+ void SetConnectableControllers();
+
+ /// Gets the Controller Type for a given controller combobox index.
+ Settings::ControllerType GetControllerTypeFromIndex(int index) const;
+
+ /// Gets the controller combobox index for a given Controller Type.
+ int GetIndexFromControllerType(Settings::ControllerType type) const;
+
+ /// Update the available input devices.
+ void UpdateInputDevices();
/// Update the current controller icon.
void UpdateControllerIcon();
@@ -107,9 +143,30 @@ private:
/// Hides and disables controller settings based on the current controller type.
void UpdateControllerAvailableButtons();
+ /// Disables controller settings based on the current controller type.
+ void UpdateControllerEnabledButtons();
+
+ /// Shows or hides motion groupboxes based on the current controller type.
+ void UpdateMotionButtons();
+
+ /// Alters the button names based on the current controller type.
+ void UpdateControllerButtonNames();
+
/// Gets the default controller mapping for this device and auto configures the input to match.
void UpdateMappingWithDefaults();
+ /// Creates a controller profile.
+ void CreateProfile();
+
+ /// Deletes the selected controller profile.
+ void DeleteProfile();
+
+ /// Loads the selected controller profile.
+ void LoadProfile();
+
+ /// Saves the current controller configuration into a selected controller profile.
+ void SaveProfile();
+
std::unique_ptr<Ui::ConfigureInputPlayer> ui;
std::size_t player_index;
@@ -117,9 +174,14 @@ private:
InputCommon::InputSubsystem* input_subsystem;
+ InputProfiles* profiles;
+
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
+ /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum.
+ std::vector<std::pair<int, Settings::ControllerType>> index_controller_type_pairs;
+
static constexpr int PLAYER_COUNT = 8;
std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox;
@@ -128,23 +190,25 @@ private:
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
+ std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param;
static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;
/// Each button input is represented by a QPushButton.
std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
- /// Extra buttons for the modifiers.
- Common::ParamPackage lstick_mod;
- Common::ParamPackage rstick_mod;
/// A group of four QPushButtons represent one analog input. The buttons each represent up,
/// down, left, right, respectively.
std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
analog_map_buttons;
+ /// Each motion input is represented by a QPushButton.
+ std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map;
+
std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_label;
std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_slider;
std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_groupbox;
+ std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_button;
std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_label;
std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_slider;
std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_groupbox;
@@ -154,12 +218,15 @@ private:
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
+ /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once.
+ bool map_analog_stick_accepted{};
+
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
- bool want_keyboard_mouse = false;
+ bool want_keyboard_mouse{};
/// List of physical devices users can map with. If a SDL backed device is selected, then you
- /// can usue this device to get a default mapping.
+ /// can use this device to get a default mapping.
std::vector<Common::ParamPackage> input_devices;
/// Bottom row is where console wide settings are held, and its "owned" by the parent
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index 9bc681894..e76aa484f 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -83,6 +83,12 @@
</property>
<item>
<widget class="QComboBox" name="comboControllerType">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>21</height>
+ </size>
+ </property>
<item>
<property name="text">
<string>Pro Controller</string>
@@ -136,6 +142,12 @@
</property>
<item>
<widget class="QComboBox" name="comboDevices">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>21</height>
+ </size>
+ </property>
<item>
<property name="text">
<string>Any</string>
@@ -152,14 +164,14 @@
<widget class="QPushButton" name="buttonRefreshDevices">
<property name="minimumSize">
<size>
- <width>24</width>
- <height>22</height>
+ <width>21</width>
+ <height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>24</width>
- <height>22</height>
+ <width>21</width>
+ <height>21</height>
</size>
</property>
<property name="styleSheet">
@@ -198,18 +210,25 @@
<number>5</number>
</property>
<item>
- <widget class="QComboBox" name="comboProfiles"/>
+ <widget class="QComboBox" name="comboProfiles">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>21</height>
+ </size>
+ </property>
+ </widget>
</item>
<item>
<widget class="QPushButton" name="buttonProfilesSave">
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Save</string>
@@ -220,12 +239,12 @@
<widget class="QPushButton" name="buttonProfilesNew">
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>New</string>
@@ -236,12 +255,12 @@
<widget class="QPushButton" name="buttonProfilesDelete">
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Delete</string>
@@ -393,18 +412,18 @@
<widget class="QPushButton" name="buttonLStickUp">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Up</string>
@@ -463,18 +482,18 @@
<widget class="QPushButton" name="buttonLStickLeft">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Left</string>
@@ -512,18 +531,18 @@
<widget class="QPushButton" name="buttonLStickRight">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Right</string>
@@ -594,18 +613,18 @@
<widget class="QPushButton" name="buttonLStickDown">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Down</string>
@@ -664,18 +683,18 @@
<widget class="QPushButton" name="buttonLStick">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Pressed</string>
@@ -713,18 +732,18 @@
<widget class="QPushButton" name="buttonLStickMod">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Modifier</string>
@@ -759,13 +778,13 @@
<widget class="QSpinBox" name="spinboxLStickRange">
<property name="minimumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -966,18 +985,18 @@
<widget class="QPushButton" name="buttonDpadUp">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Up</string>
@@ -1036,18 +1055,18 @@
<widget class="QPushButton" name="buttonDpadLeft">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Left</string>
@@ -1085,18 +1104,18 @@
<widget class="QPushButton" name="buttonDpadRight">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Right</string>
@@ -1167,18 +1186,18 @@
<widget class="QPushButton" name="buttonDpadDown">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Down</string>
@@ -1292,18 +1311,18 @@
<widget class="QPushButton" name="buttonL">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>L</string>
@@ -1341,18 +1360,18 @@
<widget class="QPushButton" name="buttonZL">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>ZL</string>
@@ -1445,18 +1464,18 @@
<widget class="QPushButton" name="buttonMinus">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Minus</string>
@@ -1494,18 +1513,18 @@
<widget class="QPushButton" name="buttonScreenshot">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Capture</string>
@@ -1564,18 +1583,18 @@
<widget class="QPushButton" name="buttonPlus">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Plus</string>
@@ -1613,18 +1632,18 @@
<widget class="QPushButton" name="buttonHome">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Home</string>
@@ -1717,18 +1736,18 @@
<widget class="QPushButton" name="buttonR">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>R</string>
@@ -1766,18 +1785,18 @@
<widget class="QPushButton" name="buttonZR">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>ZR</string>
@@ -1870,18 +1889,18 @@
<widget class="QPushButton" name="buttonSL">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>SL</string>
@@ -1919,18 +1938,18 @@
<widget class="QPushButton" name="buttonSR">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>SR</string>
@@ -1945,44 +1964,47 @@
</item>
</layout>
</item>
- <item>
- <widget class="QFrame" name="controllerFrame">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
- <property name="styleSheet">
- <string notr="true">image: url(:/controller/pro);</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- </layout>
- </widget>
- </item>
+ <item>
+ <widget class="PlayerControlPreview" name="controllerFrame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">image: url(:/controller/pro);</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
<item>
<layout class="QHBoxLayout" name="miscButtons">
<property name="spacing">
<number>3</number>
</property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
<item>
<spacer name="horizontalSpacerMiscButtons1">
<property name="orientation">
@@ -1990,21 +2012,119 @@
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
- <height>0</height>
+ <width>20</width>
+ <height>20</height>
</size>
</property>
</spacer>
</item>
<item>
+ <widget class="QGroupBox" name="buttonMotionLeftGroup">
+ <property name="title">
+ <string>Motion 1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonMotionLeft">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="buttonMotionRightGroup">
+ <property name="title">
+ <string>Motion 2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonMotionRight">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="horizontalSpacerMiscButtons4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
- <height>0</height>
+ <width>20</width>
+ <height>20</height>
</size>
</property>
</spacer>
@@ -2124,18 +2244,18 @@
<widget class="QPushButton" name="buttonX">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>X</string>
@@ -2194,18 +2314,18 @@
<widget class="QPushButton" name="buttonY">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Y</string>
@@ -2243,18 +2363,18 @@
<widget class="QPushButton" name="buttonA">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>A</string>
@@ -2325,18 +2445,18 @@
<widget class="QPushButton" name="buttonB">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>B</string>
@@ -2479,18 +2599,18 @@
<widget class="QPushButton" name="buttonRStickUp">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Up</string>
@@ -2549,18 +2669,18 @@
<widget class="QPushButton" name="buttonRStickLeft">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Left</string>
@@ -2598,18 +2718,18 @@
<widget class="QPushButton" name="buttonRStickRight">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Right</string>
@@ -2680,18 +2800,18 @@
<widget class="QPushButton" name="buttonRStickDown">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Down</string>
@@ -2750,18 +2870,18 @@
<widget class="QPushButton" name="buttonRStick">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Pressed</string>
@@ -2799,18 +2919,18 @@
<widget class="QPushButton" name="buttonRStickMod">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
- <string notr="true">min-width: 55px;</string>
+ <string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Modifier</string>
@@ -2845,13 +2965,13 @@
<widget class="QSpinBox" name="spinboxRStickRange">
<property name="minimumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>55</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -2967,6 +3087,14 @@
</item>
</layout>
</widget>
+ <customwidgets>
+ <customwidget>
+ <class>PlayerControlPreview</class>
+ <extends>QFrame</extends>
+ <header>yuzu/configuration/configure_input_player_widget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
<resources>
<include location="../../../dist/icons/controller/controller.qrc"/>
</resources>
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
new file mode 100644
index 000000000..61ba91cef
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -0,0 +1,2732 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <QMenu>
+#include <QPainter>
+#include <QTimer>
+#include "yuzu/configuration/configure_input_player_widget.h"
+
+PlayerControlPreview::PlayerControlPreview(QWidget* parent) : QFrame(parent) {
+ UpdateColors();
+ QTimer* timer = new QTimer(this);
+ connect(timer, &QTimer::timeout, this, QOverload<>::of(&PlayerControlPreview::UpdateInput));
+
+ // refresh at 60hz
+ timer->start(16);
+}
+
+PlayerControlPreview::~PlayerControlPreview() = default;
+
+void PlayerControlPreview::SetPlayerInput(std::size_t index, const ButtonParam& buttons_param,
+ const AnalogParam& analogs_param) {
+ player_index = index;
+ Settings::ButtonsRaw buttonss;
+ Settings::AnalogsRaw analogs;
+ std::transform(buttons_param.begin(), buttons_param.end(), buttonss.begin(),
+ [](const Common::ParamPackage& param) { return param.Serialize(); });
+ std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
+ [](const Common::ParamPackage& param) { return param.Serialize(); });
+
+ std::transform(buttonss.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+ buttonss.begin() + Settings::NativeButton::BUTTON_NS_END, buttons.begin(),
+ Input::CreateDevice<Input::ButtonDevice>);
+ std::transform(analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
+ analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks.begin(),
+ Input::CreateDevice<Input::AnalogDevice>);
+ UpdateColors();
+}
+void PlayerControlPreview::SetPlayerInputRaw(std::size_t index,
+ const Settings::ButtonsRaw& buttons_,
+ Settings::AnalogsRaw analogs_) {
+ player_index = index;
+ std::transform(buttons_.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+ buttons_.begin() + Settings::NativeButton::BUTTON_NS_END, buttons.begin(),
+ Input::CreateDevice<Input::ButtonDevice>);
+ std::transform(analogs_.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
+ analogs_.begin() + Settings::NativeAnalog::STICK_HID_END, sticks.begin(),
+ Input::CreateDevice<Input::AnalogDevice>);
+ UpdateColors();
+}
+
+PlayerControlPreview::LedPattern PlayerControlPreview::GetColorPattern(std::size_t index,
+ bool player_on) {
+ if (!player_on) {
+ return {0, 0, 0, 0};
+ }
+
+ switch (index) {
+ case 0:
+ return {1, 0, 0, 0};
+ case 1:
+ return {1, 1, 0, 0};
+ case 2:
+ return {1, 1, 1, 0};
+ case 3:
+ return {1, 1, 1, 1};
+ case 4:
+ return {1, 0, 0, 1};
+ case 5:
+ return {1, 0, 1, 0};
+ case 6:
+ return {1, 0, 1, 1};
+ case 7:
+ return {0, 1, 1, 0};
+ default:
+ return {0, 0, 0, 0};
+ }
+}
+
+void PlayerControlPreview::SetConnectedStatus(bool checked) {
+ LedPattern led_pattern = GetColorPattern(player_index, checked);
+
+ led_color[0] = led_pattern.position1 ? colors.led_on : colors.led_off;
+ led_color[1] = led_pattern.position2 ? colors.led_on : colors.led_off;
+ led_color[2] = led_pattern.position3 ? colors.led_on : colors.led_off;
+ led_color[3] = led_pattern.position4 ? colors.led_on : colors.led_off;
+}
+
+void PlayerControlPreview::SetControllerType(const Settings::ControllerType type) {
+ controller_type = type;
+ UpdateColors();
+}
+
+void PlayerControlPreview::BeginMappingButton(std::size_t index) {
+ button_mapping_index = index;
+ mapping_active = true;
+}
+
+void PlayerControlPreview::BeginMappingAnalog(std::size_t index) {
+ button_mapping_index = Settings::NativeButton::LStick + index;
+ analog_mapping_index = index;
+ mapping_active = true;
+}
+
+void PlayerControlPreview::EndMapping() {
+ button_mapping_index = Settings::NativeButton::BUTTON_NS_END;
+ analog_mapping_index = Settings::NativeAnalog::NumAnalogs;
+ mapping_active = false;
+ blink_counter = 0;
+}
+
+void PlayerControlPreview::UpdateColors() {
+ if (QIcon::themeName().contains(QStringLiteral("dark")) ||
+ QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ colors.primary = QColor(204, 204, 204);
+ colors.button = QColor(35, 38, 41);
+ colors.button2 = QColor(26, 27, 30);
+ colors.slider_arrow = QColor(14, 15, 18);
+ colors.font2 = QColor(255, 255, 255);
+ colors.indicator = QColor(170, 238, 255);
+ colors.deadzone = QColor(204, 136, 136);
+ colors.slider_button = colors.button;
+ }
+
+ if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+ colors.outline = QColor(160, 160, 160);
+ } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+ colors.outline = QColor(145, 145, 145);
+ } else {
+ colors.outline = QColor(0, 0, 0);
+ colors.primary = QColor(225, 225, 225);
+ colors.button = QColor(109, 111, 114);
+ colors.button2 = QColor(109, 111, 114);
+ colors.button2 = QColor(77, 80, 84);
+ colors.slider_arrow = QColor(65, 68, 73);
+ colors.font2 = QColor(0, 0, 0);
+ colors.indicator = QColor(0, 0, 200);
+ colors.deadzone = QColor(170, 0, 0);
+ colors.slider_button = QColor(153, 149, 149);
+ }
+
+ // Constant colors
+ colors.highlight = QColor(170, 0, 0);
+ colors.highlight2 = QColor(119, 0, 0);
+ colors.slider = QColor(103, 106, 110);
+ colors.transparent = QColor(0, 0, 0, 0);
+ colors.font = QColor(255, 255, 255);
+ colors.led_on = QColor(255, 255, 0);
+ colors.led_off = QColor(170, 238, 255);
+
+ colors.left = colors.primary;
+ colors.right = colors.primary;
+ // Possible alternative to set colors from settings
+ // colors.left = QColor(Settings::values.players.GetValue()[player_index].body_color_left);
+ // colors.right = QColor(Settings::values.players.GetValue()[player_index].body_color_right);
+}
+
+void PlayerControlPreview::UpdateInput() {
+ bool input_changed = false;
+ const auto& button_state = buttons;
+ for (std::size_t index = 0; index < button_values.size(); ++index) {
+ bool value = false;
+ if (index < Settings::NativeButton::BUTTON_NS_END) {
+ value = button_state[index]->GetStatus();
+ }
+ bool blink = mapping_active && index == button_mapping_index;
+ if (analog_mapping_index == Settings::NativeAnalog::NUM_STICKS_HID) {
+ blink &= blink_counter > 25;
+ }
+ if (button_values[index] != value || blink) {
+ input_changed = true;
+ }
+ button_values[index] = value || blink;
+ }
+
+ const auto& analog_state = sticks;
+ for (std::size_t index = 0; index < axis_values.size(); ++index) {
+ const auto [stick_x_f, stick_y_f] = analog_state[index]->GetStatus();
+ const auto [stick_x_rf, stick_y_rf] = analog_state[index]->GetRawStatus();
+
+ if (static_cast<int>(stick_x_rf * 45) !=
+ static_cast<int>(axis_values[index].raw_value.x() * 45) ||
+ static_cast<int>(-stick_y_rf * 45) !=
+ static_cast<int>(axis_values[index].raw_value.y() * 45)) {
+ input_changed = true;
+ }
+
+ axis_values[index].properties = analog_state[index]->GetAnalogProperties();
+ axis_values[index].value = QPointF(stick_x_f, -stick_y_f);
+ axis_values[index].raw_value = QPointF(stick_x_rf, -stick_y_rf);
+
+ const bool blink_analog = mapping_active && index == analog_mapping_index;
+ if (blink_analog) {
+ input_changed = true;
+ axis_values[index].value =
+ QPointF(blink_counter < 25 ? -blink_counter / 25.0f : 0,
+ blink_counter > 25 ? -(blink_counter - 25) / 25.0f : 0);
+ }
+ }
+
+ if (input_changed) {
+ update();
+ }
+
+ if (mapping_active) {
+ blink_counter = (blink_counter + 1) % 50;
+ }
+}
+
+void PlayerControlPreview::paintEvent(QPaintEvent* event) {
+ QFrame::paintEvent(event);
+ QPainter p(this);
+ p.setRenderHint(QPainter::Antialiasing);
+ const QPointF center = rect().center();
+
+ switch (controller_type) {
+ case Settings::ControllerType::Handheld:
+ DrawHandheldController(p, center);
+ break;
+ case Settings::ControllerType::DualJoyconDetached:
+ DrawDualController(p, center);
+ break;
+ case Settings::ControllerType::LeftJoycon:
+ DrawLeftController(p, center);
+ break;
+ case Settings::ControllerType::RightJoycon:
+ DrawRightController(p, center);
+ break;
+ case Settings::ControllerType::GameCube:
+ DrawGCController(p, center);
+ break;
+ case Settings::ControllerType::ProController:
+ default:
+ DrawProController(p, center);
+ break;
+ }
+}
+
+void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center) {
+ {
+ using namespace Settings::NativeButton;
+
+ // Sideview left joystick
+ DrawJoystickSideview(p, center + QPoint(142, -69),
+ -axis_values[Settings::NativeAnalog::LStick].value.y(), 1.15f,
+ button_values[LStick]);
+
+ // Topview D-pad buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRoundButton(p, center + QPoint(-163, -21), button_values[DLeft], 11, 5, Direction::Up);
+ DrawRoundButton(p, center + QPoint(-117, -21), button_values[DRight], 11, 5, Direction::Up);
+
+ // Topview left joystick
+ DrawJoystickSideview(p, center + QPointF(-140.5f, -28),
+ -axis_values[Settings::NativeAnalog::LStick].value.x() + 15.0f, 1.15f,
+ button_values[LStick]);
+
+ // Topview minus button
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRoundButton(p, center + QPoint(-111, -22), button_values[Minus], 8, 4, Direction::Up,
+ 1);
+
+ // Left trigger
+ DrawLeftTriggers(p, center, button_values[L]);
+ DrawRoundButton(p, center + QPoint(151, -146), button_values[L], 8, 4, Direction::Down);
+ DrawLeftZTriggers(p, center, button_values[ZL]);
+
+ // Sideview D-pad buttons
+ DrawRoundButton(p, center + QPoint(135, 14), button_values[DLeft], 5, 11, Direction::Right);
+ DrawRoundButton(p, center + QPoint(135, 36), button_values[DDown], 5, 11, Direction::Right);
+ DrawRoundButton(p, center + QPoint(135, -10), button_values[DUp], 5, 11, Direction::Right);
+ DrawRoundButton(p, center + QPoint(135, 14), button_values[DRight], 5, 11,
+ Direction::Right);
+ DrawRoundButton(p, center + QPoint(135, 71), button_values[Screenshot], 3, 8,
+ Direction::Right, 1);
+
+ // Sideview minus button
+ DrawRoundButton(p, center + QPoint(135, -118), button_values[Minus], 4, 2.66f,
+ Direction::Right, 1);
+
+ // Sideview SL and SR buttons
+ button_color = colors.slider_button;
+ DrawRoundButton(p, center + QPoint(59, 52), button_values[SR], 5, 12, Direction::Left);
+ DrawRoundButton(p, center + QPoint(59, -69), button_values[SL], 5, 12, Direction::Left);
+
+ DrawLeftBody(p, center);
+
+ // Left trigger top view
+ DrawLeftTriggersTopView(p, center, button_values[L]);
+ DrawLeftZTriggersTopView(p, center, button_values[ZL]);
+ }
+
+ {
+ // Draw joysticks
+ using namespace Settings::NativeAnalog;
+ DrawJoystick(p, center + QPointF(9, -69) + (axis_values[LStick].value * 8), 1.8f,
+ button_values[Settings::NativeButton::LStick]);
+ DrawRawJoystick(p, center + QPointF(-140, 90), axis_values[LStick].raw_value,
+ axis_values[LStick].properties);
+ }
+
+ using namespace Settings::NativeButton;
+
+ // D-pad constants
+ const QPointF dpad_center = center + QPoint(9, 14);
+ constexpr int dpad_distance = 23;
+ constexpr int dpad_radius = 11;
+ constexpr float dpad_arrow_size = 1.2f;
+
+ // D-pad buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawCircleButton(p, dpad_center + QPoint(dpad_distance, 0), button_values[DRight], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(0, dpad_distance), button_values[DDown], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(0, -dpad_distance), button_values[DUp], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(-dpad_distance, 0), button_values[DLeft], dpad_radius);
+
+ // D-pad arrows
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawArrow(p, dpad_center + QPoint(dpad_distance, 0), Direction::Right, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(0, dpad_distance), Direction::Down, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(0, -dpad_distance), Direction::Up, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(-dpad_distance, 0), Direction::Left, dpad_arrow_size);
+
+ // SR and SL buttons
+ p.setPen(colors.outline);
+ button_color = colors.slider_button;
+ DrawRoundButton(p, center + QPoint(155, 52), button_values[SR], 5.2f, 12, Direction::None, 4);
+ DrawRoundButton(p, center + QPoint(155, -69), button_values[SL], 5.2f, 12, Direction::None, 4);
+
+ // SR and SL text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(155, 52), Symbol::SR, 1.0f);
+ DrawSymbol(p, center + QPointF(155, -69), Symbol::SL, 1.0f);
+
+ // Minus button
+ button_color = colors.button;
+ DrawMinusButton(p, center + QPoint(39, -118), button_values[Minus], 16);
+
+ // Screenshot button
+ DrawRoundButton(p, center + QPoint(26, 71), button_values[Screenshot], 8, 8);
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawCircle(p, center + QPoint(26, 71), 5);
+}
+
+void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center) {
+ {
+ using namespace Settings::NativeButton;
+
+ // Sideview right joystick
+ DrawJoystickSideview(p, center + QPoint(173 - 315, 11),
+ axis_values[Settings::NativeAnalog::RStick].value.y() + 10.0f, 1.15f,
+ button_values[Settings::NativeButton::RStick]);
+
+ // Topview face buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRoundButton(p, center + QPoint(163, -21), button_values[A], 11, 5, Direction::Up);
+ DrawRoundButton(p, center + QPoint(117, -21), button_values[Y], 11, 5, Direction::Up);
+
+ // Topview right joystick
+ DrawJoystickSideview(p, center + QPointF(140, -28),
+ -axis_values[Settings::NativeAnalog::RStick].value.x() + 15.0f, 1.15f,
+ button_values[RStick]);
+
+ // Topview plus button
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRoundButton(p, center + QPoint(111, -22), button_values[Plus], 8, 4, Direction::Up, 1);
+ DrawRoundButton(p, center + QPoint(111, -22), button_values[Plus], 2.66f, 4, Direction::Up,
+ 1);
+
+ // Right trigger
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRightTriggers(p, center, button_values[R]);
+ DrawRoundButton(p, center + QPoint(-151, -146), button_values[R], 8, 4, Direction::Down);
+ DrawRightZTriggers(p, center, button_values[ZR]);
+
+ // Sideview face buttons
+ DrawRoundButton(p, center + QPoint(-135, -73), button_values[A], 5, 11, Direction::Left);
+ DrawRoundButton(p, center + QPoint(-135, -50), button_values[B], 5, 11, Direction::Left);
+ DrawRoundButton(p, center + QPoint(-135, -95), button_values[X], 5, 11, Direction::Left);
+ DrawRoundButton(p, center + QPoint(-135, -73), button_values[Y], 5, 11, Direction::Left);
+
+ // Sideview home and plus button
+ DrawRoundButton(p, center + QPoint(-135, 66), button_values[Home], 3, 12, Direction::Left);
+ DrawRoundButton(p, center + QPoint(-135, -118), button_values[Plus], 4, 8, Direction::Left,
+ 1);
+ DrawRoundButton(p, center + QPoint(-135, -118), button_values[Plus], 4, 2.66f,
+ Direction::Left, 1);
+
+ // Sideview SL and SR buttons
+ button_color = colors.slider_button;
+ DrawRoundButton(p, center + QPoint(-59, 52), button_values[SL], 5, 11, Direction::Right);
+ DrawRoundButton(p, center + QPoint(-59, -69), button_values[SR], 5, 11, Direction::Right);
+
+ DrawRightBody(p, center);
+
+ // Right trigger top view
+ DrawRightTriggersTopView(p, center, button_values[R]);
+ DrawRightZTriggersTopView(p, center, button_values[ZR]);
+ }
+
+ {
+ // Draw joysticks
+ using namespace Settings::NativeAnalog;
+ DrawJoystick(p, center + QPointF(-9, 11) + (axis_values[RStick].value * 8), 1.8f,
+ button_values[Settings::NativeButton::RStick]);
+ DrawRawJoystick(p, center + QPointF(140, 90), axis_values[RStick].raw_value,
+ axis_values[RStick].properties);
+ }
+
+ using namespace Settings::NativeButton;
+
+ // Face buttons constants
+ const QPointF face_center = center + QPoint(-9, -73);
+ constexpr int face_distance = 23;
+ constexpr int face_radius = 11;
+ constexpr float text_size = 1.1f;
+
+ // Face buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius);
+ DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius);
+ DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius);
+ DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius);
+
+ // Face buttons text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size);
+ DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size);
+ DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size);
+ DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size);
+
+ // SR and SL buttons
+ p.setPen(colors.outline);
+ button_color = colors.slider_button;
+ DrawRoundButton(p, center + QPoint(-155, 52), button_values[SL], 5, 12, Direction::None, 4.0f);
+ DrawRoundButton(p, center + QPoint(-155, -69), button_values[SR], 5, 12, Direction::None, 4.0f);
+
+ // SR and SL text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ p.rotate(-180);
+ DrawSymbol(p, QPointF(-center.x(), -center.y()) + QPointF(155, 69), Symbol::SR, 1.0f);
+ DrawSymbol(p, QPointF(-center.x(), -center.y()) + QPointF(155, -52), Symbol::SL, 1.0f);
+ p.rotate(180);
+
+ // Plus Button
+ DrawPlusButton(p, center + QPoint(-40, -118), button_values[Plus], 16);
+
+ // Home Button
+ p.setPen(colors.outline);
+ button_color = colors.slider_button;
+ DrawCircleButton(p, center + QPoint(-26, 66), button_values[Home], 12);
+ button_color = colors.button;
+ DrawCircleButton(p, center + QPoint(-26, 66), button_values[Home], 9);
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPoint(-26, 66), Symbol::House, 5);
+}
+
+void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center) {
+ {
+ using namespace Settings::NativeButton;
+
+ // Left/Right trigger
+ DrawDualTriggers(p, center, button_values[L], button_values[R]);
+
+ // Topview face buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRoundButton(p, center + QPoint(200, -71), button_values[A], 10, 5, Direction::Up);
+ DrawRoundButton(p, center + QPoint(160, -71), button_values[Y], 10, 5, Direction::Up);
+
+ // Topview right joystick
+ DrawJoystickSideview(p, center + QPointF(180, -78),
+ -axis_values[Settings::NativeAnalog::RStick].value.x() + 15.0f, 1,
+ button_values[RStick]);
+
+ // Topview plus button
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRoundButton(p, center + QPoint(154, -72), button_values[Plus], 7, 4, Direction::Up, 1);
+ DrawRoundButton(p, center + QPoint(154, -72), button_values[Plus], 2.33f, 4, Direction::Up,
+ 1);
+
+ // Topview D-pad buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRoundButton(p, center + QPoint(-200, -71), button_values[DLeft], 10, 5, Direction::Up);
+ DrawRoundButton(p, center + QPoint(-160, -71), button_values[DRight], 10, 5, Direction::Up);
+
+ // Topview left joystick
+ DrawJoystickSideview(p, center + QPointF(-180.5f, -78),
+ -axis_values[Settings::NativeAnalog::LStick].value.x() + 15.0f, 1,
+ button_values[LStick]);
+
+ // Topview minus button
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawRoundButton(p, center + QPoint(-154, -72), button_values[Minus], 7, 4, Direction::Up,
+ 1);
+
+ DrawDualBody(p, center);
+
+ // Right trigger top view
+ DrawDualTriggersTopView(p, center, button_values[L], button_values[R]);
+ DrawDualZTriggersTopView(p, center, button_values[ZL], button_values[ZR]);
+ }
+
+ {
+ // Draw joysticks
+ using namespace Settings::NativeAnalog;
+ const auto& l_stick = axis_values[LStick];
+ const auto l_button = button_values[Settings::NativeButton::LStick];
+ const auto& r_stick = axis_values[RStick];
+ const auto r_button = button_values[Settings::NativeButton::RStick];
+
+ DrawJoystick(p, center + QPointF(-65, -65) + (l_stick.value * 7), 1.62f, l_button);
+ DrawJoystick(p, center + QPointF(65, 12) + (r_stick.value * 7), 1.62f, r_button);
+ DrawRawJoystick(p, center + QPointF(-180, 90), l_stick.raw_value, l_stick.properties);
+ DrawRawJoystick(p, center + QPointF(180, 90), r_stick.raw_value, r_stick.properties);
+ }
+
+ using namespace Settings::NativeButton;
+
+ // Face buttons constants
+ const QPointF face_center = center + QPoint(65, -65);
+ constexpr int face_distance = 20;
+ constexpr int face_radius = 10;
+ constexpr float text_size = 1.0f;
+
+ // Face buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius);
+ DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius);
+ DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius);
+ DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius);
+
+ // Face buttons text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size);
+ DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size);
+ DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size);
+ DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size);
+
+ // D-pad constants
+ const QPointF dpad_center = center + QPoint(-65, 12);
+ constexpr int dpad_distance = 20;
+ constexpr int dpad_radius = 10;
+ constexpr float dpad_arrow_size = 1.1f;
+
+ // D-pad buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawCircleButton(p, dpad_center + QPoint(dpad_distance, 0), button_values[DRight], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(0, dpad_distance), button_values[DDown], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(0, -dpad_distance), button_values[DUp], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(-dpad_distance, 0), button_values[DLeft], dpad_radius);
+
+ // D-pad arrows
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawArrow(p, dpad_center + QPoint(dpad_distance, 0), Direction::Right, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(0, dpad_distance), Direction::Down, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(0, -dpad_distance), Direction::Up, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(-dpad_distance, 0), Direction::Left, dpad_arrow_size);
+
+ // Minus and Plus button
+ button_color = colors.button;
+ DrawMinusButton(p, center + QPoint(-39, -106), button_values[Minus], 14);
+ DrawPlusButton(p, center + QPoint(39, -106), button_values[Plus], 14);
+
+ // Screenshot button
+ p.setPen(colors.outline);
+ DrawRoundButton(p, center + QPoint(-52, 63), button_values[Screenshot], 8, 8);
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawCircle(p, center + QPoint(-52, 63), 5);
+
+ // Home Button
+ p.setPen(colors.outline);
+ button_color = colors.slider_button;
+ DrawCircleButton(p, center + QPoint(50, 60), button_values[Home], 11);
+ button_color = colors.button;
+ DrawCircleButton(p, center + QPoint(50, 60), button_values[Home], 8.5f);
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPoint(50, 60), Symbol::House, 4.2f);
+}
+
+void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF center) {
+ DrawHandheldTriggers(p, center, button_values[Settings::NativeButton::L],
+ button_values[Settings::NativeButton::R]);
+ DrawHandheldBody(p, center);
+ {
+ // Draw joysticks
+ using namespace Settings::NativeAnalog;
+ const auto& l_stick = axis_values[LStick];
+ const auto l_button = button_values[Settings::NativeButton::LStick];
+ const auto& r_stick = axis_values[RStick];
+ const auto r_button = button_values[Settings::NativeButton::RStick];
+
+ DrawJoystick(p, center + QPointF(-171, -41) + (l_stick.value * 4), 1.0f, l_button);
+ DrawJoystick(p, center + QPointF(171, 8) + (r_stick.value * 4), 1.0f, r_button);
+ DrawRawJoystick(p, center + QPointF(-50, 0), l_stick.raw_value, l_stick.properties);
+ DrawRawJoystick(p, center + QPointF(50, 0), r_stick.raw_value, r_stick.properties);
+ }
+
+ using namespace Settings::NativeButton;
+
+ // Face buttons constants
+ const QPointF face_center = center + QPoint(171, -41);
+ constexpr float face_distance = 12.8f;
+ constexpr float face_radius = 6.4f;
+ constexpr float text_size = 0.6f;
+
+ // Face buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius);
+ DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius);
+ DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius);
+ DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius);
+
+ // Face buttons text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size);
+ DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size);
+ DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size);
+ DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size);
+
+ // D-pad constants
+ const QPointF dpad_center = center + QPoint(-171, 8);
+ constexpr float dpad_distance = 12.8f;
+ constexpr float dpad_radius = 6.4f;
+ constexpr float dpad_arrow_size = 0.68f;
+
+ // D-pad buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawCircleButton(p, dpad_center + QPoint(dpad_distance, 0), button_values[DRight], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(0, dpad_distance), button_values[DDown], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(0, -dpad_distance), button_values[DUp], dpad_radius);
+ DrawCircleButton(p, dpad_center + QPoint(-dpad_distance, 0), button_values[DLeft], dpad_radius);
+
+ // D-pad arrows
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawArrow(p, dpad_center + QPoint(dpad_distance, 0), Direction::Right, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(0, dpad_distance), Direction::Down, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(0, -dpad_distance), Direction::Up, dpad_arrow_size);
+ DrawArrow(p, dpad_center + QPoint(-dpad_distance, 0), Direction::Left, dpad_arrow_size);
+
+ // ZL and ZR buttons
+ p.setPen(colors.outline);
+ DrawTriggerButton(p, center + QPoint(-210, -130), Direction::Left, button_values[ZL]);
+ DrawTriggerButton(p, center + QPoint(210, -130), Direction::Right, button_values[ZR]);
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, center + QPoint(-210, -130), Symbol::ZL, 1.5f);
+ DrawSymbol(p, center + QPoint(210, -130), Symbol::ZR, 1.5f);
+
+ // Minus and Plus button
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawMinusButton(p, center + QPoint(-155, -67), button_values[Minus], 8);
+ DrawPlusButton(p, center + QPoint(155, -67), button_values[Plus], 8);
+
+ // Screenshot button
+ p.setPen(colors.outline);
+ DrawRoundButton(p, center + QPoint(-162, 39), button_values[Screenshot], 5, 5);
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawCircle(p, center + QPoint(-162, 39), 3);
+
+ // Home Button
+ p.setPen(colors.outline);
+ button_color = colors.slider_button;
+ DrawCircleButton(p, center + QPoint(161, 37), button_values[Home], 7);
+ button_color = colors.button;
+ DrawCircleButton(p, center + QPoint(161, 37), button_values[Home], 5);
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPoint(161, 37), Symbol::House, 2.75f);
+}
+
+void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center) {
+ DrawProTriggers(p, center, button_values[Settings::NativeButton::L],
+ button_values[Settings::NativeButton::R]);
+ DrawProBody(p, center);
+ {
+ // Draw joysticks
+ using namespace Settings::NativeAnalog;
+ DrawProJoystick(p, center + QPointF(-111, -55), axis_values[LStick].value, 11,
+ button_values[Settings::NativeButton::LStick]);
+ DrawProJoystick(p, center + QPointF(51, 0), axis_values[RStick].value, 11,
+ button_values[Settings::NativeButton::RStick]);
+ DrawRawJoystick(p, center + QPointF(-50, 105), axis_values[LStick].raw_value,
+ axis_values[LStick].properties);
+ DrawRawJoystick(p, center + QPointF(50, 105), axis_values[RStick].raw_value,
+ axis_values[RStick].properties);
+ }
+
+ using namespace Settings::NativeButton;
+
+ // Face buttons constants
+ const QPointF face_center = center + QPoint(105, -56);
+ constexpr int face_distance = 31;
+ constexpr int face_radius = 15;
+ constexpr float text_size = 1.5f;
+
+ // Face buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawCircleButton(p, face_center + QPoint(face_distance, 0), button_values[A], face_radius);
+ DrawCircleButton(p, face_center + QPoint(0, face_distance), button_values[B], face_radius);
+ DrawCircleButton(p, face_center + QPoint(0, -face_distance), button_values[X], face_radius);
+ DrawCircleButton(p, face_center + QPoint(-face_distance, 0), button_values[Y], face_radius);
+
+ // Face buttons text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, face_center + QPoint(face_distance, 0), Symbol::A, text_size);
+ DrawSymbol(p, face_center + QPoint(0, face_distance), Symbol::B, text_size);
+ DrawSymbol(p, face_center + QPoint(0, -face_distance), Symbol::X, text_size);
+ DrawSymbol(p, face_center + QPoint(-face_distance, 1), Symbol::Y, text_size);
+
+ // D-pad buttons
+ const QPointF dpad_postion = center + QPoint(-61, 0);
+ DrawArrowButton(p, dpad_postion, Direction::Up, button_values[DUp]);
+ DrawArrowButton(p, dpad_postion, Direction::Left, button_values[DLeft]);
+ DrawArrowButton(p, dpad_postion, Direction::Right, button_values[DRight]);
+ DrawArrowButton(p, dpad_postion, Direction::Down, button_values[DDown]);
+ DrawArrowButtonOutline(p, dpad_postion);
+
+ // ZL and ZR buttons
+ p.setPen(colors.outline);
+ DrawTriggerButton(p, center + QPoint(-210, -130), Direction::Left, button_values[ZL]);
+ DrawTriggerButton(p, center + QPoint(210, -130), Direction::Right, button_values[ZR]);
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, center + QPoint(-210, -130), Symbol::ZL, 1.5f);
+ DrawSymbol(p, center + QPoint(210, -130), Symbol::ZR, 1.5f);
+
+ // Minus and Plus buttons
+ p.setPen(colors.outline);
+ DrawCircleButton(p, center + QPoint(-50, -86), button_values[Minus], 9);
+ DrawCircleButton(p, center + QPoint(50, -86), button_values[Plus], 9);
+
+ // Minus and Plus symbols
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawRectangle(p, center + QPoint(-50, -86), 9, 1.5f);
+ DrawRectangle(p, center + QPoint(50, -86), 9, 1.5f);
+ DrawRectangle(p, center + QPoint(50, -86), 1.5f, 9);
+
+ // Screenshot button
+ p.setPen(colors.outline);
+ DrawRoundButton(p, center + QPoint(-29, -56), button_values[Screenshot], 7, 7);
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawCircle(p, center + QPoint(-29, -56), 4.5f);
+
+ // Home Button
+ p.setPen(colors.outline);
+ button_color = colors.slider_button;
+ DrawCircleButton(p, center + QPoint(29, -56), button_values[Home], 10.0f);
+ button_color = colors.button;
+ DrawCircleButton(p, center + QPoint(29, -56), button_values[Home], 7.1f);
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPoint(29, -56), Symbol::House, 3.9f);
+}
+
+void PlayerControlPreview::DrawGCController(QPainter& p, const QPointF center) {
+ DrawGCTriggers(p, center, button_values[Settings::NativeButton::ZL],
+ button_values[Settings::NativeButton::ZR]);
+ DrawGCButtonZ(p, center, button_values[Settings::NativeButton::R]);
+ DrawGCBody(p, center);
+ {
+ // Draw joysticks
+ using namespace Settings::NativeAnalog;
+ DrawGCJoystick(p, center + QPointF(-111, -44) + (axis_values[LStick].value * 10), false);
+ button_color = colors.button2;
+ DrawCircleButton(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), false,
+ 15);
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, center + QPointF(61, 37) + (axis_values[RStick].value * 9.5f), Symbol::C,
+ 1.0f);
+ DrawRawJoystick(p, center + QPointF(-198, -125), axis_values[LStick].raw_value,
+ axis_values[LStick].properties);
+ DrawRawJoystick(p, center + QPointF(198, -125), axis_values[RStick].raw_value,
+ axis_values[RStick].properties);
+ }
+
+ using namespace Settings::NativeButton;
+
+ // Face buttons constants
+ constexpr float text_size = 1.1f;
+
+ // Face buttons
+ p.setPen(colors.outline);
+ button_color = colors.button;
+ DrawCircleButton(p, center + QPoint(111, -44), button_values[A], 21);
+ DrawCircleButton(p, center + QPoint(70, -23), button_values[B], 13);
+ DrawGCButtonX(p, center, button_values[Settings::NativeButton::X]);
+ DrawGCButtonY(p, center, button_values[Settings::NativeButton::Y]);
+
+ // Face buttons text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, center + QPoint(111, -44), Symbol::A, 1.5f);
+ DrawSymbol(p, center + QPoint(70, -23), Symbol::B, text_size);
+ DrawSymbol(p, center + QPoint(151, -53), Symbol::X, text_size);
+ DrawSymbol(p, center + QPoint(100, -83), Symbol::Y, text_size);
+
+ // D-pad buttons
+ const QPointF dpad_postion = center + QPoint(-61, 37);
+ const float dpad_size = 0.8f;
+ DrawArrowButton(p, dpad_postion, Direction::Up, button_values[DUp], dpad_size);
+ DrawArrowButton(p, dpad_postion, Direction::Left, button_values[DLeft], dpad_size);
+ DrawArrowButton(p, dpad_postion, Direction::Right, button_values[DRight], dpad_size);
+ DrawArrowButton(p, dpad_postion, Direction::Down, button_values[DDown], dpad_size);
+ DrawArrowButtonOutline(p, dpad_postion, dpad_size);
+
+ // Minus and Plus buttons
+ p.setPen(colors.outline);
+ DrawCircleButton(p, center + QPoint(0, -44), button_values[Plus], 8);
+}
+
+constexpr std::array<float, 13 * 2> symbol_a = {
+ -1.085f, -5.2f, 1.085f, -5.2f, 5.085f, 5.0f, 2.785f, 5.0f, 1.785f,
+ 2.65f, -1.785f, 2.65f, -2.785f, 5.0f, -5.085f, 5.0f, -1.4f, 1.0f,
+ 0.0f, -2.8f, 1.4f, 1.0f, -1.4f, 1.0f, -5.085f, 5.0f,
+};
+constexpr std::array<float, 134 * 2> symbol_b = {
+ -4.0f, 0.0f, -4.0f, 0.0f, -4.0f, -0.1f, -3.8f, -5.1f, 1.8f, -5.0f, 2.3f, -4.9f, 2.6f,
+ -4.8f, 2.8f, -4.7f, 2.9f, -4.6f, 3.1f, -4.5f, 3.2f, -4.4f, 3.4f, -4.3f, 3.4f, -4.2f,
+ 3.5f, -4.1f, 3.7f, -4.0f, 3.7f, -3.9f, 3.8f, -3.8f, 3.8f, -3.7f, 3.9f, -3.6f, 3.9f,
+ -3.5f, 4.0f, -3.4f, 4.0f, -3.3f, 4.1f, -3.1f, 4.1f, -3.0f, 4.0f, -2.0f, 4.0f, -1.9f,
+ 3.9f, -1.7f, 3.9f, -1.6f, 3.8f, -1.5f, 3.8f, -1.4f, 3.7f, -1.3f, 3.7f, -1.2f, 3.6f,
+ -1.1f, 3.6f, -1.0f, 3.5f, -0.9f, 3.3f, -0.8f, 3.3f, -0.7f, 3.2f, -0.6f, 3.0f, -0.5f,
+ 2.9f, -0.4f, 2.7f, -0.3f, 2.9f, -0.2f, 3.2f, -0.1f, 3.3f, 0.0f, 3.5f, 0.1f, 3.6f,
+ 0.2f, 3.8f, 0.3f, 3.9f, 0.4f, 4.0f, 0.6f, 4.1f, 0.7f, 4.3f, 0.8f, 4.3f, 0.9f,
+ 4.4f, 1.0f, 4.4f, 1.1f, 4.5f, 1.3f, 4.5f, 1.4f, 4.6f, 1.6f, 4.6f, 1.7f, 4.5f,
+ 2.8f, 4.5f, 2.9f, 4.4f, 3.1f, 4.4f, 3.2f, 4.3f, 3.4f, 4.3f, 3.5f, 4.2f, 3.6f,
+ 4.2f, 3.7f, 4.1f, 3.8f, 4.1f, 3.9f, 4.0f, 4.0f, 3.9f, 4.2f, 3.8f, 4.3f, 3.6f,
+ 4.4f, 3.6f, 4.5f, 3.4f, 4.6f, 3.3f, 4.7f, 3.1f, 4.8f, 2.8f, 4.9f, 2.6f, 5.0f,
+ 2.1f, 5.1f, -4.0f, 5.0f, -4.0f, 4.9f,
+
+ -4.0f, 0.0f, 1.1f, 3.4f, 1.1f, 3.4f, 1.5f, 3.3f, 1.8f, 3.2f, 2.0f, 3.1f, 2.1f,
+ 3.0f, 2.3f, 2.9f, 2.3f, 2.8f, 2.4f, 2.7f, 2.4f, 2.6f, 2.5f, 2.3f, 2.5f, 2.2f,
+ 2.4f, 1.7f, 2.4f, 1.6f, 2.3f, 1.4f, 2.3f, 1.3f, 2.2f, 1.2f, 2.2f, 1.1f, 2.1f,
+ 1.0f, 1.9f, 0.9f, 1.6f, 0.8f, 1.4f, 0.7f, -1.9f, 0.6f, -1.9f, 0.7f, -1.8f, 3.4f,
+ 1.1f, 3.4f, -4.0f, 0.0f,
+
+ 0.3f, -1.1f, 0.3f, -1.1f, 1.3f, -1.2f, 1.5f, -1.3f, 1.8f, -1.4f, 1.8f, -1.5f, 1.9f,
+ -1.6f, 2.0f, -1.8f, 2.0f, -1.9f, 2.1f, -2.0f, 2.1f, -2.1f, 2.0f, -2.7f, 2.0f, -2.8f,
+ 1.9f, -2.9f, 1.9f, -3.0f, 1.8f, -3.1f, 1.6f, -3.2f, 1.6f, -3.3f, 1.3f, -3.4f, -1.9f,
+ -3.3f, -1.9f, -3.2f, -1.8f, -1.0f, 0.2f, -1.1f, 0.3f, -1.1f, -4.0f, 0.0f,
+};
+
+constexpr std::array<float, 9 * 2> symbol_y = {
+ -4.79f, -4.9f, -2.44f, -4.9f, 0.0f, -0.9f, 2.44f, -4.9f, 4.79f,
+ -4.9f, 1.05f, 1.0f, 1.05f, 5.31f, -1.05f, 5.31f, -1.05f, 1.0f,
+
+};
+
+constexpr std::array<float, 12 * 2> symbol_x = {
+ -4.4f, -5.0f, -2.0f, -5.0f, 0.0f, -1.7f, 2.0f, -5.0f, 4.4f, -5.0f, 1.2f, 0.0f,
+ 4.4f, 5.0f, 2.0f, 5.0f, 0.0f, 1.7f, -2.0f, 5.0f, -4.4f, 5.0f, -1.2f, 0.0f,
+
+};
+
+constexpr std::array<float, 7 * 2> symbol_l = {
+ 2.4f, -3.23f, 2.4f, 2.1f, 5.43f, 2.1f, 5.43f, 3.22f, 0.98f, 3.22f, 0.98f, -3.23f, 2.4f, -3.23f,
+};
+
+constexpr std::array<float, 98 * 2> symbol_r = {
+ 1.0f, 0.0f, 1.0f, -0.1f, 1.1f, -3.3f, 4.3f, -3.2f, 5.1f, -3.1f, 5.4f, -3.0f, 5.6f, -2.9f,
+ 5.7f, -2.8f, 5.9f, -2.7f, 5.9f, -2.6f, 6.0f, -2.5f, 6.1f, -2.3f, 6.2f, -2.2f, 6.2f, -2.1f,
+ 6.3f, -2.0f, 6.3f, -1.9f, 6.2f, -0.8f, 6.2f, -0.7f, 6.1f, -0.6f, 6.1f, -0.5f, 6.0f, -0.4f,
+ 6.0f, -0.3f, 5.9f, -0.2f, 5.7f, -0.1f, 5.7f, 0.0f, 5.6f, 0.1f, 5.4f, 0.2f, 5.1f, 0.3f,
+ 4.7f, 0.4f, 4.7f, 0.5f, 4.9f, 0.6f, 5.0f, 0.7f, 5.2f, 0.8f, 5.2f, 0.9f, 5.3f, 1.0f,
+ 5.5f, 1.1f, 5.5f, 1.2f, 5.6f, 1.3f, 5.7f, 1.5f, 5.8f, 1.6f, 5.9f, 1.8f, 6.0f, 1.9f,
+ 6.1f, 2.1f, 6.2f, 2.2f, 6.2f, 2.3f, 6.3f, 2.4f, 6.4f, 2.6f, 6.5f, 2.7f, 6.6f, 2.9f,
+ 6.7f, 3.0f, 6.7f, 3.1f, 6.8f, 3.2f, 6.8f, 3.3f, 5.3f, 3.2f, 5.2f, 3.1f, 5.2f, 3.0f,
+ 5.1f, 2.9f, 5.0f, 2.7f, 4.9f, 2.6f, 4.8f, 2.4f, 4.7f, 2.3f, 4.6f, 2.1f, 4.5f, 2.0f,
+ 4.4f, 1.8f, 4.3f, 1.7f, 4.1f, 1.4f, 4.0f, 1.3f, 3.9f, 1.1f, 3.8f, 1.0f, 3.6f, 0.9f,
+ 3.6f, 0.8f, 3.5f, 0.7f, 3.3f, 0.6f, 2.9f, 0.5f, 2.3f, 0.6f, 2.3f, 0.7f, 2.2f, 3.3f,
+ 1.0f, 3.2f, 1.0f, 3.1f, 1.0f, 0.0f,
+
+ 4.2f, -0.5f, 4.4f, -0.6f, 4.7f, -0.7f, 4.8f, -0.8f, 4.9f, -1.0f, 5.0f, -1.1f, 5.0f, -1.2f,
+ 4.9f, -1.7f, 4.9f, -1.8f, 4.8f, -1.9f, 4.8f, -2.0f, 4.6f, -2.1f, 4.3f, -2.2f, 2.3f, -2.1f,
+ 2.3f, -2.0f, 2.4f, -0.5f, 4.2f, -0.5f, 1.0f, 0.0f,
+};
+
+constexpr std::array<float, 18 * 2> symbol_zl = {
+ -2.6f, -2.13f, -5.6f, -2.13f, -5.6f, -3.23f, -0.8f, -3.23f, -0.8f, -2.13f, -4.4f, 2.12f,
+ -0.7f, 2.12f, -0.7f, 3.22f, -6.0f, 3.22f, -6.0f, 2.12f, 2.4f, -3.23f, 2.4f, 2.1f,
+ 5.43f, 2.1f, 5.43f, 3.22f, 0.98f, 3.22f, 0.98f, -3.23f, 2.4f, -3.23f, -6.0f, 2.12f,
+};
+
+constexpr std::array<float, 57 * 2> symbol_sl = {
+ -3.0f, -3.65f, -2.76f, -4.26f, -2.33f, -4.76f, -1.76f, -5.09f, -1.13f, -5.26f, -0.94f,
+ -4.77f, -0.87f, -4.11f, -1.46f, -3.88f, -1.91f, -3.41f, -2.05f, -2.78f, -1.98f, -2.13f,
+ -1.59f, -1.61f, -0.96f, -1.53f, -0.56f, -2.04f, -0.38f, -2.67f, -0.22f, -3.31f, 0.0f,
+ -3.93f, 0.34f, -4.49f, 0.86f, -4.89f, 1.49f, -5.05f, 2.14f, -4.95f, 2.69f, -4.6f,
+ 3.07f, -4.07f, 3.25f, -3.44f, 3.31f, -2.78f, 3.25f, -2.12f, 3.07f, -1.49f, 2.7f,
+ -0.95f, 2.16f, -0.58f, 1.52f, -0.43f, 1.41f, -0.99f, 1.38f, -1.65f, 1.97f, -1.91f,
+ 2.25f, -2.49f, 2.25f, -3.15f, 1.99f, -3.74f, 1.38f, -3.78f, 1.06f, -3.22f, 0.88f,
+ -2.58f, 0.71f, -1.94f, 0.49f, -1.32f, 0.13f, -0.77f, -0.4f, -0.4f, -1.04f, -0.25f,
+ -1.69f, -0.32f, -2.28f, -0.61f, -2.73f, -1.09f, -2.98f, -1.69f, -3.09f, -2.34f,
+
+ 3.23f, 2.4f, -2.1f, 2.4f, -2.1f, 5.43f, -3.22f, 5.43f, -3.22f, 0.98f, 3.23f,
+ 0.98f, 3.23f, 2.4f, -3.09f, -2.34f,
+};
+constexpr std::array<float, 109 * 2> symbol_zr = {
+ -2.6f, -2.13f, -5.6f, -2.13f, -5.6f, -3.23f, -0.8f, -3.23f, -0.8f, -2.13f, -4.4f, 2.12f, -0.7f,
+ 2.12f, -0.7f, 3.22f, -6.0f, 3.22f, -6.0f, 2.12f,
+
+ 1.0f, 0.0f, 1.0f, -0.1f, 1.1f, -3.3f, 4.3f, -3.2f, 5.1f, -3.1f, 5.4f, -3.0f, 5.6f,
+ -2.9f, 5.7f, -2.8f, 5.9f, -2.7f, 5.9f, -2.6f, 6.0f, -2.5f, 6.1f, -2.3f, 6.2f, -2.2f,
+ 6.2f, -2.1f, 6.3f, -2.0f, 6.3f, -1.9f, 6.2f, -0.8f, 6.2f, -0.7f, 6.1f, -0.6f, 6.1f,
+ -0.5f, 6.0f, -0.4f, 6.0f, -0.3f, 5.9f, -0.2f, 5.7f, -0.1f, 5.7f, 0.0f, 5.6f, 0.1f,
+ 5.4f, 0.2f, 5.1f, 0.3f, 4.7f, 0.4f, 4.7f, 0.5f, 4.9f, 0.6f, 5.0f, 0.7f, 5.2f,
+ 0.8f, 5.2f, 0.9f, 5.3f, 1.0f, 5.5f, 1.1f, 5.5f, 1.2f, 5.6f, 1.3f, 5.7f, 1.5f,
+ 5.8f, 1.6f, 5.9f, 1.8f, 6.0f, 1.9f, 6.1f, 2.1f, 6.2f, 2.2f, 6.2f, 2.3f, 6.3f,
+ 2.4f, 6.4f, 2.6f, 6.5f, 2.7f, 6.6f, 2.9f, 6.7f, 3.0f, 6.7f, 3.1f, 6.8f, 3.2f,
+ 6.8f, 3.3f, 5.3f, 3.2f, 5.2f, 3.1f, 5.2f, 3.0f, 5.1f, 2.9f, 5.0f, 2.7f, 4.9f,
+ 2.6f, 4.8f, 2.4f, 4.7f, 2.3f, 4.6f, 2.1f, 4.5f, 2.0f, 4.4f, 1.8f, 4.3f, 1.7f,
+ 4.1f, 1.4f, 4.0f, 1.3f, 3.9f, 1.1f, 3.8f, 1.0f, 3.6f, 0.9f, 3.6f, 0.8f, 3.5f,
+ 0.7f, 3.3f, 0.6f, 2.9f, 0.5f, 2.3f, 0.6f, 2.3f, 0.7f, 2.2f, 3.3f, 1.0f, 3.2f,
+ 1.0f, 3.1f, 1.0f, 0.0f,
+
+ 4.2f, -0.5f, 4.4f, -0.6f, 4.7f, -0.7f, 4.8f, -0.8f, 4.9f, -1.0f, 5.0f, -1.1f, 5.0f,
+ -1.2f, 4.9f, -1.7f, 4.9f, -1.8f, 4.8f, -1.9f, 4.8f, -2.0f, 4.6f, -2.1f, 4.3f, -2.2f,
+ 2.3f, -2.1f, 2.3f, -2.0f, 2.4f, -0.5f, 4.2f, -0.5f, 1.0f, 0.0f, -6.0f, 2.12f,
+};
+
+constexpr std::array<float, 148 * 2> symbol_sr = {
+ -3.0f, -3.65f, -2.76f, -4.26f, -2.33f, -4.76f, -1.76f, -5.09f, -1.13f, -5.26f, -0.94f, -4.77f,
+ -0.87f, -4.11f, -1.46f, -3.88f, -1.91f, -3.41f, -2.05f, -2.78f, -1.98f, -2.13f, -1.59f, -1.61f,
+ -0.96f, -1.53f, -0.56f, -2.04f, -0.38f, -2.67f, -0.22f, -3.31f, 0.0f, -3.93f, 0.34f, -4.49f,
+ 0.86f, -4.89f, 1.49f, -5.05f, 2.14f, -4.95f, 2.69f, -4.6f, 3.07f, -4.07f, 3.25f, -3.44f,
+ 3.31f, -2.78f, 3.25f, -2.12f, 3.07f, -1.49f, 2.7f, -0.95f, 2.16f, -0.58f, 1.52f, -0.43f,
+ 1.41f, -0.99f, 1.38f, -1.65f, 1.97f, -1.91f, 2.25f, -2.49f, 2.25f, -3.15f, 1.99f, -3.74f,
+ 1.38f, -3.78f, 1.06f, -3.22f, 0.88f, -2.58f, 0.71f, -1.94f, 0.49f, -1.32f, 0.13f, -0.77f,
+ -0.4f, -0.4f, -1.04f, -0.25f, -1.69f, -0.32f, -2.28f, -0.61f, -2.73f, -1.09f, -2.98f, -1.69f,
+ -3.09f, -2.34f,
+
+ -1.0f, 0.0f, 0.1f, 1.0f, 3.3f, 1.1f, 3.2f, 4.3f, 3.1f, 5.1f, 3.0f, 5.4f,
+ 2.9f, 5.6f, 2.8f, 5.7f, 2.7f, 5.9f, 2.6f, 5.9f, 2.5f, 6.0f, 2.3f, 6.1f,
+ 2.2f, 6.2f, 2.1f, 6.2f, 2.0f, 6.3f, 1.9f, 6.3f, 0.8f, 6.2f, 0.7f, 6.2f,
+ 0.6f, 6.1f, 0.5f, 6.1f, 0.4f, 6.0f, 0.3f, 6.0f, 0.2f, 5.9f, 0.1f, 5.7f,
+ 0.0f, 5.7f, -0.1f, 5.6f, -0.2f, 5.4f, -0.3f, 5.1f, -0.4f, 4.7f, -0.5f, 4.7f,
+ -0.6f, 4.9f, -0.7f, 5.0f, -0.8f, 5.2f, -0.9f, 5.2f, -1.0f, 5.3f, -1.1f, 5.5f,
+ -1.2f, 5.5f, -1.3f, 5.6f, -1.5f, 5.7f, -1.6f, 5.8f, -1.8f, 5.9f, -1.9f, 6.0f,
+ -2.1f, 6.1f, -2.2f, 6.2f, -2.3f, 6.2f, -2.4f, 6.3f, -2.6f, 6.4f, -2.7f, 6.5f,
+ -2.9f, 6.6f, -3.0f, 6.7f, -3.1f, 6.7f, -3.2f, 6.8f, -3.3f, 6.8f, -3.2f, 5.3f,
+ -3.1f, 5.2f, -3.0f, 5.2f, -2.9f, 5.1f, -2.7f, 5.0f, -2.6f, 4.9f, -2.4f, 4.8f,
+ -2.3f, 4.7f, -2.1f, 4.6f, -2.0f, 4.5f, -1.8f, 4.4f, -1.7f, 4.3f, -1.4f, 4.1f,
+ -1.3f, 4.0f, -1.1f, 3.9f, -1.0f, 3.8f, -0.9f, 3.6f, -0.8f, 3.6f, -0.7f, 3.5f,
+ -0.6f, 3.3f, -0.5f, 2.9f, -0.6f, 2.3f, -0.7f, 2.3f, -3.3f, 2.2f, -3.2f, 1.0f,
+ -3.1f, 1.0f, 0.0f, 1.0f,
+
+ 0.5f, 4.2f, 0.6f, 4.4f, 0.7f, 4.7f, 0.8f, 4.8f, 1.0f, 4.9f, 1.1f, 5.0f,
+ 1.2f, 5.0f, 1.7f, 4.9f, 1.8f, 4.9f, 1.9f, 4.8f, 2.0f, 4.8f, 2.1f, 4.6f,
+ 2.2f, 4.3f, 2.1f, 2.3f, 2.0f, 2.3f, 0.5f, 2.4f, 0.5f, 4.2f, -0.0f, 1.0f,
+ -3.09f, -2.34f,
+
+};
+
+constexpr std::array<float, 30 * 2> symbol_c = {
+ 2.86f, 7.57f, 0.99f, 7.94f, -0.91f, 7.87f, -2.73f, 7.31f, -4.23f, 6.14f, -5.2f, 4.51f,
+ -5.65f, 2.66f, -5.68f, 0.75f, -5.31f, -1.12f, -4.43f, -2.81f, -3.01f, -4.08f, -1.24f, -4.78f,
+ 0.66f, -4.94f, 2.54f, -4.67f, 4.33f, -4.0f, 4.63f, -2.27f, 3.37f, -2.7f, 1.6f, -3.4f,
+ -0.3f, -3.5f, -2.09f, -2.87f, -3.34f, -1.45f, -3.91f, 0.37f, -3.95f, 2.27f, -3.49f, 4.12f,
+ -2.37f, 5.64f, -0.65f, 6.44f, 1.25f, 6.47f, 3.06f, 5.89f, 4.63f, 4.92f, 4.63f, 6.83f,
+};
+
+constexpr std::array<float, 12 * 2> house = {
+ -1.3f, 0.0f, -0.93f, 0.0f, -0.93f, 1.15f, 0.93f, 1.15f, 0.93f, 0.0f, 1.3f, 0.0f,
+ 0.0f, -1.2f, -1.3f, 0.0f, -0.43f, 0.0f, -0.43f, .73f, 0.43f, .73f, 0.43f, 0.0f,
+};
+
+constexpr std::array<float, 11 * 2> up_arrow_button = {
+ 9.1f, -9.1f, 9.1f, -30.0f, 8.1f, -30.1f, 7.7f, -30.1f, -8.6f, -30.0f, -9.0f,
+ -29.8f, -9.3f, -29.5f, -9.5f, -29.1f, -9.1f, -28.7f, -9.1f, -9.1f, 0.0f, 0.6f,
+};
+
+constexpr std::array<float, 3 * 2> up_arrow_symbol = {
+ 0.0f, -3.0f, -3.0f, 2.0f, 3.0f, 2.0f,
+};
+
+constexpr std::array<float, 64 * 2> trigger_button = {
+ 5.5f, -12.6f, 5.8f, -12.6f, 6.7f, -12.5f, 8.1f, -12.3f, 8.6f, -12.2f, 9.2f, -12.0f,
+ 9.5f, -11.9f, 9.9f, -11.8f, 10.6f, -11.5f, 11.0f, -11.3f, 11.2f, -11.2f, 11.4f, -11.1f,
+ 11.8f, -10.9f, 12.0f, -10.8f, 12.2f, -10.7f, 12.4f, -10.5f, 12.6f, -10.4f, 12.8f, -10.3f,
+ 13.6f, -9.7f, 13.8f, -9.6f, 13.9f, -9.4f, 14.1f, -9.3f, 14.8f, -8.6f, 15.0f, -8.5f,
+ 15.1f, -8.3f, 15.6f, -7.8f, 15.7f, -7.6f, 16.1f, -7.0f, 16.3f, -6.8f, 16.4f, -6.6f,
+ 16.5f, -6.4f, 16.8f, -6.0f, 16.9f, -5.8f, 17.0f, -5.6f, 17.1f, -5.4f, 17.2f, -5.2f,
+ 17.3f, -5.0f, 17.4f, -4.8f, 17.5f, -4.6f, 17.6f, -4.4f, 17.7f, -4.1f, 17.8f, -3.9f,
+ 17.9f, -3.5f, 18.0f, -3.3f, 18.1f, -3.0f, 18.2f, -2.6f, 18.2f, -2.3f, 18.3f, -2.1f,
+ 18.3f, -1.9f, 18.4f, -1.4f, 18.5f, -1.2f, 18.6f, -0.3f, 18.6f, 0.0f, 18.3f, 13.9f,
+ -17.0f, 13.8f, -17.0f, 13.6f, -16.4f, -11.4f, -16.3f, -11.6f, -16.1f, -11.8f, -15.7f, -12.0f,
+ -15.5f, -12.1f, -15.1f, -12.3f, -14.6f, -12.4f, -13.4f, -12.5f,
+};
+
+constexpr std::array<float, 36 * 2> pro_left_trigger = {
+ -65.2f, -132.6f, -68.2f, -134.1f, -71.3f, -135.5f, -74.4f, -136.7f, -77.6f,
+ -137.6f, -80.9f, -138.1f, -84.3f, -138.3f, -87.6f, -138.3f, -91.0f, -138.1f,
+ -94.3f, -137.8f, -97.6f, -137.3f, -100.9f, -136.7f, -107.5f, -135.3f, -110.7f,
+ -134.5f, -120.4f, -131.8f, -123.6f, -130.8f, -126.8f, -129.7f, -129.9f, -128.5f,
+ -132.9f, -127.1f, -135.9f, -125.6f, -138.8f, -123.9f, -141.6f, -122.0f, -144.1f,
+ -119.8f, -146.3f, -117.3f, -148.4f, -114.7f, -150.4f, -112.0f, -152.3f, -109.2f,
+ -155.3f, -104.0f, -152.0f, -104.3f, -148.7f, -104.5f, -145.3f, -104.8f, -35.5f,
+ -117.2f, -38.5f, -118.7f, -41.4f, -120.3f, -44.4f, -121.8f, -50.4f, -124.9f,
+};
+
+constexpr std::array<float, 14 * 2> pro_body_top = {
+ 0.0f, -115.4f, -4.4f, -116.1f, -69.7f, -131.3f, -66.4f, -131.9f, -63.1f, -132.3f,
+ -56.4f, -133.0f, -53.1f, -133.3f, -49.8f, -133.5f, -43.1f, -133.8f, -39.8f, -134.0f,
+ -36.5f, -134.1f, -16.4f, -134.4f, -13.1f, -134.4f, 0.0f, -134.1f,
+};
+
+constexpr std::array<float, 145 * 2> pro_left_handle = {
+ -178.7f, -47.5f, -179.0f, -46.1f, -179.3f, -44.6f, -182.0f, -29.8f, -182.3f, -28.4f,
+ -182.6f, -26.9f, -182.8f, -25.4f, -183.1f, -23.9f, -183.3f, -22.4f, -183.6f, -21.0f,
+ -183.8f, -19.5f, -184.1f, -18.0f, -184.3f, -16.5f, -184.6f, -15.1f, -184.8f, -13.6f,
+ -185.1f, -12.1f, -185.3f, -10.6f, -185.6f, -9.1f, -185.8f, -7.7f, -186.1f, -6.2f,
+ -186.3f, -4.7f, -186.6f, -3.2f, -186.8f, -1.7f, -187.1f, -0.3f, -187.3f, 1.2f,
+ -187.6f, 2.7f, -187.8f, 4.2f, -188.3f, 7.1f, -188.5f, 8.6f, -188.8f, 10.1f,
+ -189.0f, 11.6f, -189.3f, 13.1f, -189.5f, 14.5f, -190.0f, 17.5f, -190.2f, 19.0f,
+ -190.5f, 20.5f, -190.7f, 21.9f, -191.2f, 24.9f, -191.4f, 26.4f, -191.7f, 27.9f,
+ -191.9f, 29.3f, -192.4f, 32.3f, -192.6f, 33.8f, -193.1f, 36.8f, -193.3f, 38.2f,
+ -193.8f, 41.2f, -194.0f, 42.7f, -194.7f, 47.1f, -194.9f, 48.6f, -199.0f, 82.9f,
+ -199.1f, 84.4f, -199.1f, 85.9f, -199.2f, 87.4f, -199.2f, 88.9f, -199.1f, 94.9f,
+ -198.9f, 96.4f, -198.8f, 97.8f, -198.5f, 99.3f, -198.3f, 100.8f, -198.0f, 102.3f,
+ -197.7f, 103.7f, -197.4f, 105.2f, -197.0f, 106.7f, -196.6f, 108.1f, -195.7f, 111.0f,
+ -195.2f, 112.4f, -194.1f, 115.2f, -193.5f, 116.5f, -192.8f, 117.9f, -192.1f, 119.2f,
+ -190.6f, 121.8f, -189.8f, 123.1f, -188.9f, 124.3f, -187.0f, 126.6f, -186.0f, 127.7f,
+ -183.9f, 129.8f, -182.7f, 130.8f, -180.3f, 132.6f, -179.1f, 133.4f, -177.8f, 134.1f,
+ -176.4f, 134.8f, -175.1f, 135.5f, -173.7f, 136.0f, -169.4f, 137.3f, -167.9f, 137.7f,
+ -166.5f, 138.0f, -165.0f, 138.3f, -163.5f, 138.4f, -162.0f, 138.4f, -160.5f, 138.3f,
+ -159.0f, 138.0f, -157.6f, 137.7f, -156.1f, 137.3f, -154.7f, 136.9f, -153.2f, 136.5f,
+ -151.8f, 136.0f, -150.4f, 135.4f, -149.1f, 134.8f, -147.7f, 134.1f, -146.5f, 133.3f,
+ -145.2f, 132.5f, -144.0f, 131.6f, -142.8f, 130.6f, -141.7f, 129.6f, -139.6f, 127.5f,
+ -138.6f, 126.4f, -137.7f, 125.2f, -135.1f, 121.5f, -134.3f, 120.3f, -133.5f, 119.0f,
+ -131.9f, 116.5f, -131.1f, 115.2f, -128.8f, 111.3f, -128.0f, 110.1f, -127.2f, 108.8f,
+ -126.5f, 107.5f, -125.7f, 106.2f, -125.0f, 104.9f, -124.2f, 103.6f, -123.5f, 102.3f,
+ -122.0f, 99.6f, -121.3f, 98.3f, -115.8f, 87.7f, -115.1f, 86.4f, -114.4f, 85.0f,
+ -113.7f, 83.7f, -112.3f, 81.0f, -111.6f, 79.7f, -110.1f, 77.1f, -109.4f, 75.8f,
+ -108.0f, 73.1f, -107.2f, 71.8f, -106.4f, 70.6f, -105.7f, 69.3f, -104.8f, 68.0f,
+ -104.0f, 66.8f, -103.1f, 65.6f, -101.1f, 63.3f, -100.0f, 62.3f, -98.8f, 61.4f,
+ -97.6f, 60.6f, -97.9f, 59.5f, -98.8f, 58.3f, -101.5f, 54.6f, -102.4f, 53.4f,
+};
+
+constexpr std::array<float, 245 * 2> pro_body = {
+ -0.7f, -129.1f, -54.3f, -129.1f, -55.0f, -129.1f, -57.8f, -129.0f, -58.5f, -129.0f,
+ -60.7f, -128.9f, -61.4f, -128.9f, -62.8f, -128.8f, -63.5f, -128.8f, -65.7f, -128.7f,
+ -66.4f, -128.7f, -67.8f, -128.6f, -68.5f, -128.6f, -69.2f, -128.5f, -70.0f, -128.5f,
+ -70.7f, -128.4f, -71.4f, -128.4f, -72.1f, -128.3f, -72.8f, -128.3f, -73.5f, -128.2f,
+ -74.2f, -128.2f, -74.9f, -128.1f, -75.7f, -128.1f, -76.4f, -128.0f, -77.1f, -128.0f,
+ -77.8f, -127.9f, -78.5f, -127.9f, -79.2f, -127.8f, -80.6f, -127.7f, -81.4f, -127.6f,
+ -82.1f, -127.5f, -82.8f, -127.5f, -83.5f, -127.4f, -84.9f, -127.3f, -85.6f, -127.2f,
+ -87.0f, -127.1f, -87.7f, -127.0f, -88.5f, -126.9f, -89.2f, -126.8f, -89.9f, -126.8f,
+ -90.6f, -126.7f, -94.1f, -126.3f, -94.8f, -126.2f, -113.2f, -123.3f, -113.9f, -123.2f,
+ -114.6f, -123.0f, -115.3f, -122.9f, -116.7f, -122.6f, -117.4f, -122.5f, -118.1f, -122.3f,
+ -118.8f, -122.2f, -119.5f, -122.0f, -120.9f, -121.7f, -121.6f, -121.5f, -122.3f, -121.4f,
+ -122.9f, -121.2f, -123.6f, -121.0f, -126.4f, -120.3f, -127.1f, -120.1f, -127.8f, -119.8f,
+ -128.4f, -119.6f, -129.1f, -119.4f, -131.2f, -118.7f, -132.5f, -118.3f, -133.2f, -118.0f,
+ -133.8f, -117.7f, -134.5f, -117.4f, -135.1f, -117.2f, -135.8f, -116.9f, -136.4f, -116.5f,
+ -137.0f, -116.2f, -137.7f, -115.8f, -138.3f, -115.4f, -138.9f, -115.1f, -139.5f, -114.7f,
+ -160.0f, -100.5f, -160.5f, -100.0f, -162.5f, -97.9f, -162.9f, -97.4f, -163.4f, -96.8f,
+ -163.8f, -96.2f, -165.3f, -93.8f, -165.7f, -93.2f, -166.0f, -92.6f, -166.4f, -91.9f,
+ -166.7f, -91.3f, -167.3f, -90.0f, -167.6f, -89.4f, -167.8f, -88.7f, -168.1f, -88.0f,
+ -168.4f, -87.4f, -168.6f, -86.7f, -168.9f, -86.0f, -169.1f, -85.4f, -169.3f, -84.7f,
+ -169.6f, -84.0f, -169.8f, -83.3f, -170.2f, -82.0f, -170.4f, -81.3f, -172.8f, -72.3f,
+ -173.0f, -71.6f, -173.5f, -69.5f, -173.7f, -68.8f, -173.9f, -68.2f, -174.0f, -67.5f,
+ -174.2f, -66.8f, -174.5f, -65.4f, -174.7f, -64.7f, -174.8f, -64.0f, -175.0f, -63.3f,
+ -175.3f, -61.9f, -175.5f, -61.2f, -175.8f, -59.8f, -176.0f, -59.1f, -176.1f, -58.4f,
+ -176.3f, -57.7f, -176.6f, -56.3f, -176.8f, -55.6f, -176.9f, -54.9f, -177.1f, -54.2f,
+ -177.3f, -53.6f, -177.4f, -52.9f, -177.6f, -52.2f, -177.9f, -50.8f, -178.1f, -50.1f,
+ -178.2f, -49.4f, -178.2f, -48.7f, -177.8f, -48.1f, -177.1f, -46.9f, -176.7f, -46.3f,
+ -176.4f, -45.6f, -176.0f, -45.0f, -175.3f, -43.8f, -174.9f, -43.2f, -174.2f, -42.0f,
+ -173.4f, -40.7f, -173.1f, -40.1f, -172.7f, -39.5f, -172.0f, -38.3f, -171.6f, -37.7f,
+ -170.5f, -35.9f, -170.1f, -35.3f, -169.7f, -34.6f, -169.3f, -34.0f, -168.6f, -32.8f,
+ -168.2f, -32.2f, -166.3f, -29.2f, -165.9f, -28.6f, -163.2f, -24.4f, -162.8f, -23.8f,
+ -141.8f, 6.8f, -141.4f, 7.4f, -139.4f, 10.3f, -139.0f, 10.9f, -138.5f, 11.5f,
+ -138.1f, 12.1f, -137.3f, 13.2f, -136.9f, 13.8f, -136.0f, 15.0f, -135.6f, 15.6f,
+ -135.2f, 16.1f, -134.8f, 16.7f, -133.9f, 17.9f, -133.5f, 18.4f, -133.1f, 19.0f,
+ -131.8f, 20.7f, -131.4f, 21.3f, -130.1f, 23.0f, -129.7f, 23.6f, -128.4f, 25.3f,
+ -128.0f, 25.9f, -126.7f, 27.6f, -126.3f, 28.2f, -125.4f, 29.3f, -125.0f, 29.9f,
+ -124.1f, 31.0f, -123.7f, 31.6f, -122.8f, 32.7f, -122.4f, 33.3f, -121.5f, 34.4f,
+ -121.1f, 35.0f, -120.6f, 35.6f, -120.2f, 36.1f, -119.7f, 36.7f, -119.3f, 37.2f,
+ -118.9f, 37.8f, -118.4f, 38.4f, -118.0f, 38.9f, -117.5f, 39.5f, -117.1f, 40.0f,
+ -116.6f, 40.6f, -116.2f, 41.1f, -115.7f, 41.7f, -115.2f, 42.2f, -114.8f, 42.8f,
+ -114.3f, 43.3f, -113.9f, 43.9f, -113.4f, 44.4f, -112.4f, 45.5f, -112.0f, 46.0f,
+ -111.5f, 46.5f, -110.5f, 47.6f, -110.0f, 48.1f, -109.6f, 48.6f, -109.1f, 49.2f,
+ -108.6f, 49.7f, -107.7f, 50.8f, -107.2f, 51.3f, -105.7f, 52.9f, -105.3f, 53.4f,
+ -104.8f, 53.9f, -104.3f, 54.5f, -103.8f, 55.0f, -100.7f, 58.0f, -100.2f, 58.4f,
+ -99.7f, 58.9f, -99.1f, 59.3f, -97.2f, 60.3f, -96.5f, 60.1f, -95.9f, 59.7f,
+ -95.3f, 59.4f, -94.6f, 59.1f, -93.9f, 58.9f, -92.6f, 58.5f, -91.9f, 58.4f,
+ -91.2f, 58.2f, -90.5f, 58.1f, -89.7f, 58.0f, -89.0f, 57.9f, -86.2f, 57.6f,
+ -85.5f, 57.5f, -84.1f, 57.4f, -83.4f, 57.3f, -82.6f, 57.3f, -81.9f, 57.2f,
+ -81.2f, 57.2f, -80.5f, 57.1f, -79.8f, 57.1f, -78.4f, 57.0f, -77.7f, 57.0f,
+ -75.5f, 56.9f, -74.8f, 56.9f, -71.9f, 56.8f, -71.2f, 56.8f, 0.0f, 56.8f,
+};
+
+constexpr std::array<float, 199 * 2> gc_body = {
+ 0.0f, -138.03f, -4.91f, -138.01f, -8.02f, -137.94f, -11.14f, -137.82f, -14.25f,
+ -137.67f, -17.37f, -137.48f, -20.48f, -137.25f, -23.59f, -137.0f, -26.69f, -136.72f,
+ -29.8f, -136.41f, -32.9f, -136.07f, -35.99f, -135.71f, -39.09f, -135.32f, -42.18f,
+ -134.91f, -45.27f, -134.48f, -48.35f, -134.03f, -51.43f, -133.55f, -54.51f, -133.05f,
+ -57.59f, -132.52f, -60.66f, -131.98f, -63.72f, -131.41f, -66.78f, -130.81f, -69.84f,
+ -130.2f, -72.89f, -129.56f, -75.94f, -128.89f, -78.98f, -128.21f, -82.02f, -127.49f,
+ -85.05f, -126.75f, -88.07f, -125.99f, -91.09f, -125.19f, -94.1f, -124.37f, -97.1f,
+ -123.52f, -100.09f, -122.64f, -103.07f, -121.72f, -106.04f, -120.77f, -109.0f, -119.79f,
+ -111.95f, -118.77f, -114.88f, -117.71f, -117.8f, -116.61f, -120.7f, -115.46f, -123.58f,
+ -114.27f, -126.44f, -113.03f, -129.27f, -111.73f, -132.08f, -110.38f, -134.86f, -108.96f,
+ -137.6f, -107.47f, -140.3f, -105.91f, -142.95f, -104.27f, -145.55f, -102.54f, -148.07f,
+ -100.71f, -150.51f, -98.77f, -152.86f, -96.71f, -155.09f, -94.54f, -157.23f, -92.27f,
+ -159.26f, -89.9f, -161.2f, -87.46f, -163.04f, -84.94f, -164.78f, -82.35f, -166.42f,
+ -79.7f, -167.97f, -77.0f, -169.43f, -74.24f, -170.8f, -71.44f, -172.09f, -68.6f,
+ -173.29f, -65.72f, -174.41f, -62.81f, -175.45f, -59.87f, -176.42f, -56.91f, -177.31f,
+ -53.92f, -178.14f, -50.91f, -178.9f, -47.89f, -179.6f, -44.85f, -180.24f, -41.8f,
+ -180.82f, -38.73f, -181.34f, -35.66f, -181.8f, -32.57f, -182.21f, -29.48f, -182.57f,
+ -26.38f, -182.88f, -23.28f, -183.15f, -20.17f, -183.36f, -17.06f, -183.54f, -13.95f,
+ -183.71f, -10.84f, -184.0f, -7.73f, -184.23f, -4.62f, -184.44f, -1.51f, -184.62f,
+ 1.6f, -184.79f, 4.72f, -184.95f, 7.83f, -185.11f, 10.95f, -185.25f, 14.06f,
+ -185.38f, 17.18f, -185.51f, 20.29f, -185.63f, 23.41f, -185.74f, 26.53f, -185.85f,
+ 29.64f, -185.95f, 32.76f, -186.04f, 35.88f, -186.12f, 39.0f, -186.19f, 42.11f,
+ -186.26f, 45.23f, -186.32f, 48.35f, -186.37f, 51.47f, -186.41f, 54.59f, -186.44f,
+ 57.7f, -186.46f, 60.82f, -186.46f, 63.94f, -186.44f, 70.18f, -186.41f, 73.3f,
+ -186.36f, 76.42f, -186.3f, 79.53f, -186.22f, 82.65f, -186.12f, 85.77f, -185.99f,
+ 88.88f, -185.84f, 92.0f, -185.66f, 95.11f, -185.44f, 98.22f, -185.17f, 101.33f,
+ -184.85f, 104.43f, -184.46f, 107.53f, -183.97f, 110.61f, -183.37f, 113.67f, -182.65f,
+ 116.7f, -181.77f, 119.69f, -180.71f, 122.62f, -179.43f, 125.47f, -177.89f, 128.18f,
+ -176.05f, 130.69f, -173.88f, 132.92f, -171.36f, 134.75f, -168.55f, 136.1f, -165.55f,
+ 136.93f, -162.45f, 137.29f, -156.23f, 137.03f, -153.18f, 136.41f, -150.46f, 134.9f,
+ -148.14f, 132.83f, -146.14f, 130.43f, -144.39f, 127.85f, -142.83f, 125.16f, -141.41f,
+ 122.38f, -140.11f, 119.54f, -138.9f, 116.67f, -137.77f, 113.76f, -136.7f, 110.84f,
+ -135.68f, 107.89f, -134.71f, 104.93f, -133.77f, 101.95f, -132.86f, 98.97f, -131.97f,
+ 95.98f, -131.09f, 92.99f, -130.23f, 89.99f, -129.36f, 86.99f, -128.49f, 84.0f,
+ -127.63f, 81.0f, -126.76f, 78.01f, -125.9f, 75.01f, -124.17f, 69.02f, -123.31f,
+ 66.02f, -121.59f, 60.03f, -120.72f, 57.03f, -119.86f, 54.03f, -118.13f, 48.04f,
+ -117.27f, 45.04f, -115.55f, 39.05f, -114.68f, 36.05f, -113.82f, 33.05f, -112.96f,
+ 30.06f, -110.4f, 28.29f, -107.81f, 26.55f, -105.23f, 24.8f, -97.48f, 19.55f,
+ -94.9f, 17.81f, -92.32f, 16.06f, -87.15f, 12.56f, -84.57f, 10.81f, -81.99f,
+ 9.07f, -79.4f, 7.32f, -76.82f, 5.57f, -69.07f, 0.33f, -66.49f, -1.42f,
+ -58.74f, -6.66f, -56.16f, -8.41f, -48.4f, -13.64f, -45.72f, -15.22f, -42.93f,
+ -16.62f, -40.07f, -17.86f, -37.15f, -18.96f, -34.19f, -19.94f, -31.19f, -20.79f,
+ -28.16f, -21.55f, -25.12f, -22.21f, -22.05f, -22.79f, -18.97f, -23.28f, -15.88f,
+ -23.7f, -12.78f, -24.05f, -9.68f, -24.33f, -6.57f, -24.55f, -3.45f, -24.69f,
+ 0.0f, -24.69f,
+};
+
+constexpr std::array<float, 99 * 2> gc_left_body = {
+ -74.59f, -97.22f, -70.17f, -94.19f, -65.95f, -90.89f, -62.06f, -87.21f, -58.58f,
+ -83.14f, -55.58f, -78.7f, -53.08f, -73.97f, -51.05f, -69.01f, -49.46f, -63.89f,
+ -48.24f, -58.67f, -47.36f, -53.39f, -46.59f, -48.09f, -45.7f, -42.8f, -44.69f,
+ -37.54f, -43.54f, -32.31f, -42.25f, -27.11f, -40.8f, -21.95f, -39.19f, -16.84f,
+ -37.38f, -11.8f, -35.34f, -6.84f, -33.04f, -2.0f, -30.39f, 2.65f, -27.26f,
+ 7.0f, -23.84f, 11.11f, -21.19f, 15.76f, -19.18f, 20.73f, -17.73f, 25.88f,
+ -16.82f, 31.16f, -16.46f, 36.5f, -16.7f, 41.85f, -17.63f, 47.13f, -19.31f,
+ 52.21f, -21.8f, 56.95f, -24.91f, 61.3f, -28.41f, 65.36f, -32.28f, 69.06f,
+ -36.51f, 72.35f, -41.09f, 75.13f, -45.97f, 77.32f, -51.1f, 78.86f, -56.39f,
+ 79.7f, -61.74f, 79.84f, -67.07f, 79.3f, -72.3f, 78.15f, -77.39f, 76.48f,
+ -82.29f, 74.31f, -86.76f, 71.37f, -90.7f, 67.75f, -94.16f, 63.66f, -97.27f,
+ 59.3f, -100.21f, 54.81f, -103.09f, 50.3f, -106.03f, 45.82f, -109.11f, 41.44f,
+ -112.37f, 37.19f, -115.85f, 33.11f, -119.54f, 29.22f, -123.45f, 25.56f, -127.55f,
+ 22.11f, -131.77f, 18.81f, -136.04f, 15.57f, -140.34f, 12.37f, -144.62f, 9.15f,
+ -148.86f, 5.88f, -153.03f, 2.51f, -157.05f, -1.03f, -160.83f, -4.83f, -164.12f,
+ -9.05f, -166.71f, -13.73f, -168.91f, -18.62f, -170.77f, -23.64f, -172.3f, -28.78f,
+ -173.49f, -34.0f, -174.3f, -39.3f, -174.72f, -44.64f, -174.72f, -49.99f, -174.28f,
+ -55.33f, -173.37f, -60.61f, -172.0f, -65.79f, -170.17f, -70.82f, -167.79f, -75.62f,
+ -164.84f, -80.09f, -161.43f, -84.22f, -157.67f, -88.03f, -153.63f, -91.55f, -149.37f,
+ -94.81f, -144.94f, -97.82f, -140.37f, -100.61f, -135.65f, -103.16f, -130.73f, -105.26f,
+ -125.62f, -106.86f, -120.37f, -107.95f, -115.05f, -108.56f, -109.7f, -108.69f, -104.35f,
+ -108.36f, -99.05f, -107.6f, -93.82f, -106.41f, -88.72f, -104.79f, -83.78f, -102.7f,
+};
+
+constexpr std::array<float, 47 * 2> left_gc_trigger = {
+ -99.69f, -125.04f, -101.81f, -126.51f, -104.02f, -127.85f, -106.3f, -129.06f, -108.65f,
+ -130.12f, -111.08f, -130.99f, -113.58f, -131.62f, -116.14f, -131.97f, -121.26f, -131.55f,
+ -123.74f, -130.84f, -126.17f, -129.95f, -128.53f, -128.9f, -130.82f, -127.71f, -133.03f,
+ -126.38f, -135.15f, -124.92f, -137.18f, -123.32f, -139.11f, -121.6f, -140.91f, -119.75f,
+ -142.55f, -117.77f, -144.0f, -115.63f, -145.18f, -113.34f, -146.17f, -110.95f, -147.05f,
+ -108.53f, -147.87f, -106.08f, -148.64f, -103.61f, -149.37f, -101.14f, -149.16f, -100.12f,
+ -147.12f, -101.71f, -144.99f, -103.16f, -142.8f, -104.53f, -140.57f, -105.83f, -138.31f,
+ -107.08f, -136.02f, -108.27f, -133.71f, -109.42f, -131.38f, -110.53f, -129.04f, -111.61f,
+ -126.68f, -112.66f, -124.31f, -113.68f, -121.92f, -114.67f, -119.53f, -115.64f, -117.13f,
+ -116.58f, -114.72f, -117.51f, -112.3f, -118.41f, -109.87f, -119.29f, -107.44f, -120.16f,
+ -105.0f, -121.0f, -100.11f, -122.65f,
+};
+
+constexpr std::array<float, 50 * 2> gc_button_x = {
+ 142.1f, -50.67f, 142.44f, -48.65f, 142.69f, -46.62f, 142.8f, -44.57f, 143.0f, -42.54f,
+ 143.56f, -40.57f, 144.42f, -38.71f, 145.59f, -37.04f, 147.08f, -35.64f, 148.86f, -34.65f,
+ 150.84f, -34.11f, 152.88f, -34.03f, 154.89f, -34.38f, 156.79f, -35.14f, 158.49f, -36.28f,
+ 159.92f, -37.74f, 161.04f, -39.45f, 161.85f, -41.33f, 162.4f, -43.3f, 162.72f, -45.32f,
+ 162.85f, -47.37f, 162.82f, -49.41f, 162.67f, -51.46f, 162.39f, -53.48f, 162.0f, -55.5f,
+ 161.51f, -57.48f, 160.9f, -59.44f, 160.17f, -61.35f, 159.25f, -63.18f, 158.19f, -64.93f,
+ 157.01f, -66.61f, 155.72f, -68.2f, 154.31f, -69.68f, 152.78f, -71.04f, 151.09f, -72.2f,
+ 149.23f, -73.04f, 147.22f, -73.36f, 145.19f, -73.11f, 143.26f, -72.42f, 141.51f, -71.37f,
+ 140.0f, -69.99f, 138.82f, -68.32f, 138.13f, -66.4f, 138.09f, -64.36f, 138.39f, -62.34f,
+ 139.05f, -60.41f, 139.91f, -58.55f, 140.62f, -56.63f, 141.21f, -54.67f, 141.67f, -52.67f,
+};
+
+constexpr std::array<float, 50 * 2> gc_button_y = {
+ 104.02f, -75.23f, 106.01f, -75.74f, 108.01f, -76.15f, 110.04f, -76.42f, 112.05f, -76.78f,
+ 113.97f, -77.49f, 115.76f, -78.49f, 117.33f, -79.79f, 118.6f, -81.39f, 119.46f, -83.25f,
+ 119.84f, -85.26f, 119.76f, -87.3f, 119.24f, -89.28f, 118.33f, -91.11f, 117.06f, -92.71f,
+ 115.49f, -94.02f, 113.7f, -95.01f, 111.77f, -95.67f, 109.76f, -96.05f, 107.71f, -96.21f,
+ 105.67f, -96.18f, 103.63f, -95.99f, 101.61f, -95.67f, 99.61f, -95.24f, 97.63f, -94.69f,
+ 95.69f, -94.04f, 93.79f, -93.28f, 91.94f, -92.4f, 90.19f, -91.34f, 88.53f, -90.14f,
+ 86.95f, -88.84f, 85.47f, -87.42f, 84.1f, -85.9f, 82.87f, -84.26f, 81.85f, -82.49f,
+ 81.15f, -80.57f, 81.0f, -78.54f, 81.41f, -76.54f, 82.24f, -74.67f, 83.43f, -73.01f,
+ 84.92f, -71.61f, 86.68f, -70.57f, 88.65f, -70.03f, 90.69f, -70.15f, 92.68f, -70.61f,
+ 94.56f, -71.42f, 96.34f, -72.43f, 98.2f, -73.29f, 100.11f, -74.03f, 102.06f, -74.65f,
+};
+
+constexpr std::array<float, 47 * 2> gc_button_z = {
+ 95.74f, -126.41f, 98.34f, -126.38f, 100.94f, -126.24f, 103.53f, -126.01f, 106.11f, -125.7f,
+ 108.69f, -125.32f, 111.25f, -124.87f, 113.8f, -124.34f, 116.33f, -123.73f, 118.84f, -123.05f,
+ 121.33f, -122.3f, 123.79f, -121.47f, 126.23f, -120.56f, 128.64f, -119.58f, 131.02f, -118.51f,
+ 133.35f, -117.37f, 135.65f, -116.14f, 137.9f, -114.84f, 140.1f, -113.46f, 142.25f, -111.99f,
+ 144.35f, -110.45f, 146.38f, -108.82f, 148.35f, -107.13f, 150.25f, -105.35f, 151.89f, -103.38f,
+ 151.43f, -100.86f, 149.15f, -100.15f, 146.73f, -101.06f, 144.36f, -102.12f, 141.98f, -103.18f,
+ 139.6f, -104.23f, 137.22f, -105.29f, 134.85f, -106.35f, 132.47f, -107.41f, 127.72f, -109.53f,
+ 125.34f, -110.58f, 122.96f, -111.64f, 120.59f, -112.7f, 118.21f, -113.76f, 113.46f, -115.88f,
+ 111.08f, -116.93f, 108.7f, -117.99f, 106.33f, -119.05f, 103.95f, -120.11f, 99.2f, -122.23f,
+ 96.82f, -123.29f, 94.44f, -124.34f,
+};
+
+constexpr std::array<float, 84 * 2> left_joycon_body = {
+ -145.0f, -78.9f, -145.0f, -77.9f, -145.0f, 85.6f, -145.0f, 85.6f, -168.3f, 85.5f,
+ -169.3f, 85.4f, -171.3f, 85.1f, -172.3f, 84.9f, -173.4f, 84.7f, -174.3f, 84.5f,
+ -175.3f, 84.2f, -176.3f, 83.8f, -177.3f, 83.5f, -178.2f, 83.1f, -179.2f, 82.7f,
+ -180.1f, 82.2f, -181.0f, 81.8f, -181.9f, 81.3f, -182.8f, 80.7f, -183.7f, 80.2f,
+ -184.5f, 79.6f, -186.2f, 78.3f, -186.9f, 77.7f, -187.7f, 77.0f, -189.2f, 75.6f,
+ -189.9f, 74.8f, -190.6f, 74.1f, -191.3f, 73.3f, -191.9f, 72.5f, -192.5f, 71.6f,
+ -193.1f, 70.8f, -193.7f, 69.9f, -194.3f, 69.1f, -194.8f, 68.2f, -196.2f, 65.5f,
+ -196.6f, 64.5f, -197.0f, 63.6f, -197.4f, 62.6f, -198.1f, 60.7f, -198.4f, 59.7f,
+ -198.6f, 58.7f, -199.2f, 55.6f, -199.3f, 54.6f, -199.5f, 51.5f, -199.5f, 50.5f,
+ -199.5f, -49.4f, -199.4f, -50.5f, -199.3f, -51.5f, -199.1f, -52.5f, -198.2f, -56.5f,
+ -197.9f, -57.5f, -197.2f, -59.4f, -196.8f, -60.4f, -196.4f, -61.3f, -195.9f, -62.2f,
+ -194.3f, -64.9f, -193.7f, -65.7f, -193.1f, -66.6f, -192.5f, -67.4f, -191.8f, -68.2f,
+ -191.2f, -68.9f, -190.4f, -69.7f, -188.2f, -71.8f, -187.4f, -72.5f, -186.6f, -73.1f,
+ -185.8f, -73.8f, -185.0f, -74.4f, -184.1f, -74.9f, -183.2f, -75.5f, -182.4f, -76.0f,
+ -181.5f, -76.5f, -179.6f, -77.5f, -178.7f, -77.9f, -177.8f, -78.4f, -176.8f, -78.8f,
+ -175.9f, -79.1f, -174.9f, -79.5f, -173.9f, -79.8f, -170.9f, -80.6f, -169.9f, -80.8f,
+ -167.9f, -81.1f, -166.9f, -81.2f, -165.8f, -81.2f, -145.0f, -80.9f,
+};
+
+constexpr std::array<float, 84 * 2> left_joycon_trigger = {
+ -166.8f, -83.3f, -167.9f, -83.2f, -168.9f, -83.1f, -170.0f, -83.0f, -171.0f, -82.8f,
+ -172.1f, -82.6f, -173.1f, -82.4f, -174.2f, -82.1f, -175.2f, -81.9f, -176.2f, -81.5f,
+ -177.2f, -81.2f, -178.2f, -80.8f, -180.1f, -80.0f, -181.1f, -79.5f, -182.0f, -79.0f,
+ -183.0f, -78.5f, -183.9f, -78.0f, -184.8f, -77.4f, -185.7f, -76.9f, -186.6f, -76.3f,
+ -187.4f, -75.6f, -188.3f, -75.0f, -189.1f, -74.3f, -192.2f, -71.5f, -192.9f, -70.7f,
+ -193.7f, -69.9f, -194.3f, -69.1f, -195.0f, -68.3f, -195.6f, -67.4f, -196.8f, -65.7f,
+ -197.3f, -64.7f, -197.8f, -63.8f, -198.2f, -62.8f, -198.9f, -60.8f, -198.6f, -59.8f,
+ -197.6f, -59.7f, -196.6f, -60.0f, -195.6f, -60.5f, -194.7f, -60.9f, -193.7f, -61.4f,
+ -192.8f, -61.9f, -191.8f, -62.4f, -190.9f, -62.8f, -189.9f, -63.3f, -189.0f, -63.8f,
+ -187.1f, -64.8f, -186.2f, -65.2f, -185.2f, -65.7f, -184.3f, -66.2f, -183.3f, -66.7f,
+ -182.4f, -67.1f, -181.4f, -67.6f, -180.5f, -68.1f, -179.5f, -68.6f, -178.6f, -69.0f,
+ -177.6f, -69.5f, -176.7f, -70.0f, -175.7f, -70.5f, -174.8f, -70.9f, -173.8f, -71.4f,
+ -172.9f, -71.9f, -171.9f, -72.4f, -171.0f, -72.8f, -170.0f, -73.3f, -169.1f, -73.8f,
+ -168.1f, -74.3f, -167.2f, -74.7f, -166.2f, -75.2f, -165.3f, -75.7f, -164.3f, -76.2f,
+ -163.4f, -76.6f, -162.4f, -77.1f, -161.5f, -77.6f, -160.5f, -78.1f, -159.6f, -78.5f,
+ -158.7f, -79.0f, -157.7f, -79.5f, -156.8f, -80.0f, -155.8f, -80.4f, -154.9f, -80.9f,
+ -154.2f, -81.6f, -154.3f, -82.6f, -155.2f, -83.3f, -156.2f, -83.3f,
+};
+
+constexpr std::array<float, 70 * 2> handheld_body = {
+ -137.3f, -81.9f, -137.6f, -81.8f, -137.8f, -81.6f, -138.0f, -81.3f, -138.1f, -81.1f,
+ -138.1f, -80.8f, -138.2f, -78.7f, -138.2f, -78.4f, -138.3f, -78.1f, -138.7f, -77.3f,
+ -138.9f, -77.0f, -139.0f, -76.8f, -139.2f, -76.5f, -139.5f, -76.3f, -139.7f, -76.1f,
+ -139.9f, -76.0f, -140.2f, -75.8f, -140.5f, -75.7f, -140.7f, -75.6f, -141.0f, -75.5f,
+ -141.9f, -75.3f, -142.2f, -75.3f, -142.5f, -75.2f, -143.0f, -74.9f, -143.2f, -74.7f,
+ -143.3f, -74.4f, -143.0f, -74.1f, -143.0f, 85.3f, -143.0f, 85.6f, -142.7f, 85.8f,
+ -142.4f, 85.9f, -142.2f, 85.9f, 143.0f, 85.6f, 143.1f, 85.4f, 143.3f, 85.1f,
+ 143.0f, 84.8f, 143.0f, -74.9f, 142.8f, -75.1f, 142.5f, -75.2f, 141.9f, -75.3f,
+ 141.6f, -75.3f, 141.3f, -75.4f, 141.1f, -75.4f, 140.8f, -75.5f, 140.5f, -75.7f,
+ 140.2f, -75.8f, 140.0f, -76.0f, 139.7f, -76.1f, 139.5f, -76.3f, 139.1f, -76.8f,
+ 138.9f, -77.0f, 138.6f, -77.5f, 138.4f, -77.8f, 138.3f, -78.1f, 138.3f, -78.3f,
+ 138.2f, -78.6f, 138.2f, -78.9f, 138.1f, -79.2f, 138.1f, -79.5f, 138.0f, -81.3f,
+ 137.8f, -81.6f, 137.6f, -81.8f, 137.3f, -81.9f, 137.1f, -81.9f, 120.0f, -70.0f,
+ -120.0f, -70.0f, -120.0f, 70.0f, 120.0f, 70.0f, 120.0f, -70.0f, 137.1f, -81.9f,
+};
+
+constexpr std::array<float, 40 * 2> handheld_bezel = {
+ -131.4f, -75.9f, -132.2f, -75.7f, -132.9f, -75.3f, -134.2f, -74.3f, -134.7f, -73.6f,
+ -135.1f, -72.8f, -135.4f, -72.0f, -135.5f, -71.2f, -135.5f, -70.4f, -135.2f, 76.7f,
+ -134.8f, 77.5f, -134.3f, 78.1f, -133.7f, 78.8f, -133.1f, 79.2f, -132.3f, 79.6f,
+ -131.5f, 79.9f, -130.7f, 80.0f, -129.8f, 80.0f, 132.2f, 79.7f, 133.0f, 79.3f,
+ 133.7f, 78.8f, 134.3f, 78.3f, 134.8f, 77.6f, 135.1f, 76.8f, 135.5f, 75.2f,
+ 135.5f, 74.3f, 135.2f, -72.7f, 134.8f, -73.5f, 134.4f, -74.2f, 133.8f, -74.8f,
+ 133.1f, -75.3f, 132.3f, -75.6f, 130.7f, -76.0f, 129.8f, -76.0f, -112.9f, -62.2f,
+ 112.9f, -62.2f, 112.9f, 62.2f, -112.9f, 62.2f, -112.9f, -62.2f, 129.8f, -76.0f,
+};
+
+constexpr std::array<float, 58 * 2> handheld_buttons = {
+ -82.48f, -82.95f, -82.53f, -82.95f, -106.69f, -82.96f, -106.73f, -82.98f, -106.78f, -83.01f,
+ -106.81f, -83.05f, -106.83f, -83.1f, -106.83f, -83.15f, -106.82f, -83.93f, -106.81f, -83.99f,
+ -106.8f, -84.04f, -106.78f, -84.08f, -106.76f, -84.13f, -106.73f, -84.18f, -106.7f, -84.22f,
+ -106.6f, -84.34f, -106.56f, -84.37f, -106.51f, -84.4f, -106.47f, -84.42f, -106.42f, -84.45f,
+ -106.37f, -84.47f, -106.32f, -84.48f, -106.17f, -84.5f, -98.9f, -84.48f, -98.86f, -84.45f,
+ -98.83f, -84.41f, -98.81f, -84.36f, -98.8f, -84.31f, -98.8f, -84.26f, -98.79f, -84.05f,
+ -90.26f, -84.1f, -90.26f, -84.15f, -90.25f, -84.36f, -90.23f, -84.41f, -90.2f, -84.45f,
+ -90.16f, -84.48f, -90.11f, -84.5f, -82.79f, -84.49f, -82.74f, -84.48f, -82.69f, -84.46f,
+ -82.64f, -84.45f, -82.59f, -84.42f, -82.55f, -84.4f, -82.5f, -84.37f, -82.46f, -84.33f,
+ -82.42f, -84.3f, -82.39f, -84.26f, -82.3f, -84.13f, -82.28f, -84.08f, -82.25f, -83.98f,
+ -82.24f, -83.93f, -82.23f, -83.83f, -82.23f, -83.78f, -82.24f, -83.1f, -82.26f, -83.05f,
+ -82.29f, -83.01f, -82.33f, -82.97f, -82.38f, -82.95f,
+};
+
+constexpr std::array<float, 47 * 2> left_joycon_slider = {
+ -23.7f, -118.2f, -23.7f, -117.3f, -23.7f, 96.6f, -22.8f, 96.6f, -21.5f, 97.2f, -21.5f,
+ 98.1f, -21.2f, 106.7f, -20.8f, 107.5f, -20.1f, 108.2f, -19.2f, 108.2f, -16.4f, 108.1f,
+ -15.8f, 107.5f, -15.8f, 106.5f, -15.8f, 62.8f, -16.3f, 61.9f, -15.8f, 61.0f, -17.3f,
+ 60.3f, -19.1f, 58.9f, -19.1f, 58.1f, -19.1f, 57.2f, -19.1f, 34.5f, -17.9f, 33.9f,
+ -17.2f, 33.2f, -16.6f, 32.4f, -16.2f, 31.6f, -15.8f, 30.7f, -15.8f, 29.7f, -15.8f,
+ 28.8f, -15.8f, -46.4f, -16.3f, -47.3f, -15.8f, -48.1f, -17.4f, -48.8f, -19.1f, -49.4f,
+ -19.1f, -50.1f, -19.1f, -51.0f, -19.1f, -51.9f, -19.1f, -73.7f, -19.1f, -74.5f, -17.5f,
+ -75.2f, -16.4f, -76.7f, -16.0f, -77.6f, -15.8f, -78.5f, -15.8f, -79.4f, -15.8f, -80.4f,
+ -15.8f, -118.2f, -15.8f, -118.2f, -18.3f, -118.2f,
+};
+
+constexpr std::array<float, 66 * 2> left_joycon_sideview = {
+ -158.8f, -133.5f, -159.8f, -133.5f, -173.5f, -133.3f, -174.5f, -133.0f, -175.4f, -132.6f,
+ -176.2f, -132.1f, -177.0f, -131.5f, -177.7f, -130.9f, -178.3f, -130.1f, -179.4f, -128.5f,
+ -179.8f, -127.6f, -180.4f, -125.7f, -180.6f, -124.7f, -180.7f, -123.8f, -180.7f, -122.8f,
+ -180.0f, 128.8f, -179.6f, 129.7f, -179.1f, 130.5f, -177.9f, 132.1f, -177.2f, 132.7f,
+ -176.4f, 133.3f, -175.6f, 133.8f, -174.7f, 134.3f, -173.8f, 134.6f, -172.8f, 134.8f,
+ -170.9f, 135.0f, -169.9f, 135.0f, -156.1f, 134.8f, -155.2f, 134.6f, -154.2f, 134.3f,
+ -153.3f, 134.0f, -152.4f, 133.6f, -151.6f, 133.1f, -150.7f, 132.6f, -149.9f, 132.0f,
+ -149.2f, 131.4f, -148.5f, 130.7f, -147.1f, 129.2f, -146.5f, 128.5f, -146.0f, 127.7f,
+ -145.5f, 126.8f, -145.0f, 126.0f, -144.6f, 125.1f, -144.2f, 124.1f, -143.9f, 123.2f,
+ -143.7f, 122.2f, -143.6f, 121.3f, -143.5f, 120.3f, -143.5f, 119.3f, -144.4f, -123.4f,
+ -144.8f, -124.3f, -145.3f, -125.1f, -145.8f, -126.0f, -146.3f, -126.8f, -147.0f, -127.5f,
+ -147.6f, -128.3f, -148.3f, -129.0f, -149.0f, -129.6f, -149.8f, -130.3f, -150.6f, -130.8f,
+ -151.4f, -131.4f, -152.2f, -131.9f, -153.1f, -132.3f, -155.9f, -133.3f, -156.8f, -133.5f,
+ -157.8f, -133.5f,
+};
+
+constexpr std::array<float, 40 * 2> left_joycon_body_trigger = {
+ -146.1f, -124.3f, -146.0f, -122.0f, -145.8f, -119.7f, -145.7f, -117.4f, -145.4f, -112.8f,
+ -145.3f, -110.5f, -145.0f, -105.9f, -144.9f, -103.6f, -144.6f, -99.1f, -144.5f, -96.8f,
+ -144.5f, -89.9f, -144.5f, -87.6f, -144.5f, -83.0f, -144.5f, -80.7f, -144.5f, -80.3f,
+ -142.4f, -82.4f, -141.4f, -84.5f, -140.2f, -86.4f, -138.8f, -88.3f, -137.4f, -90.1f,
+ -134.5f, -93.6f, -133.0f, -95.3f, -130.0f, -98.8f, -128.5f, -100.6f, -127.1f, -102.4f,
+ -125.8f, -104.3f, -124.7f, -106.3f, -123.9f, -108.4f, -125.1f, -110.2f, -127.4f, -110.3f,
+ -129.7f, -110.3f, -134.2f, -110.5f, -136.4f, -111.4f, -138.1f, -112.8f, -139.4f, -114.7f,
+ -140.5f, -116.8f, -141.4f, -118.9f, -143.3f, -123.1f, -144.6f, -124.9f, -146.2f, -126.0f,
+};
+
+constexpr std::array<float, 49 * 2> left_joycon_topview = {
+ -184.8f, -20.8f, -185.6f, -21.1f, -186.4f, -21.5f, -187.1f, -22.1f, -187.8f, -22.6f,
+ -188.4f, -23.2f, -189.6f, -24.5f, -190.2f, -25.2f, -190.7f, -25.9f, -191.1f, -26.7f,
+ -191.4f, -27.5f, -191.6f, -28.4f, -191.7f, -29.2f, -191.7f, -30.1f, -191.5f, -47.7f,
+ -191.2f, -48.5f, -191.0f, -49.4f, -190.7f, -50.2f, -190.3f, -51.0f, -190.0f, -51.8f,
+ -189.6f, -52.6f, -189.1f, -53.4f, -188.6f, -54.1f, -187.5f, -55.4f, -186.9f, -56.1f,
+ -186.2f, -56.7f, -185.5f, -57.2f, -184.0f, -58.1f, -183.3f, -58.5f, -182.5f, -58.9f,
+ -181.6f, -59.2f, -180.8f, -59.5f, -179.9f, -59.7f, -179.1f, -59.9f, -178.2f, -60.0f,
+ -174.7f, -60.1f, -168.5f, -60.2f, -162.4f, -60.3f, -156.2f, -60.4f, -149.2f, -60.5f,
+ -143.0f, -60.6f, -136.9f, -60.7f, -130.7f, -60.8f, -123.7f, -60.9f, -117.5f, -61.0f,
+ -110.5f, -61.1f, -94.4f, -60.4f, -94.4f, -59.5f, -94.4f, -20.6f,
+};
+
+constexpr std::array<float, 41 * 2> left_joycon_slider_topview = {
+ -95.1f, -51.5f, -95.0f, -51.5f, -91.2f, -51.6f, -91.2f, -51.7f, -91.1f, -52.4f, -91.1f, -52.6f,
+ -91.0f, -54.1f, -86.3f, -54.0f, -86.0f, -53.9f, -85.9f, -53.8f, -85.6f, -53.4f, -85.5f, -53.2f,
+ -85.5f, -53.1f, -85.4f, -52.9f, -85.4f, -52.8f, -85.3f, -52.4f, -85.3f, -52.3f, -85.4f, -27.2f,
+ -85.4f, -27.1f, -85.5f, -27.0f, -85.5f, -26.9f, -85.6f, -26.7f, -85.6f, -26.6f, -85.7f, -26.5f,
+ -85.9f, -26.4f, -86.0f, -26.3f, -86.4f, -26.0f, -86.5f, -25.9f, -86.7f, -25.8f, -87.1f, -25.7f,
+ -90.4f, -25.8f, -90.7f, -25.9f, -90.8f, -26.0f, -90.9f, -26.3f, -91.0f, -26.4f, -91.0f, -26.5f,
+ -91.1f, -26.7f, -91.1f, -26.9f, -91.2f, -28.9f, -95.2f, -29.1f, -95.2f, -29.2f,
+};
+
+constexpr std::array<float, 42 * 2> left_joycon_sideview_zl = {
+ -148.9f, -128.2f, -148.7f, -126.6f, -148.4f, -124.9f, -148.2f, -123.3f, -147.9f, -121.7f,
+ -147.7f, -120.1f, -147.4f, -118.5f, -147.2f, -116.9f, -146.9f, -115.3f, -146.4f, -112.1f,
+ -146.1f, -110.5f, -145.9f, -108.9f, -145.6f, -107.3f, -144.2f, -107.3f, -142.6f, -107.5f,
+ -141.0f, -107.8f, -137.8f, -108.3f, -136.2f, -108.6f, -131.4f, -109.4f, -129.8f, -109.7f,
+ -125.6f, -111.4f, -124.5f, -112.7f, -123.9f, -114.1f, -123.8f, -115.8f, -123.8f, -117.4f,
+ -123.9f, -120.6f, -124.5f, -122.1f, -125.8f, -123.1f, -127.4f, -123.4f, -129.0f, -123.6f,
+ -130.6f, -124.0f, -132.1f, -124.4f, -133.7f, -124.8f, -135.3f, -125.3f, -136.8f, -125.9f,
+ -138.3f, -126.4f, -139.9f, -126.9f, -141.4f, -127.5f, -142.9f, -128.0f, -144.5f, -128.5f,
+ -146.0f, -129.0f, -147.6f, -129.4f,
+};
+
+constexpr std::array<float, 72 * 2> left_joystick_sideview = {
+ -14.7f, -3.8f, -15.2f, -5.6f, -15.2f, -7.6f, -15.5f, -17.6f, -17.4f, -18.3f, -19.4f, -18.2f,
+ -21.3f, -17.6f, -22.8f, -16.4f, -23.4f, -14.5f, -23.4f, -12.5f, -24.1f, -8.6f, -24.8f, -6.7f,
+ -25.3f, -4.8f, -25.7f, -2.8f, -25.9f, -0.8f, -26.0f, 1.2f, -26.0f, 3.2f, -25.8f, 5.2f,
+ -25.5f, 7.2f, -25.0f, 9.2f, -24.4f, 11.1f, -23.7f, 13.0f, -23.4f, 14.9f, -23.4f, 16.9f,
+ -23.3f, 18.9f, -22.0f, 20.5f, -20.2f, 21.3f, -18.3f, 21.6f, -16.3f, 21.4f, -15.3f, 19.9f,
+ -15.3f, 17.8f, -15.2f, 7.8f, -13.5f, 6.4f, -12.4f, 7.2f, -11.4f, 8.9f, -10.2f, 10.5f,
+ -8.7f, 11.8f, -7.1f, 13.0f, -5.3f, 14.0f, -3.5f, 14.7f, -1.5f, 15.0f, 0.5f, 15.0f,
+ 2.5f, 14.7f, 4.4f, 14.2f, 6.3f, 13.4f, 8.0f, 12.4f, 9.6f, 11.1f, 10.9f, 9.6f,
+ 12.0f, 7.9f, 12.7f, 6.0f, 13.2f, 4.1f, 13.3f, 2.1f, 13.2f, 0.1f, 12.9f, -1.9f,
+ 12.2f, -3.8f, 11.3f, -5.6f, 10.2f, -7.2f, 8.8f, -8.6f, 7.1f, -9.8f, 5.4f, -10.8f,
+ 3.5f, -11.5f, 1.5f, -11.9f, -0.5f, -12.0f, -2.5f, -11.8f, -4.4f, -11.3f, -6.2f, -10.4f,
+ -8.0f, -9.4f, -9.6f, -8.2f, -10.9f, -6.7f, -11.9f, -4.9f, -12.8f, -3.2f, -13.5f, -3.8f,
+};
+
+constexpr std::array<float, 63 * 2> left_joystick_L_topview = {
+ -186.7f, -43.7f, -186.4f, -43.7f, -110.6f, -43.4f, -110.6f, -43.1f, -110.7f, -34.3f,
+ -110.7f, -34.0f, -110.8f, -33.7f, -111.1f, -32.9f, -111.2f, -32.6f, -111.4f, -32.3f,
+ -111.5f, -32.1f, -111.7f, -31.8f, -111.8f, -31.5f, -112.0f, -31.3f, -112.2f, -31.0f,
+ -112.4f, -30.8f, -112.8f, -30.3f, -113.0f, -30.1f, -114.1f, -29.1f, -114.3f, -28.9f,
+ -114.6f, -28.7f, -114.8f, -28.6f, -115.1f, -28.4f, -115.3f, -28.3f, -115.6f, -28.1f,
+ -115.9f, -28.0f, -116.4f, -27.8f, -116.7f, -27.7f, -117.3f, -27.6f, -117.6f, -27.5f,
+ -182.9f, -27.6f, -183.5f, -27.7f, -183.8f, -27.8f, -184.4f, -27.9f, -184.6f, -28.1f,
+ -184.9f, -28.2f, -185.4f, -28.5f, -185.7f, -28.7f, -185.9f, -28.8f, -186.2f, -29.0f,
+ -186.4f, -29.2f, -187.0f, -29.9f, -187.2f, -30.1f, -187.6f, -30.6f, -187.8f, -30.8f,
+ -187.9f, -31.1f, -188.1f, -31.3f, -188.2f, -31.6f, -188.4f, -31.9f, -188.5f, -32.1f,
+ -188.6f, -32.4f, -188.8f, -33.3f, -188.9f, -33.6f, -188.9f, -33.9f, -188.8f, -39.9f,
+ -188.8f, -40.2f, -188.7f, -41.1f, -188.7f, -41.4f, -188.6f, -41.7f, -188.0f, -43.1f,
+ -187.9f, -43.4f, -187.6f, -43.6f, -187.3f, -43.7f,
+};
+
+constexpr std::array<float, 44 * 2> left_joystick_ZL_topview = {
+ -179.4f, -53.3f, -177.4f, -53.3f, -111.2f, -53.3f, -111.3f, -53.3f, -111.5f, -58.6f,
+ -111.8f, -60.5f, -112.2f, -62.4f, -113.1f, -66.1f, -113.8f, -68.0f, -114.5f, -69.8f,
+ -115.3f, -71.5f, -116.3f, -73.2f, -117.3f, -74.8f, -118.5f, -76.4f, -119.8f, -77.8f,
+ -121.2f, -79.1f, -122.8f, -80.2f, -124.4f, -81.2f, -126.2f, -82.0f, -128.1f, -82.6f,
+ -130.0f, -82.9f, -131.9f, -83.0f, -141.5f, -82.9f, -149.3f, -82.8f, -153.1f, -82.6f,
+ -155.0f, -82.1f, -156.8f, -81.6f, -158.7f, -80.9f, -160.4f, -80.2f, -162.2f, -79.3f,
+ -163.8f, -78.3f, -165.4f, -77.2f, -166.9f, -76.0f, -168.4f, -74.7f, -169.7f, -73.3f,
+ -172.1f, -70.3f, -173.2f, -68.7f, -174.2f, -67.1f, -175.2f, -65.4f, -176.1f, -63.7f,
+ -178.7f, -58.5f, -179.6f, -56.8f, -180.4f, -55.1f, -181.3f, -53.3f,
+};
+
+void PlayerControlPreview::DrawProBody(QPainter& p, const QPointF center) {
+ std::array<QPointF, pro_left_handle.size() / 2> qleft_handle;
+ std::array<QPointF, pro_left_handle.size() / 2> qright_handle;
+ std::array<QPointF, pro_body.size()> qbody;
+ constexpr int radius1 = 32;
+
+ for (std::size_t point = 0; point < pro_left_handle.size() / 2; ++point) {
+ const float left_x = pro_left_handle[point * 2 + 0];
+ const float left_y = pro_left_handle[point * 2 + 1];
+
+ qleft_handle[point] = center + QPointF(left_x, left_y);
+ qright_handle[point] = center + QPointF(-left_x, left_y);
+ }
+ for (std::size_t point = 0; point < pro_body.size() / 2; ++point) {
+ const float body_x = pro_body[point * 2 + 0];
+ const float body_y = pro_body[point * 2 + 1];
+
+ qbody[point] = center + QPointF(body_x, body_y);
+ qbody[pro_body.size() - 1 - point] = center + QPointF(-body_x, body_y);
+ }
+
+ // Draw left handle body
+ p.setPen(colors.outline);
+ p.setBrush(colors.left);
+ DrawPolygon(p, qleft_handle);
+
+ // Draw right handle body
+ p.setBrush(colors.right);
+ DrawPolygon(p, qright_handle);
+
+ // Draw body
+ p.setBrush(colors.primary);
+ DrawPolygon(p, qbody);
+
+ // Draw joycon circles
+ p.setBrush(colors.transparent);
+ p.drawEllipse(center + QPoint(-111, -55), radius1, radius1);
+ p.drawEllipse(center + QPoint(51, 0), radius1, radius1);
+}
+
+void PlayerControlPreview::DrawGCBody(QPainter& p, const QPointF center) {
+ std::array<QPointF, gc_left_body.size() / 2> qleft_handle;
+ std::array<QPointF, gc_left_body.size() / 2> qright_handle;
+ std::array<QPointF, gc_body.size()> qbody;
+ std::array<QPointF, 8> left_hex;
+ std::array<QPointF, 8> right_hex;
+ constexpr float angle = 2 * 3.1415f / 8;
+
+ for (std::size_t point = 0; point < gc_left_body.size() / 2; ++point) {
+ const float body_x = gc_left_body[point * 2 + 0];
+ const float body_y = gc_left_body[point * 2 + 1];
+
+ qleft_handle[point] = center + QPointF(body_x, body_y);
+ qright_handle[point] = center + QPointF(-body_x, body_y);
+ }
+ for (std::size_t point = 0; point < gc_body.size() / 2; ++point) {
+ const float body_x = gc_body[point * 2 + 0];
+ const float body_y = gc_body[point * 2 + 1];
+
+ qbody[point] = center + QPointF(body_x, body_y);
+ qbody[gc_body.size() - 1 - point] = center + QPointF(-body_x, body_y);
+ }
+ for (std::size_t point = 0; point < 8; ++point) {
+ const float point_cos = std::cos(point * angle);
+ const float point_sin = std::sin(point * angle);
+
+ left_hex[point] = center + QPointF(34 * point_cos - 111, 34 * point_sin - 44);
+ right_hex[point] = center + QPointF(26 * point_cos + 61, 26 * point_sin + 37);
+ }
+
+ // Draw body
+ p.setPen(colors.outline);
+ p.setBrush(colors.primary);
+ DrawPolygon(p, qbody);
+
+ // Draw left handle body
+ p.setBrush(colors.left);
+ DrawPolygon(p, qleft_handle);
+
+ // Draw right handle body
+ p.setBrush(colors.right);
+ DrawPolygon(p, qright_handle);
+
+ DrawText(p, center + QPoint(0, -58), 4.7f, tr("START/PAUSE"));
+
+ // Draw right joystick body
+ p.setBrush(colors.button);
+ DrawCircle(p, center + QPointF(61, 37), 23.5f);
+
+ // Draw joystick details
+ p.setBrush(colors.transparent);
+ DrawPolygon(p, left_hex);
+ DrawPolygon(p, right_hex);
+}
+
+void PlayerControlPreview::DrawHandheldBody(QPainter& p, const QPointF center) {
+ const std::size_t body_outline_end = handheld_body.size() / 2 - 6;
+ const std::size_t bezel_outline_end = handheld_bezel.size() / 2 - 6;
+ const std::size_t bezel_inline_size = 4;
+ const std::size_t bezel_inline_start = 35;
+ std::array<QPointF, left_joycon_body.size() / 2> left_joycon;
+ std::array<QPointF, left_joycon_body.size() / 2> right_joycon;
+ std::array<QPointF, handheld_body.size() / 2> qhandheld_body;
+ std::array<QPointF, body_outline_end> qhandheld_body_outline;
+ std::array<QPointF, handheld_bezel.size() / 2> qhandheld_bezel;
+ std::array<QPointF, bezel_inline_size> qhandheld_bezel_inline;
+ std::array<QPointF, bezel_outline_end> qhandheld_bezel_outline;
+ std::array<QPointF, handheld_buttons.size() / 2> qhandheld_buttons;
+
+ for (std::size_t point = 0; point < left_joycon_body.size() / 2; ++point) {
+ left_joycon[point] =
+ center + QPointF(left_joycon_body[point * 2], left_joycon_body[point * 2 + 1]);
+ right_joycon[point] =
+ center + QPointF(-left_joycon_body[point * 2], left_joycon_body[point * 2 + 1]);
+ }
+ for (std::size_t point = 0; point < body_outline_end; ++point) {
+ qhandheld_body_outline[point] =
+ center + QPointF(handheld_body[point * 2], handheld_body[point * 2 + 1]);
+ }
+ for (std::size_t point = 0; point < handheld_body.size() / 2; ++point) {
+ qhandheld_body[point] =
+ center + QPointF(handheld_body[point * 2], handheld_body[point * 2 + 1]);
+ }
+ for (std::size_t point = 0; point < handheld_bezel.size() / 2; ++point) {
+ qhandheld_bezel[point] =
+ center + QPointF(handheld_bezel[point * 2], handheld_bezel[point * 2 + 1]);
+ }
+ for (std::size_t point = 0; point < bezel_outline_end; ++point) {
+ qhandheld_bezel_outline[point] =
+ center + QPointF(handheld_bezel[point * 2], handheld_bezel[point * 2 + 1]);
+ }
+ for (std::size_t point = 0; point < bezel_inline_size; ++point) {
+ qhandheld_bezel_inline[point] =
+ center + QPointF(handheld_bezel[(point + bezel_inline_start) * 2],
+ handheld_bezel[(point + bezel_inline_start) * 2 + 1]);
+ }
+ for (std::size_t point = 0; point < handheld_buttons.size() / 2; ++point) {
+ qhandheld_buttons[point] =
+ center + QPointF(handheld_buttons[point * 2], handheld_buttons[point * 2 + 1]);
+ }
+
+ // Draw left joycon
+ p.setPen(colors.outline);
+ p.setBrush(colors.left);
+ DrawPolygon(p, left_joycon);
+
+ // Draw right joycon
+ p.setPen(colors.outline);
+ p.setBrush(colors.right);
+ DrawPolygon(p, right_joycon);
+
+ // Draw Handheld buttons
+ p.setPen(colors.outline);
+ p.setBrush(colors.button);
+ DrawPolygon(p, qhandheld_buttons);
+
+ // Draw handheld body
+ p.setPen(colors.transparent);
+ p.setBrush(colors.primary);
+ DrawPolygon(p, qhandheld_body);
+ p.setPen(colors.outline);
+ p.setBrush(colors.transparent);
+ DrawPolygon(p, qhandheld_body_outline);
+
+ // Draw Handheld bezel
+ p.setPen(colors.transparent);
+ p.setBrush(colors.button);
+ DrawPolygon(p, qhandheld_bezel);
+ p.setPen(colors.outline);
+ p.setBrush(colors.transparent);
+ DrawPolygon(p, qhandheld_bezel_outline);
+ DrawPolygon(p, qhandheld_bezel_inline);
+}
+
+void PlayerControlPreview::DrawDualBody(QPainter& p, const QPointF center) {
+ std::array<QPointF, left_joycon_body.size() / 2> left_joycon;
+ std::array<QPointF, left_joycon_body.size() / 2> right_joycon;
+ std::array<QPointF, left_joycon_slider.size() / 2> qleft_joycon_slider;
+ std::array<QPointF, left_joycon_slider.size() / 2> qright_joycon_slider;
+ std::array<QPointF, left_joycon_slider_topview.size() / 2> qleft_joycon_slider_topview;
+ std::array<QPointF, left_joycon_slider_topview.size() / 2> qright_joycon_slider_topview;
+ std::array<QPointF, left_joycon_topview.size() / 2> qleft_joycon_topview;
+ std::array<QPointF, left_joycon_topview.size() / 2> qright_joycon_topview;
+ constexpr float size = 1.61f;
+ constexpr float size2 = 0.9f;
+ constexpr float offset = 209.3f;
+
+ for (std::size_t point = 0; point < left_joycon_body.size() / 2; ++point) {
+ const float body_x = left_joycon_body[point * 2 + 0];
+ const float body_y = left_joycon_body[point * 2 + 1];
+
+ left_joycon[point] = center + QPointF(body_x * size + offset, body_y * size - 1);
+ right_joycon[point] = center + QPointF(-body_x * size - offset, body_y * size - 1);
+ }
+ for (std::size_t point = 0; point < left_joycon_slider.size() / 2; ++point) {
+ const float slider_x = left_joycon_slider[point * 2 + 0];
+ const float slider_y = left_joycon_slider[point * 2 + 1];
+
+ qleft_joycon_slider[point] = center + QPointF(slider_x, slider_y);
+ qright_joycon_slider[point] = center + QPointF(-slider_x, slider_y);
+ }
+ for (std::size_t point = 0; point < left_joycon_topview.size() / 2; ++point) {
+ const float top_view_x = left_joycon_topview[point * 2 + 0];
+ const float top_view_y = left_joycon_topview[point * 2 + 1];
+
+ qleft_joycon_topview[point] =
+ center + QPointF(top_view_x * size2 - 52, top_view_y * size2 - 52);
+ qright_joycon_topview[point] =
+ center + QPointF(-top_view_x * size2 + 52, top_view_y * size2 - 52);
+ }
+ for (std::size_t point = 0; point < left_joycon_slider_topview.size() / 2; ++point) {
+ const float top_view_x = left_joycon_slider_topview[point * 2 + 0];
+ const float top_view_y = left_joycon_slider_topview[point * 2 + 1];
+
+ qleft_joycon_slider_topview[point] =
+ center + QPointF(top_view_x * size2 - 52, top_view_y * size2 - 52);
+ qright_joycon_slider_topview[point] =
+ center + QPointF(-top_view_x * size2 + 52, top_view_y * size2 - 52);
+ }
+
+ // right joycon body
+ p.setPen(colors.outline);
+ p.setBrush(colors.right);
+ DrawPolygon(p, right_joycon);
+
+ // Left joycon body
+ p.setPen(colors.outline);
+ p.setBrush(colors.left);
+ DrawPolygon(p, left_joycon);
+
+ // Slider release button top view
+ p.setBrush(colors.button);
+ DrawRoundRectangle(p, center + QPoint(-149, -108), 12, 11, 2);
+ DrawRoundRectangle(p, center + QPoint(149, -108), 12, 11, 2);
+
+ // Joycon slider top view
+ p.setBrush(colors.slider);
+ DrawPolygon(p, qleft_joycon_slider_topview);
+ p.drawLine(center + QPointF(-133.8f, -99.0f), center + QPointF(-133.8f, -78.5f));
+ DrawPolygon(p, qright_joycon_slider_topview);
+ p.drawLine(center + QPointF(133.8f, -99.0f), center + QPointF(133.8f, -78.5f));
+
+ // Joycon body top view
+ p.setBrush(colors.left);
+ DrawPolygon(p, qleft_joycon_topview);
+ p.setBrush(colors.right);
+ DrawPolygon(p, qright_joycon_topview);
+
+ // Right SR and SL sideview buttons
+ p.setPen(colors.outline);
+ p.setBrush(colors.slider_button);
+ DrawRoundRectangle(p, center + QPoint(19, 47), 7, 22, 1);
+ DrawRoundRectangle(p, center + QPoint(19, -62), 7, 22, 1);
+
+ // Left SR and SL sideview buttons
+ DrawRoundRectangle(p, center + QPoint(-19, 47), 7, 22, 1);
+ DrawRoundRectangle(p, center + QPoint(-19, -62), 7, 22, 1);
+
+ // Right Sideview body
+ p.setBrush(colors.slider);
+ DrawPolygon(p, qright_joycon_slider);
+
+ // Left Sideview body
+ p.setBrush(colors.slider);
+ DrawPolygon(p, qleft_joycon_slider);
+}
+
+void PlayerControlPreview::DrawLeftBody(QPainter& p, const QPointF center) {
+ std::array<QPointF, left_joycon_body.size() / 2> left_joycon;
+ std::array<QPointF, left_joycon_sideview.size() / 2> qleft_joycon_sideview;
+ std::array<QPointF, left_joycon_body_trigger.size() / 2> qleft_joycon_trigger;
+ std::array<QPointF, left_joycon_slider.size() / 2> qleft_joycon_slider;
+ std::array<QPointF, left_joycon_slider_topview.size() / 2> qleft_joycon_slider_topview;
+ std::array<QPointF, left_joycon_topview.size() / 2> qleft_joycon_topview;
+ constexpr float size = 1.78f;
+ constexpr float size2 = 1.1115f;
+ constexpr float offset = 312.39f;
+ constexpr float offset2 = 335;
+
+ for (std::size_t point = 0; point < left_joycon_body.size() / 2; ++point) {
+ left_joycon[point] = center + QPointF(left_joycon_body[point * 2] * size + offset,
+ left_joycon_body[point * 2 + 1] * size - 1);
+ }
+
+ for (std::size_t point = 0; point < left_joycon_sideview.size() / 2; ++point) {
+ qleft_joycon_sideview[point] =
+ center + QPointF(left_joycon_sideview[point * 2] * size2 + offset2,
+ left_joycon_sideview[point * 2 + 1] * size2 + 2);
+ }
+ for (std::size_t point = 0; point < left_joycon_slider.size() / 2; ++point) {
+ qleft_joycon_slider[point] = center + QPointF(left_joycon_slider[point * 2] * size2 + 81,
+ left_joycon_slider[point * 2 + 1] * size2);
+ }
+ for (std::size_t point = 0; point < left_joycon_body_trigger.size() / 2; ++point) {
+ qleft_joycon_trigger[point] =
+ center + QPointF(left_joycon_body_trigger[point * 2] * size2 + offset2,
+ left_joycon_body_trigger[point * 2 + 1] * size2 + 2);
+ }
+ for (std::size_t point = 0; point < left_joycon_topview.size() / 2; ++point) {
+ qleft_joycon_topview[point] =
+ center + QPointF(left_joycon_topview[point * 2], left_joycon_topview[point * 2 + 1]);
+ }
+ for (std::size_t point = 0; point < left_joycon_slider_topview.size() / 2; ++point) {
+ qleft_joycon_slider_topview[point] =
+ center + QPointF(left_joycon_slider_topview[point * 2],
+ left_joycon_slider_topview[point * 2 + 1]);
+ }
+
+ // Joycon body
+ p.setPen(colors.outline);
+ p.setBrush(colors.left);
+ DrawPolygon(p, left_joycon);
+ DrawPolygon(p, qleft_joycon_trigger);
+
+ // Slider release button top view
+ p.setBrush(colors.button);
+ DrawRoundRectangle(p, center + QPoint(-107, -62), 14, 12, 2);
+
+ // Joycon slider top view
+ p.setBrush(colors.slider);
+ DrawPolygon(p, qleft_joycon_slider_topview);
+ p.drawLine(center + QPointF(-91.1f, -51.7f), center + QPointF(-91.1f, -26.5f));
+
+ // Joycon body top view
+ p.setBrush(colors.left);
+ DrawPolygon(p, qleft_joycon_topview);
+
+ // Slider release button
+ p.setBrush(colors.button);
+ DrawRoundRectangle(p, center + QPoint(175, -110), 12, 14, 2);
+
+ // Sideview body
+ p.setBrush(colors.left);
+ DrawPolygon(p, qleft_joycon_sideview);
+ p.setBrush(colors.slider);
+ DrawPolygon(p, qleft_joycon_slider);
+
+ const QPointF sideview_center = QPointF(155, 0) + center;
+
+ // Sideview slider body
+ p.setBrush(colors.slider);
+ DrawRoundRectangle(p, sideview_center + QPointF(0, -5), 28, 253, 3);
+ p.setBrush(colors.button2);
+ DrawRoundRectangle(p, sideview_center + QPointF(0, 97), 22.44f, 44.66f, 3);
+
+ // Slider decorations
+ p.setPen(colors.outline);
+ p.setBrush(colors.slider_arrow);
+ DrawArrow(p, sideview_center + QPoint(0, 83), Direction::Down, 2.2f);
+ DrawArrow(p, sideview_center + QPoint(0, 96), Direction::Down, 2.2f);
+ DrawArrow(p, sideview_center + QPoint(0, 109), Direction::Down, 2.2f);
+ DrawCircle(p, sideview_center + QPointF(0, 19), 4.44f);
+
+ // LED indicators
+ const float led_size = 5.0f;
+ const QPointF led_position = sideview_center + QPointF(0, -36);
+ int led_count = 0;
+ for (const auto color : led_color) {
+ p.setBrush(color);
+ DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
+ }
+}
+
+void PlayerControlPreview::DrawRightBody(QPainter& p, const QPointF center) {
+ std::array<QPointF, left_joycon_body.size() / 2> right_joycon;
+ std::array<QPointF, left_joycon_sideview.size() / 2> qright_joycon_sideview;
+ std::array<QPointF, left_joycon_body_trigger.size() / 2> qright_joycon_trigger;
+ std::array<QPointF, left_joycon_slider.size() / 2> qright_joycon_slider;
+ std::array<QPointF, left_joycon_slider_topview.size() / 2> qright_joycon_slider_topview;
+ std::array<QPointF, left_joycon_topview.size() / 2> qright_joycon_topview;
+ constexpr float size = 1.78f;
+ constexpr float size2 = 1.1115f;
+ constexpr float offset = 312.39f;
+ constexpr float offset2 = 335;
+
+ for (std::size_t point = 0; point < left_joycon_body.size() / 2; ++point) {
+ right_joycon[point] = center + QPointF(-left_joycon_body[point * 2] * size - offset,
+ left_joycon_body[point * 2 + 1] * size - 1);
+ }
+
+ for (std::size_t point = 0; point < left_joycon_sideview.size() / 2; ++point) {
+ qright_joycon_sideview[point] =
+ center + QPointF(-left_joycon_sideview[point * 2] * size2 - offset2,
+ left_joycon_sideview[point * 2 + 1] * size2 + 2);
+ }
+ for (std::size_t point = 0; point < left_joycon_body_trigger.size() / 2; ++point) {
+ qright_joycon_trigger[point] =
+ center + QPointF(-left_joycon_body_trigger[point * 2] * size2 - offset2,
+ left_joycon_body_trigger[point * 2 + 1] * size2 + 2);
+ }
+ for (std::size_t point = 0; point < left_joycon_slider.size() / 2; ++point) {
+ qright_joycon_slider[point] = center + QPointF(-left_joycon_slider[point * 2] * size2 - 81,
+ left_joycon_slider[point * 2 + 1] * size2);
+ }
+ for (std::size_t point = 0; point < left_joycon_topview.size() / 2; ++point) {
+ qright_joycon_topview[point] =
+ center + QPointF(-left_joycon_topview[point * 2], left_joycon_topview[point * 2 + 1]);
+ }
+ for (std::size_t point = 0; point < left_joycon_slider_topview.size() / 2; ++point) {
+ qright_joycon_slider_topview[point] =
+ center + QPointF(-left_joycon_slider_topview[point * 2],
+ left_joycon_slider_topview[point * 2 + 1]);
+ }
+
+ // Joycon body
+ p.setPen(colors.outline);
+ p.setBrush(colors.left);
+ DrawPolygon(p, right_joycon);
+ DrawPolygon(p, qright_joycon_trigger);
+
+ // Slider release button top view
+ p.setBrush(colors.button);
+ DrawRoundRectangle(p, center + QPoint(107, -62), 14, 12, 2);
+
+ // Joycon slider top view
+ p.setBrush(colors.slider);
+ DrawPolygon(p, qright_joycon_slider_topview);
+ p.drawLine(center + QPointF(91.1f, -51.7f), center + QPointF(91.1f, -26.5f));
+
+ // Joycon body top view
+ p.setBrush(colors.left);
+ DrawPolygon(p, qright_joycon_topview);
+
+ // Slider release button
+ p.setBrush(colors.button);
+ DrawRoundRectangle(p, center + QPoint(-175, -110), 12, 14, 2);
+
+ // Sideview body
+ p.setBrush(colors.left);
+ DrawPolygon(p, qright_joycon_sideview);
+ p.setBrush(colors.slider);
+ DrawPolygon(p, qright_joycon_slider);
+
+ const QPointF sideview_center = QPointF(-155, 0) + center;
+
+ // Sideview slider body
+ p.setBrush(colors.slider);
+ DrawRoundRectangle(p, sideview_center + QPointF(0, -5), 28, 253, 3);
+ p.setBrush(colors.button2);
+ DrawRoundRectangle(p, sideview_center + QPointF(0, 97), 22.44f, 44.66f, 3);
+
+ // Slider decorations
+ p.setPen(colors.outline);
+ p.setBrush(colors.slider_arrow);
+ DrawArrow(p, sideview_center + QPoint(0, 83), Direction::Down, 2.2f);
+ DrawArrow(p, sideview_center + QPoint(0, 96), Direction::Down, 2.2f);
+ DrawArrow(p, sideview_center + QPoint(0, 109), Direction::Down, 2.2f);
+ DrawCircle(p, sideview_center + QPointF(0, 19), 4.44f);
+
+ // LED indicators
+ const float led_size = 5.0f;
+ const QPointF led_position = sideview_center + QPointF(0, -36);
+ int led_count = 0;
+ for (const auto color : led_color) {
+ p.setBrush(color);
+ DrawRectangle(p, led_position + QPointF(0, 12 * led_count++), led_size, led_size);
+ }
+}
+
+void PlayerControlPreview::DrawProTriggers(QPainter& p, const QPointF center, bool left_pressed,
+ bool right_pressed) {
+ std::array<QPointF, pro_left_trigger.size() / 2> qleft_trigger;
+ std::array<QPointF, pro_left_trigger.size() / 2> qright_trigger;
+ std::array<QPointF, pro_body_top.size()> qbody_top;
+
+ for (std::size_t point = 0; point < pro_left_trigger.size() / 2; ++point) {
+ const float trigger_x = pro_left_trigger[point * 2 + 0];
+ const float trigger_y = pro_left_trigger[point * 2 + 1];
+
+ qleft_trigger[point] = center + QPointF(trigger_x, trigger_y + (left_pressed ? 2 : 0));
+ qright_trigger[point] = center + QPointF(-trigger_x, trigger_y + (right_pressed ? 2 : 0));
+ }
+
+ for (std::size_t point = 0; point < pro_body_top.size() / 2; ++point) {
+ const float top_x = pro_body_top[point * 2 + 0];
+ const float top_y = pro_body_top[point * 2 + 1];
+
+ qbody_top[pro_body_top.size() - 1 - point] = center + QPointF(-top_x, top_y);
+ qbody_top[point] = center + QPointF(top_x, top_y);
+ }
+
+ // Pro body detail
+ p.setPen(colors.outline);
+ p.setBrush(colors.primary);
+ DrawPolygon(p, qbody_top);
+
+ // Left trigger
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+
+ // Right trigger
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+}
+
+void PlayerControlPreview::DrawGCTriggers(QPainter& p, const QPointF center, bool left_pressed,
+ bool right_pressed) {
+ std::array<QPointF, left_gc_trigger.size() / 2> qleft_trigger;
+ std::array<QPointF, left_gc_trigger.size() / 2> qright_trigger;
+
+ for (std::size_t point = 0; point < left_gc_trigger.size() / 2; ++point) {
+ const float trigger_x = left_gc_trigger[point * 2 + 0];
+ const float trigger_y = left_gc_trigger[point * 2 + 1];
+
+ qleft_trigger[point] = center + QPointF(trigger_x, trigger_y + (left_pressed ? 10 : 0));
+ qright_trigger[point] = center + QPointF(-trigger_x, trigger_y + (right_pressed ? 10 : 0));
+ }
+
+ // Left trigger
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+
+ // Right trigger
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+
+ // Draw L text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, center + QPointF(-132, -119 + (left_pressed ? 10 : 0)), Symbol::L, 1.7f);
+
+ // Draw R text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font);
+ DrawSymbol(p, center + QPointF(121.5f, -119 + (right_pressed ? 10 : 0)), Symbol::R, 1.7f);
+}
+
+void PlayerControlPreview::DrawHandheldTriggers(QPainter& p, const QPointF center,
+ bool left_pressed, bool right_pressed) {
+ std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger;
+ std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger;
+
+ for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) {
+ const float left_trigger_x = left_joycon_trigger[point * 2 + 0];
+ const float left_trigger_y = left_joycon_trigger[point * 2 + 1];
+
+ qleft_trigger[point] =
+ center + QPointF(left_trigger_x, left_trigger_y + (left_pressed ? 0.5f : 0));
+ qright_trigger[point] =
+ center + QPointF(-left_trigger_x, left_trigger_y + (right_pressed ? 0.5f : 0));
+ }
+
+ // Left trigger
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+
+ // Right trigger
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+}
+
+void PlayerControlPreview::DrawDualTriggers(QPainter& p, const QPointF center, bool left_pressed,
+ bool right_pressed) {
+ std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger;
+ std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger;
+ constexpr float size = 1.62f;
+ constexpr float offset = 210.6f;
+ for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) {
+ const float left_trigger_x = left_joycon_trigger[point * 2 + 0];
+ const float left_trigger_y = left_joycon_trigger[point * 2 + 1];
+
+ qleft_trigger[point] = center + QPointF(left_trigger_x * size + offset,
+ left_trigger_y * size + (left_pressed ? 0.5f : 0));
+ qright_trigger[point] =
+ center + QPointF(-left_trigger_x * size - offset,
+ left_trigger_y * size + (right_pressed ? 0.5f : 0));
+ }
+
+ // Left trigger
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+
+ // Right trigger
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+}
+
+void PlayerControlPreview::DrawDualTriggersTopView(QPainter& p, const QPointF center,
+ bool left_pressed, bool right_pressed) {
+ std::array<QPointF, left_joystick_L_topview.size() / 2> qleft_trigger;
+ std::array<QPointF, left_joystick_L_topview.size() / 2> qright_trigger;
+ constexpr float size = 0.9f;
+
+ for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) {
+ const float top_view_x = left_joystick_L_topview[point * 2 + 0];
+ const float top_view_y = left_joystick_L_topview[point * 2 + 1];
+
+ qleft_trigger[point] = center + QPointF(top_view_x * size - 50, top_view_y * size - 52);
+ }
+ for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) {
+ const float top_view_x = left_joystick_L_topview[point * 2 + 0];
+ const float top_view_y = left_joystick_L_topview[point * 2 + 1];
+
+ qright_trigger[point] = center + QPointF(-top_view_x * size + 50, top_view_y * size - 52);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+
+ // Draw L text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(-183, -84), Symbol::L, 1.0f);
+
+ // Draw R text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(177, -84), Symbol::R, 1.0f);
+}
+
+void PlayerControlPreview::DrawDualZTriggersTopView(QPainter& p, const QPointF center,
+ bool left_pressed, bool right_pressed) {
+ std::array<QPointF, left_joystick_ZL_topview.size() / 2> qleft_trigger;
+ std::array<QPointF, left_joystick_ZL_topview.size() / 2> qright_trigger;
+ constexpr float size = 0.9f;
+
+ for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) {
+ qleft_trigger[point] =
+ center + QPointF(left_joystick_ZL_topview[point * 2] * size - 52,
+ left_joystick_ZL_topview[point * 2 + 1] * size - 52);
+ }
+ for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) {
+ qright_trigger[point] =
+ center + QPointF(-left_joystick_ZL_topview[point * 2] * size + 52,
+ left_joystick_ZL_topview[point * 2 + 1] * size - 52);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+
+ // Draw ZL text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(-180, -113), Symbol::ZL, 1.0f);
+
+ // Draw ZR text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(180, -113), Symbol::ZR, 1.0f);
+}
+
+void PlayerControlPreview::DrawLeftTriggers(QPainter& p, const QPointF center, bool left_pressed) {
+ std::array<QPointF, left_joycon_trigger.size() / 2> qleft_trigger;
+ constexpr float size = 1.78f;
+ constexpr float offset = 311.5f;
+
+ for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) {
+ qleft_trigger[point] = center + QPointF(left_joycon_trigger[point * 2] * size + offset,
+ left_joycon_trigger[point * 2 + 1] * size -
+ (left_pressed ? 0.5f : 1.0f));
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+}
+
+void PlayerControlPreview::DrawLeftZTriggers(QPainter& p, const QPointF center, bool left_pressed) {
+ std::array<QPointF, left_joycon_sideview_zl.size() / 2> qleft_trigger;
+ constexpr float size = 1.1115f;
+ constexpr float offset2 = 335;
+
+ for (std::size_t point = 0; point < left_joycon_sideview_zl.size() / 2; ++point) {
+ qleft_trigger[point] = center + QPointF(left_joycon_sideview_zl[point * 2] * size + offset2,
+ left_joycon_sideview_zl[point * 2 + 1] * size +
+ (left_pressed ? 1.5f : 1.0f));
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+ p.drawArc(center.x() + 158, center.y() + (left_pressed ? -203.5f : -204.0f), 77, 77, 225 * 16,
+ 44 * 16);
+}
+
+void PlayerControlPreview::DrawLeftTriggersTopView(QPainter& p, const QPointF center,
+ bool left_pressed) {
+ std::array<QPointF, left_joystick_L_topview.size() / 2> qleft_trigger;
+
+ for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) {
+ qleft_trigger[point] = center + QPointF(left_joystick_L_topview[point * 2],
+ left_joystick_L_topview[point * 2 + 1]);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+
+ // Draw L text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(-143, -36), Symbol::L, 1.0f);
+}
+
+void PlayerControlPreview::DrawLeftZTriggersTopView(QPainter& p, const QPointF center,
+ bool left_pressed) {
+ std::array<QPointF, left_joystick_ZL_topview.size() / 2> qleft_trigger;
+
+ for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) {
+ qleft_trigger[point] = center + QPointF(left_joystick_ZL_topview[point * 2],
+ left_joystick_ZL_topview[point * 2 + 1]);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(left_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qleft_trigger);
+
+ // Draw ZL text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(-140, -68), Symbol::ZL, 1.0f);
+}
+
+void PlayerControlPreview::DrawRightTriggers(QPainter& p, const QPointF center,
+ bool right_pressed) {
+ std::array<QPointF, left_joycon_trigger.size() / 2> qright_trigger;
+ constexpr float size = 1.78f;
+ constexpr float offset = 311.5f;
+
+ for (std::size_t point = 0; point < left_joycon_trigger.size() / 2; ++point) {
+ qright_trigger[point] = center + QPointF(-left_joycon_trigger[point * 2] * size - offset,
+ left_joycon_trigger[point * 2 + 1] * size -
+ (right_pressed ? 0.5f : 1.0f));
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+}
+
+void PlayerControlPreview::DrawRightZTriggers(QPainter& p, const QPointF center,
+ bool right_pressed) {
+ std::array<QPointF, left_joycon_sideview_zl.size() / 2> qright_trigger;
+ constexpr float size = 1.1115f;
+ constexpr float offset2 = 335;
+
+ for (std::size_t point = 0; point < left_joycon_sideview_zl.size() / 2; ++point) {
+ qright_trigger[point] =
+ center +
+ QPointF(-left_joycon_sideview_zl[point * 2] * size - offset2,
+ left_joycon_sideview_zl[point * 2 + 1] * size + (right_pressed ? 0.5f : 0) + 1);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+ p.drawArc(center.x() - 236, center.y() + (right_pressed ? -203.5f : -204.0f), 77, 77, 271 * 16,
+ 44 * 16);
+}
+
+void PlayerControlPreview::DrawRightTriggersTopView(QPainter& p, const QPointF center,
+ bool right_pressed) {
+ std::array<QPointF, left_joystick_L_topview.size() / 2> qright_trigger;
+
+ for (std::size_t point = 0; point < left_joystick_L_topview.size() / 2; ++point) {
+ qright_trigger[point] = center + QPointF(-left_joystick_L_topview[point * 2],
+ left_joystick_L_topview[point * 2 + 1]);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+
+ // Draw R text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(137, -36), Symbol::R, 1.0f);
+}
+
+void PlayerControlPreview::DrawRightZTriggersTopView(QPainter& p, const QPointF center,
+ bool right_pressed) {
+ std::array<QPointF, left_joystick_ZL_topview.size() / 2> qright_trigger;
+
+ for (std::size_t point = 0; point < left_joystick_ZL_topview.size() / 2; ++point) {
+ qright_trigger[point] = center + QPointF(-left_joystick_ZL_topview[point * 2],
+ left_joystick_ZL_topview[point * 2 + 1]);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(right_pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qright_trigger);
+
+ // Draw ZR text
+ p.setPen(colors.transparent);
+ p.setBrush(colors.font2);
+ DrawSymbol(p, center + QPointF(140, -68), Symbol::ZR, 1.0f);
+}
+
+void PlayerControlPreview::DrawJoystick(QPainter& p, const QPointF center, float size,
+ bool pressed) {
+ const float radius1 = 13.0f * size;
+ const float radius2 = 9.0f * size;
+
+ // Outer circle
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ DrawCircle(p, center, radius1);
+
+ // Cross
+ p.drawLine(center - QPoint(radius1, 0), center + QPoint(radius1, 0));
+ p.drawLine(center - QPoint(0, radius1), center + QPoint(0, radius1));
+
+ // Inner circle
+ p.setBrush(pressed ? colors.highlight2 : colors.button2);
+ DrawCircle(p, center, radius2);
+}
+
+void PlayerControlPreview::DrawJoystickSideview(QPainter& p, const QPointF center, float angle,
+ float size, bool pressed) {
+ QVector<QPointF> joystick;
+ joystick.reserve(static_cast<int>(left_joystick_sideview.size() / 2));
+
+ for (std::size_t point = 0; point < left_joystick_sideview.size() / 2; ++point) {
+ joystick.append(QPointF(left_joystick_sideview[point * 2] * size + (pressed ? 1 : 0),
+ left_joystick_sideview[point * 2 + 1] * size - 1));
+ }
+
+ // Rotate joystick
+ QTransform t;
+ t.translate(center.x(), center.y());
+ t.rotate(18 * angle);
+ QPolygonF p2 = t.map(QPolygonF(joystick));
+
+ // Draw joystick
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ p.drawPolygon(p2);
+ p.drawLine(p2.at(1), p2.at(30));
+ p.drawLine(p2.at(32), p2.at(71));
+}
+
+void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, const QPointF offset,
+ float offset_scalar, bool pressed) {
+ const float radius1 = 24.0f;
+ const float radius2 = 17.0f;
+
+ const QPointF offset_center = center + offset * offset_scalar;
+
+ const auto amplitude = static_cast<float>(
+ 1.0 - std::sqrt((offset.x() * offset.x()) + (offset.y() * offset.y())) * 0.1f);
+
+ const float rotation =
+ ((offset.x() == 0) ? atan(1) * 2 : atan(offset.y() / offset.x())) * (180 / (atan(1) * 4));
+
+ p.save();
+ p.translate(offset_center);
+ p.rotate(rotation);
+
+ // Outer circle
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ p.drawEllipse(QPointF(0, 0), radius1 * amplitude, radius1);
+
+ // Inner circle
+ p.setBrush(pressed ? colors.highlight2 : colors.button2);
+
+ const float inner_offset =
+ (radius1 - radius2) * 0.4f * ((offset.x() == 0 && offset.y() < 0) ? -1.0f : 1.0f);
+ const float offset_factor = (1.0f - amplitude) / 0.1f;
+
+ p.drawEllipse(QPointF((offset.x() < 0) ? -inner_offset : inner_offset, 0) * offset_factor,
+ radius2 * amplitude, radius2);
+
+ p.restore();
+}
+
+void PlayerControlPreview::DrawGCJoystick(QPainter& p, const QPointF center, bool pressed) {
+ // Outer circle
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ DrawCircle(p, center, 26.0f);
+
+ // Inner circle
+ p.setBrush(pressed ? colors.highlight2 : colors.button2);
+ DrawCircle(p, center, 19.0f);
+ p.setBrush(colors.transparent);
+ DrawCircle(p, center, 13.5f);
+ DrawCircle(p, center, 7.5f);
+}
+
+void PlayerControlPreview::DrawRawJoystick(QPainter& p, const QPointF center, const QPointF value,
+ const Input::AnalogProperties& properties) {
+ constexpr float size = 45.0f;
+ const float range = size * properties.range;
+ const float deadzone = size * properties.deadzone;
+
+ // Max range zone circle
+ p.setPen(colors.outline);
+ p.setBrush(colors.transparent);
+ QPen pen = p.pen();
+ pen.setStyle(Qt::DotLine);
+ p.setPen(pen);
+ DrawCircle(p, center, range);
+
+ // Deadzone circle
+ pen.setColor(colors.deadzone);
+ p.setPen(pen);
+ DrawCircle(p, center, deadzone);
+
+ // Dot pointer
+ p.setPen(colors.indicator);
+ p.setBrush(colors.indicator);
+ DrawCircle(p, center + (value * range), 2);
+}
+
+void PlayerControlPreview::DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width,
+ float height, Direction direction, float radius) {
+ p.setBrush(button_color);
+ if (pressed) {
+ switch (direction) {
+ case Direction::Left:
+ center.setX(center.x() - 1);
+ break;
+ case Direction::Right:
+ center.setX(center.x() + 1);
+ break;
+ case Direction::Down:
+ center.setY(center.y() + 1);
+ break;
+ case Direction::Up:
+ center.setY(center.y() - 1);
+ break;
+ case Direction::None:
+ break;
+ }
+ p.setBrush(colors.highlight);
+ }
+ QRectF rect = {center.x() - width, center.y() - height, width * 2.0f, height * 2.0f};
+ p.drawRoundedRect(rect, radius, radius);
+}
+void PlayerControlPreview::DrawMinusButton(QPainter& p, const QPointF center, bool pressed,
+ int button_size) {
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ DrawRectangle(p, center, button_size, button_size / 3.0f);
+}
+void PlayerControlPreview::DrawPlusButton(QPainter& p, const QPointF center, bool pressed,
+ int button_size) {
+ // Draw outer line
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ DrawRectangle(p, center, button_size, button_size / 3.0f);
+ DrawRectangle(p, center, button_size / 3.0f, button_size);
+
+ // Scale down size
+ button_size *= 0.88f;
+
+ // Draw inner color
+ p.setPen(colors.transparent);
+ DrawRectangle(p, center, button_size, button_size / 3.0f);
+ DrawRectangle(p, center, button_size / 3.0f, button_size);
+}
+
+void PlayerControlPreview::DrawGCButtonX(QPainter& p, const QPointF center, bool pressed) {
+ std::array<QPointF, gc_button_x.size() / 2> button_x;
+
+ for (std::size_t point = 0; point < gc_button_x.size() / 2; ++point) {
+ button_x[point] = center + QPointF(gc_button_x[point * 2], gc_button_x[point * 2 + 1]);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, button_x);
+}
+
+void PlayerControlPreview::DrawGCButtonY(QPainter& p, const QPointF center, bool pressed) {
+ std::array<QPointF, gc_button_y.size() / 2> button_x;
+
+ for (std::size_t point = 0; point < gc_button_y.size() / 2; ++point) {
+ button_x[point] = center + QPointF(gc_button_y[point * 2], gc_button_y[point * 2 + 1]);
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, button_x);
+}
+
+void PlayerControlPreview::DrawGCButtonZ(QPainter& p, const QPointF center, bool pressed) {
+ std::array<QPointF, gc_button_z.size() / 2> button_x;
+
+ for (std::size_t point = 0; point < gc_button_z.size() / 2; ++point) {
+ button_x[point] = center + QPointF(gc_button_z[point * 2],
+ gc_button_z[point * 2 + 1] + (pressed ? 1 : 0));
+ }
+
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button2);
+ DrawPolygon(p, button_x);
+}
+
+void PlayerControlPreview::DrawCircleButton(QPainter& p, const QPointF center, bool pressed,
+ float button_size) {
+ p.setBrush(button_color);
+ if (pressed) {
+ p.setBrush(colors.highlight);
+ }
+ p.drawEllipse(center, button_size, button_size);
+}
+
+void PlayerControlPreview::DrawArrowButtonOutline(QPainter& p, const QPointF center, float size) {
+ const std::size_t arrow_points = up_arrow_button.size() / 2;
+ std::array<QPointF, (arrow_points - 1) * 4> arrow_button_outline;
+
+ for (std::size_t point = 0; point < arrow_points - 1; ++point) {
+ const float up_arrow_x = up_arrow_button[point * 2 + 0];
+ const float up_arrow_y = up_arrow_button[point * 2 + 1];
+
+ arrow_button_outline[point] = center + QPointF(up_arrow_x * size, up_arrow_y * size);
+ arrow_button_outline[(arrow_points - 1) * 2 - point - 1] =
+ center + QPointF(up_arrow_y * size, up_arrow_x * size);
+ arrow_button_outline[(arrow_points - 1) * 2 + point] =
+ center + QPointF(-up_arrow_x * size, -up_arrow_y * size);
+ arrow_button_outline[(arrow_points - 1) * 4 - point - 1] =
+ center + QPointF(-up_arrow_y * size, -up_arrow_x * size);
+ }
+ // Draw arrow button outline
+ p.setPen(colors.outline);
+ p.setBrush(colors.transparent);
+ DrawPolygon(p, arrow_button_outline);
+}
+
+void PlayerControlPreview::DrawArrowButton(QPainter& p, const QPointF center,
+ const Direction direction, bool pressed, float size) {
+ std::array<QPointF, up_arrow_button.size() / 2> arrow_button;
+ QPoint offset;
+
+ for (std::size_t point = 0; point < up_arrow_button.size() / 2; ++point) {
+ const float up_arrow_x = up_arrow_button[point * 2 + 0];
+ const float up_arrow_y = up_arrow_button[point * 2 + 1];
+
+ switch (direction) {
+ case Direction::Up:
+ arrow_button[point] = center + QPointF(up_arrow_x * size, up_arrow_y * size);
+ break;
+ case Direction::Left:
+ arrow_button[point] = center + QPointF(up_arrow_y * size, up_arrow_x * size);
+ break;
+ case Direction::Right:
+ arrow_button[point] = center + QPointF(-up_arrow_y * size, up_arrow_x * size);
+ break;
+ case Direction::Down:
+ arrow_button[point] = center + QPointF(up_arrow_x * size, -up_arrow_y * size);
+ break;
+ case Direction::None:
+ break;
+ }
+ }
+
+ // Draw arrow button
+ p.setPen(pressed ? colors.highlight : colors.button);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, arrow_button);
+
+ switch (direction) {
+ case Direction::Up:
+ offset = QPoint(0, -20 * size);
+ break;
+ case Direction::Left:
+ offset = QPoint(-20 * size, 0);
+ break;
+ case Direction::Right:
+ offset = QPoint(20 * size, 0);
+ break;
+ case Direction::Down:
+ offset = QPoint(0, 20 * size);
+ break;
+ case Direction::None:
+ offset = QPoint(0, 0);
+ break;
+ }
+
+ // Draw arrow icon
+ p.setPen(colors.font2);
+ p.setBrush(colors.font2);
+ DrawArrow(p, center + offset, direction, size);
+}
+
+void PlayerControlPreview::DrawTriggerButton(QPainter& p, const QPointF center,
+ const Direction direction, bool pressed) {
+ std::array<QPointF, trigger_button.size() / 2> qtrigger_button;
+
+ for (std::size_t point = 0; point < trigger_button.size() / 2; ++point) {
+ const float trigger_button_x = trigger_button[point * 2 + 0];
+ const float trigger_button_y = trigger_button[point * 2 + 1];
+
+ switch (direction) {
+ case Direction::Left:
+ qtrigger_button[point] = center + QPointF(-trigger_button_x, trigger_button_y);
+ break;
+ case Direction::Right:
+ qtrigger_button[point] = center + QPointF(trigger_button_x, trigger_button_y);
+ break;
+ case Direction::Up:
+ case Direction::Down:
+ case Direction::None:
+ break;
+ }
+ }
+
+ // Draw arrow button
+ p.setPen(colors.outline);
+ p.setBrush(pressed ? colors.highlight : colors.button);
+ DrawPolygon(p, qtrigger_button);
+}
+
+void PlayerControlPreview::DrawSymbol(QPainter& p, const QPointF center, Symbol symbol,
+ float icon_size) {
+ std::array<QPointF, house.size() / 2> house_icon;
+ std::array<QPointF, symbol_a.size() / 2> a_icon;
+ std::array<QPointF, symbol_b.size() / 2> b_icon;
+ std::array<QPointF, symbol_x.size() / 2> x_icon;
+ std::array<QPointF, symbol_y.size() / 2> y_icon;
+ std::array<QPointF, symbol_l.size() / 2> l_icon;
+ std::array<QPointF, symbol_r.size() / 2> r_icon;
+ std::array<QPointF, symbol_c.size() / 2> c_icon;
+ std::array<QPointF, symbol_zl.size() / 2> zl_icon;
+ std::array<QPointF, symbol_sl.size() / 2> sl_icon;
+ std::array<QPointF, symbol_zr.size() / 2> zr_icon;
+ std::array<QPointF, symbol_sr.size() / 2> sr_icon;
+ switch (symbol) {
+ case Symbol::House:
+ for (std::size_t point = 0; point < house.size() / 2; ++point) {
+ house_icon[point] = center + QPointF(house[point * 2] * icon_size,
+ (house[point * 2 + 1] - 0.025f) * icon_size);
+ }
+ p.drawPolygon(house_icon.data(), static_cast<int>(house_icon.size()));
+ break;
+ case Symbol::A:
+ for (std::size_t point = 0; point < symbol_a.size() / 2; ++point) {
+ a_icon[point] = center + QPointF(symbol_a[point * 2] * icon_size,
+ symbol_a[point * 2 + 1] * icon_size);
+ }
+ p.drawPolygon(a_icon.data(), static_cast<int>(a_icon.size()));
+ break;
+ case Symbol::B:
+ for (std::size_t point = 0; point < symbol_b.size() / 2; ++point) {
+ b_icon[point] = center + QPointF(symbol_b[point * 2] * icon_size,
+ symbol_b[point * 2 + 1] * icon_size);
+ }
+ p.drawPolygon(b_icon.data(), static_cast<int>(b_icon.size()));
+ break;
+ case Symbol::X:
+ for (std::size_t point = 0; point < symbol_x.size() / 2; ++point) {
+ x_icon[point] = center + QPointF(symbol_x[point * 2] * icon_size,
+ symbol_x[point * 2 + 1] * icon_size);
+ }
+ p.drawPolygon(x_icon.data(), static_cast<int>(x_icon.size()));
+ break;
+ case Symbol::Y:
+ for (std::size_t point = 0; point < symbol_y.size() / 2; ++point) {
+ y_icon[point] = center + QPointF(symbol_y[point * 2] * icon_size,
+ (symbol_y[point * 2 + 1] - 1.0f) * icon_size);
+ }
+ p.drawPolygon(y_icon.data(), static_cast<int>(y_icon.size()));
+ break;
+ case Symbol::L:
+ for (std::size_t point = 0; point < symbol_l.size() / 2; ++point) {
+ l_icon[point] = center + QPointF(symbol_l[point * 2] * icon_size,
+ (symbol_l[point * 2 + 1] - 1.0f) * icon_size);
+ }
+ p.drawPolygon(l_icon.data(), static_cast<int>(l_icon.size()));
+ break;
+ case Symbol::R:
+ for (std::size_t point = 0; point < symbol_r.size() / 2; ++point) {
+ r_icon[point] = center + QPointF(symbol_r[point * 2] * icon_size,
+ (symbol_r[point * 2 + 1] - 1.0f) * icon_size);
+ }
+ p.drawPolygon(r_icon.data(), static_cast<int>(r_icon.size()));
+ break;
+ case Symbol::C:
+ for (std::size_t point = 0; point < symbol_c.size() / 2; ++point) {
+ c_icon[point] = center + QPointF(symbol_c[point * 2] * icon_size,
+ (symbol_c[point * 2 + 1] - 1.0f) * icon_size);
+ }
+ p.drawPolygon(c_icon.data(), static_cast<int>(c_icon.size()));
+ break;
+ case Symbol::ZL:
+ for (std::size_t point = 0; point < symbol_zl.size() / 2; ++point) {
+ zl_icon[point] = center + QPointF(symbol_zl[point * 2] * icon_size,
+ symbol_zl[point * 2 + 1] * icon_size);
+ }
+ p.drawPolygon(zl_icon.data(), static_cast<int>(zl_icon.size()));
+ break;
+ case Symbol::SL:
+ for (std::size_t point = 0; point < symbol_sl.size() / 2; ++point) {
+ sl_icon[point] = center + QPointF(symbol_sl[point * 2] * icon_size,
+ symbol_sl[point * 2 + 1] * icon_size);
+ }
+ p.drawPolygon(sl_icon.data(), static_cast<int>(sl_icon.size()));
+ break;
+ case Symbol::ZR:
+ for (std::size_t point = 0; point < symbol_zr.size() / 2; ++point) {
+ zr_icon[point] = center + QPointF(symbol_zr[point * 2] * icon_size,
+ symbol_zr[point * 2 + 1] * icon_size);
+ }
+ p.drawPolygon(zr_icon.data(), static_cast<int>(zr_icon.size()));
+ break;
+ case Symbol::SR:
+ for (std::size_t point = 0; point < symbol_sr.size() / 2; ++point) {
+ sr_icon[point] = center + QPointF(symbol_sr[point * 2] * icon_size,
+ symbol_sr[point * 2 + 1] * icon_size);
+ }
+ p.drawPolygon(sr_icon.data(), static_cast<int>(sr_icon.size()));
+ break;
+ }
+}
+
+void PlayerControlPreview::DrawArrow(QPainter& p, const QPointF center, const Direction direction,
+ float size) {
+
+ std::array<QPointF, up_arrow_symbol.size() / 2> arrow_symbol;
+
+ for (std::size_t point = 0; point < up_arrow_symbol.size() / 2; ++point) {
+ const float up_arrow_x = up_arrow_symbol[point * 2 + 0];
+ const float up_arrow_y = up_arrow_symbol[point * 2 + 1];
+
+ switch (direction) {
+ case Direction::Up:
+ arrow_symbol[point] = center + QPointF(up_arrow_x * size, up_arrow_y * size);
+ break;
+ case Direction::Left:
+ arrow_symbol[point] = center + QPointF(up_arrow_y * size, up_arrow_x * size);
+ break;
+ case Direction::Right:
+ arrow_symbol[point] = center + QPointF(-up_arrow_y * size, up_arrow_x * size);
+ break;
+ case Direction::Down:
+ arrow_symbol[point] = center + QPointF(up_arrow_x * size, -up_arrow_y * size);
+ break;
+ case Direction::None:
+ break;
+ }
+ }
+
+ DrawPolygon(p, arrow_symbol);
+}
+
+template <size_t N>
+void PlayerControlPreview::DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon) {
+ p.drawPolygon(polygon.data(), static_cast<int>(polygon.size()));
+}
+
+void PlayerControlPreview::DrawCircle(QPainter& p, const QPointF center, float size) {
+ p.drawEllipse(center, size, size);
+}
+
+void PlayerControlPreview::DrawRectangle(QPainter& p, const QPointF center, float width,
+ float height) {
+ const QRectF rect = QRectF(center.x() - (width / 2), center.y() - (height / 2), width, height);
+ p.drawRect(rect);
+}
+void PlayerControlPreview::DrawRoundRectangle(QPainter& p, const QPointF center, float width,
+ float height, float round) {
+ const QRectF rect = QRectF(center.x() - (width / 2), center.y() - (height / 2), width, height);
+ p.drawRoundedRect(rect, round, round);
+}
+
+void PlayerControlPreview::DrawText(QPainter& p, const QPointF center, float text_size,
+ const QString& text) {
+ SetTextFont(p, text_size);
+ const QFontMetrics fm(p.font());
+ const QPointF offset = {fm.horizontalAdvance(text) / 2.0f, -text_size / 2.0f};
+ p.drawText(center - offset, text);
+}
+
+void PlayerControlPreview::SetTextFont(QPainter& p, float text_size, const QString& font_family) {
+ QFont font = p.font();
+ font.setPointSizeF(text_size);
+ font.setFamily(font_family);
+ p.setFont(font);
+}
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h
new file mode 100644
index 000000000..91c3343f1
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_player_widget.h
@@ -0,0 +1,192 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <QFrame>
+#include <QPointer>
+#include "core/frontend/input.h"
+#include "core/settings.h"
+
+class QLabel;
+
+using AnalogParam = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
+using ButtonParam = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
+
+// Widget for representing controller animations
+class PlayerControlPreview : public QFrame {
+ Q_OBJECT
+
+public:
+ explicit PlayerControlPreview(QWidget* parent);
+ ~PlayerControlPreview() override;
+
+ void SetPlayerInput(std::size_t index, const ButtonParam& buttons_param,
+ const AnalogParam& analogs_param);
+ void SetPlayerInputRaw(std::size_t index, const Settings::ButtonsRaw& buttons_,
+ Settings::AnalogsRaw analogs_);
+ void SetConnectedStatus(bool checked);
+ void SetControllerType(Settings::ControllerType type);
+ void BeginMappingButton(std::size_t button_id);
+ void BeginMappingAnalog(std::size_t button_id);
+ void EndMapping();
+ void UpdateInput();
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+
+private:
+ enum class Direction : std::size_t {
+ None,
+ Up,
+ Right,
+ Down,
+ Left,
+ };
+
+ enum class Symbol {
+ House,
+ A,
+ B,
+ X,
+ Y,
+ L,
+ R,
+ C,
+ SL,
+ ZL,
+ ZR,
+ SR,
+ };
+
+ struct AxisValue {
+ QPointF value{};
+ QPointF raw_value{};
+ Input::AnalogProperties properties{};
+ int size{};
+ QPoint offset{};
+ bool active{};
+ };
+
+ struct LedPattern {
+ bool position1;
+ bool position2;
+ bool position3;
+ bool position4;
+ };
+
+ struct ColorMapping {
+ QColor outline{};
+ QColor primary{};
+ QColor left{};
+ QColor right{};
+ QColor button{};
+ QColor button2{};
+ QColor font{};
+ QColor font2{};
+ QColor highlight{};
+ QColor highlight2{};
+ QColor transparent{};
+ QColor indicator{};
+ QColor led_on{};
+ QColor led_off{};
+ QColor slider{};
+ QColor slider_button{};
+ QColor slider_arrow{};
+ QColor deadzone{};
+ };
+
+ static LedPattern GetColorPattern(std::size_t index, bool player_on);
+ void UpdateColors();
+
+ // Draw controller functions
+ void DrawHandheldController(QPainter& p, QPointF center);
+ void DrawDualController(QPainter& p, QPointF center);
+ void DrawLeftController(QPainter& p, QPointF center);
+ void DrawRightController(QPainter& p, QPointF center);
+ void DrawProController(QPainter& p, QPointF center);
+ void DrawGCController(QPainter& p, QPointF center);
+
+ // Draw body functions
+ void DrawHandheldBody(QPainter& p, QPointF center);
+ void DrawDualBody(QPainter& p, QPointF center);
+ void DrawLeftBody(QPainter& p, QPointF center);
+ void DrawRightBody(QPainter& p, QPointF center);
+ void DrawProBody(QPainter& p, QPointF center);
+ void DrawGCBody(QPainter& p, QPointF center);
+
+ // Draw triggers functions
+ void DrawProTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
+ void DrawGCTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
+ void DrawHandheldTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
+ void DrawDualTriggers(QPainter& p, QPointF center, bool left_pressed, bool right_pressed);
+ void DrawDualTriggersTopView(QPainter& p, QPointF center, bool left_pressed,
+ bool right_pressed);
+ void DrawDualZTriggersTopView(QPainter& p, QPointF center, bool left_pressed,
+ bool right_pressed);
+ void DrawLeftTriggers(QPainter& p, QPointF center, bool left_pressed);
+ void DrawLeftZTriggers(QPainter& p, QPointF center, bool left_pressed);
+ void DrawLeftTriggersTopView(QPainter& p, QPointF center, bool left_pressed);
+ void DrawLeftZTriggersTopView(QPainter& p, QPointF center, bool left_pressed);
+ void DrawRightTriggers(QPainter& p, QPointF center, bool right_pressed);
+ void DrawRightZTriggers(QPainter& p, QPointF center, bool right_pressed);
+ void DrawRightTriggersTopView(QPainter& p, QPointF center, bool right_pressed);
+ void DrawRightZTriggersTopView(QPainter& p, QPointF center, bool right_pressed);
+
+ // Draw joystick functions
+ void DrawJoystick(QPainter& p, QPointF center, float size, bool pressed);
+ void DrawJoystickSideview(QPainter& p, QPointF center, float angle, float size, bool pressed);
+ void DrawRawJoystick(QPainter& p, QPointF center, QPointF value,
+ const Input::AnalogProperties& properties);
+ void DrawProJoystick(QPainter& p, QPointF center, QPointF offset, float scalar, bool pressed);
+ void DrawGCJoystick(QPainter& p, QPointF center, bool pressed);
+
+ // Draw button functions
+ void DrawCircleButton(QPainter& p, QPointF center, bool pressed, float button_size);
+ void DrawRoundButton(QPainter& p, QPointF center, bool pressed, float width, float height,
+ Direction direction = Direction::None, float radius = 2);
+ void DrawMinusButton(QPainter& p, QPointF center, bool pressed, int button_size);
+ void DrawPlusButton(QPainter& p, QPointF center, bool pressed, int button_size);
+ void DrawGCButtonX(QPainter& p, QPointF center, bool pressed);
+ void DrawGCButtonY(QPainter& p, QPointF center, bool pressed);
+ void DrawGCButtonZ(QPainter& p, QPointF center, bool pressed);
+ void DrawArrowButtonOutline(QPainter& p, const QPointF center, float size = 1.0f);
+ void DrawArrowButton(QPainter& p, QPointF center, Direction direction, bool pressed,
+ float size = 1.0f);
+ void DrawTriggerButton(QPainter& p, QPointF center, Direction direction, bool pressed);
+
+ // Draw icon functions
+ void DrawSymbol(QPainter& p, QPointF center, Symbol symbol, float icon_size);
+ void DrawArrow(QPainter& p, QPointF center, Direction direction, float size);
+
+ // Draw primitive types
+ template <size_t N>
+ void DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon);
+ void DrawCircle(QPainter& p, QPointF center, float size);
+ void DrawRectangle(QPainter& p, QPointF center, float width, float height);
+ void DrawRoundRectangle(QPainter& p, QPointF center, float width, float height, float round);
+ void DrawText(QPainter& p, QPointF center, float text_size, const QString& text);
+ void SetTextFont(QPainter& p, float text_size,
+ const QString& font_family = QStringLiteral("sans-serif"));
+
+ using ButtonArray =
+ std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::BUTTON_NS_END>;
+ using StickArray =
+ std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>;
+
+ bool mapping_active{};
+ int blink_counter{};
+ QColor button_color{};
+ ColorMapping colors{};
+ std::array<QColor, 4> led_color{};
+ ButtonArray buttons{};
+ StickArray sticks{};
+ std::size_t player_index{};
+ std::size_t button_mapping_index{Settings::NativeButton::BUTTON_NS_END};
+ std::size_t analog_mapping_index{Settings::NativeAnalog::NUM_STICKS_HID};
+ std::array<AxisValue, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{};
+ std::array<bool, Settings::NativeButton::NumButtons> button_values{};
+ Settings::ControllerType controller_type{Settings::ControllerType::ProController};
+};
diff --git a/src/yuzu/configuration/configure_input_profile_dialog.cpp b/src/yuzu/configuration/configure_input_profile_dialog.cpp
new file mode 100644
index 000000000..1f5cfa75b
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_profile_dialog.cpp
@@ -0,0 +1,37 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "ui_configure_input_profile_dialog.h"
+#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_input_profile_dialog.h"
+
+ConfigureInputProfileDialog::ConfigureInputProfileDialog(
+ QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputProfileDialog>()),
+ profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, false)) {
+ ui->setupUi(this);
+
+ ui->controllerLayout->addWidget(profile_widget);
+
+ connect(ui->clear_all_button, &QPushButton::clicked, this,
+ [this] { profile_widget->ClearAll(); });
+ connect(ui->restore_defaults_button, &QPushButton::clicked, this,
+ [this] { profile_widget->RestoreDefaults(); });
+
+ RetranslateUI();
+}
+
+ConfigureInputProfileDialog::~ConfigureInputProfileDialog() = default;
+
+void ConfigureInputProfileDialog::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureInputProfileDialog::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_input_profile_dialog.h b/src/yuzu/configuration/configure_input_profile_dialog.h
new file mode 100644
index 000000000..e6386bdbb
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_profile_dialog.h
@@ -0,0 +1,40 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+
+class QPushButton;
+
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class ConfigureInputProfileDialog;
+}
+
+class ConfigureInputProfileDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureInputProfileDialog(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem,
+ InputProfiles* profiles);
+ ~ConfigureInputProfileDialog() override;
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ std::unique_ptr<Ui::ConfigureInputProfileDialog> ui;
+
+ ConfigureInputPlayer* profile_widget;
+};
diff --git a/src/yuzu/configuration/configure_input_profile_dialog.ui b/src/yuzu/configuration/configure_input_profile_dialog.ui
new file mode 100644
index 000000000..726cf6905
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_profile_dialog.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureInputProfileDialog</class>
+ <widget class="QDialog" name="ConfigureInputProfileDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>70</width>
+ <height>540</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Create Input Profile</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="controllerLayout"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="clear_all_button">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="restore_defaults_button">
+ <property name="text">
+ <string>Defaults</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureInputProfileDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
new file mode 100644
index 000000000..1f2b792e4
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -0,0 +1,337 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <sstream>
+
+#include <QCloseEvent>
+#include <QLabel>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QRegularExpression>
+#include <QStringListModel>
+#include <QVBoxLayout>
+
+#include "common/logging/log.h"
+#include "core/settings.h"
+#include "input_common/main.h"
+#include "input_common/udp/client.h"
+#include "input_common/udp/udp.h"
+#include "ui_configure_motion_touch.h"
+#include "yuzu/configuration/configure_motion_touch.h"
+#include "yuzu/configuration/configure_touch_from_button.h"
+
+CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
+ const std::string& host, u16 port,
+ u8 pad_index, u16 client_id)
+ : QDialog(parent) {
+ layout = new QVBoxLayout;
+ status_label = new QLabel(tr("Communicating with the server..."));
+ cancel_button = new QPushButton(tr("Cancel"));
+ connect(cancel_button, &QPushButton::clicked, this, [this] {
+ if (!completed) {
+ job->Stop();
+ }
+ accept();
+ });
+ layout->addWidget(status_label);
+ layout->addWidget(cancel_button);
+ setLayout(layout);
+
+ using namespace InputCommon::CemuhookUDP;
+ job = std::make_unique<CalibrationConfigurationJob>(
+ host, port, pad_index, client_id,
+ [this](CalibrationConfigurationJob::Status status) {
+ QString text;
+ switch (status) {
+ case CalibrationConfigurationJob::Status::Ready:
+ text = tr("Touch the top left corner <br>of your touchpad.");
+ break;
+ case CalibrationConfigurationJob::Status::Stage1Completed:
+ text = tr("Now touch the bottom right corner <br>of your touchpad.");
+ break;
+ case CalibrationConfigurationJob::Status::Completed:
+ text = tr("Configuration completed!");
+ break;
+ default:
+ break;
+ }
+ QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
+ if (status == CalibrationConfigurationJob::Status::Completed) {
+ QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
+ }
+ },
+ [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
+ completed = true;
+ min_x = min_x_;
+ min_y = min_y_;
+ max_x = max_x_;
+ max_y = max_y_;
+ });
+}
+
+CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
+
+void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) {
+ status_label->setText(text);
+}
+
+void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) {
+ cancel_button->setText(text);
+}
+
+ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent,
+ InputCommon::InputSubsystem* input_subsystem_)
+ : QDialog(parent), input_subsystem{input_subsystem_},
+ ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
+ ui->setupUi(this);
+
+ ui->udp_learn_more->setOpenExternalLinks(true);
+ ui->udp_learn_more->setText(
+ tr("<a "
+ "href='https://yuzu-emu.org/wiki/"
+ "using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
+ "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
+
+ SetConfiguration();
+ UpdateUiDisplay();
+ ConnectEvents();
+}
+
+ConfigureMotionTouch::~ConfigureMotionTouch() = default;
+
+void ConfigureMotionTouch::SetConfiguration() {
+ const Common::ParamPackage motion_param(Settings::values.motion_device);
+ const Common::ParamPackage touch_param(Settings::values.touch_device);
+
+ ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button);
+ touch_from_button_maps = Settings::values.touch_from_button_maps;
+ for (const auto& touch_map : touch_from_button_maps) {
+ ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
+ }
+ ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index);
+ ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
+
+ min_x = touch_param.Get("min_x", 100);
+ min_y = touch_param.Get("min_y", 50);
+ max_x = touch_param.Get("max_x", 1800);
+ max_y = touch_param.Get("max_y", 850);
+
+ ui->udp_server->setText(QString::fromStdString("127.0.0.1"));
+ ui->udp_port->setText(QString::number(26760));
+
+ udp_server_list_model = new QStringListModel(this);
+ udp_server_list_model->setStringList({});
+ ui->udp_server_list->setModel(udp_server_list_model);
+
+ std::stringstream ss(Settings::values.udp_input_servers);
+ std::string token;
+
+ while (std::getline(ss, token, ',')) {
+ const int row = udp_server_list_model->rowCount();
+ udp_server_list_model->insertRows(row, 1);
+ const QModelIndex index = udp_server_list_model->index(row);
+ udp_server_list_model->setData(index, QString::fromStdString(token));
+ }
+}
+
+void ConfigureMotionTouch::UpdateUiDisplay() {
+ const QString cemuhook_udp = QStringLiteral("cemuhookudp");
+
+ ui->motion_sensitivity_label->setVisible(true);
+ ui->motion_sensitivity->setVisible(true);
+
+ ui->touch_calibration->setVisible(true);
+ ui->touch_calibration_config->setVisible(true);
+ ui->touch_calibration_label->setVisible(true);
+ ui->touch_calibration->setText(
+ QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y));
+
+ ui->udp_config_group_box->setVisible(true);
+}
+
+void ConfigureMotionTouch::ConnectEvents() {
+ connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
+ connect(ui->udp_add, &QPushButton::clicked, this, &ConfigureMotionTouch::OnUDPAddServer);
+ connect(ui->udp_remove, &QPushButton::clicked, this, &ConfigureMotionTouch::OnUDPDeleteServer);
+ connect(ui->touch_calibration_config, &QPushButton::clicked, this,
+ &ConfigureMotionTouch::OnConfigureTouchCalibration);
+ connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
+ &ConfigureMotionTouch::OnConfigureTouchFromButton);
+ connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
+ if (CanCloseDialog()) {
+ reject();
+ }
+ });
+}
+
+void ConfigureMotionTouch::OnUDPAddServer() {
+ // Validator for IP address
+ const QRegularExpression re(QStringLiteral(
+ R"re(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)re"));
+ bool ok;
+ const QString port_text = ui->udp_port->text();
+ const QString server_text = ui->udp_server->text();
+ const QString server_string = tr("%1:%2").arg(server_text, port_text);
+ const int port_number = port_text.toInt(&ok, 10);
+ const int row = udp_server_list_model->rowCount();
+
+ if (!ok) {
+ QMessageBox::warning(this, tr("yuzu"), tr("Port number has invalid characters"));
+ return;
+ }
+ if (port_number < 0 || port_number > 65353) {
+ QMessageBox::warning(this, tr("yuzu"), tr("Port has to be in range 0 and 65353"));
+ return;
+ }
+ if (!re.match(server_text).hasMatch()) {
+ QMessageBox::warning(this, tr("yuzu"), tr("IP address is not valid"));
+ return;
+ }
+ // Search for duplicates
+ for (const auto& item : udp_server_list_model->stringList()) {
+ if (item == server_string) {
+ QMessageBox::warning(this, tr("yuzu"), tr("This UDP server already exists"));
+ return;
+ }
+ }
+ // Limit server count to 8
+ if (row == 8) {
+ QMessageBox::warning(this, tr("yuzu"), tr("Unable to add more than 8 servers"));
+ return;
+ }
+
+ udp_server_list_model->insertRows(row, 1);
+ QModelIndex index = udp_server_list_model->index(row);
+ udp_server_list_model->setData(index, server_string);
+ ui->udp_server_list->setCurrentIndex(index);
+}
+
+void ConfigureMotionTouch::OnUDPDeleteServer() {
+ udp_server_list_model->removeRows(ui->udp_server_list->currentIndex().row(), 1);
+}
+
+void ConfigureMotionTouch::OnCemuhookUDPTest() {
+ ui->udp_test->setEnabled(false);
+ ui->udp_test->setText(tr("Testing"));
+ udp_test_in_progress = true;
+ InputCommon::CemuhookUDP::TestCommunication(
+ ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()), 0,
+ 24872,
+ [this] {
+ LOG_INFO(Frontend, "UDP input test success");
+ QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
+ },
+ [this] {
+ LOG_ERROR(Frontend, "UDP input test failed");
+ QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
+ });
+}
+
+void ConfigureMotionTouch::OnConfigureTouchCalibration() {
+ ui->touch_calibration_config->setEnabled(false);
+ ui->touch_calibration_config->setText(tr("Configuring"));
+ CalibrationConfigurationDialog dialog(this, ui->udp_server->text().toStdString(),
+ static_cast<u16>(ui->udp_port->text().toUInt()), 0,
+ 24872);
+ dialog.exec();
+ if (dialog.completed) {
+ min_x = dialog.min_x;
+ min_y = dialog.min_y;
+ max_x = dialog.max_x;
+ max_y = dialog.max_y;
+ LOG_INFO(Frontend,
+ "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
+ min_x, min_y, max_x, max_y);
+ UpdateUiDisplay();
+ } else {
+ LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
+ }
+ ui->touch_calibration_config->setEnabled(true);
+ ui->touch_calibration_config->setText(tr("Configure"));
+}
+
+void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
+ if (CanCloseDialog()) {
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+
+void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
+ udp_test_in_progress = false;
+ if (result) {
+ QMessageBox::information(this, tr("Test Successful"),
+ tr("Successfully received data from the server."));
+ } else {
+ QMessageBox::warning(this, tr("Test Failed"),
+ tr("Could not receive valid data from the server.<br>Please verify "
+ "that the server is set up correctly and "
+ "the address and port are correct."));
+ }
+ ui->udp_test->setEnabled(true);
+ ui->udp_test->setText(tr("Test"));
+}
+
+void ConfigureMotionTouch::OnConfigureTouchFromButton() {
+ ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem,
+ ui->touch_from_button_map->currentIndex()};
+ if (dialog.exec() != QDialog::Accepted) {
+ return;
+ }
+ touch_from_button_maps = dialog.GetMaps();
+
+ while (ui->touch_from_button_map->count() > 0) {
+ ui->touch_from_button_map->removeItem(0);
+ }
+ for (const auto& touch_map : touch_from_button_maps) {
+ ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
+ }
+ ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex());
+}
+
+bool ConfigureMotionTouch::CanCloseDialog() {
+ if (udp_test_in_progress) {
+ QMessageBox::warning(this, tr("yuzu"),
+ tr("UDP Test or calibration configuration is in progress.<br>Please "
+ "wait for them to finish."));
+ return false;
+ }
+ return true;
+}
+
+void ConfigureMotionTouch::ApplyConfiguration() {
+ if (!CanCloseDialog()) {
+ return;
+ }
+
+ Common::ParamPackage touch_param{};
+ touch_param.Set("min_x", min_x);
+ touch_param.Set("min_y", min_y);
+ touch_param.Set("max_x", max_x);
+ touch_param.Set("max_y", max_y);
+
+ Settings::values.touch_device = touch_param.Serialize();
+ Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked();
+ Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex();
+ Settings::values.touch_from_button_maps = touch_from_button_maps;
+ Settings::values.udp_input_servers = GetUDPServerString();
+ input_subsystem->ReloadInputDevices();
+
+ accept();
+}
+
+std::string ConfigureMotionTouch::GetUDPServerString() const {
+ QString input_servers;
+
+ for (const auto& item : udp_server_list_model->stringList()) {
+ input_servers += item;
+ input_servers += QLatin1Char{','};
+ }
+
+ // Remove last comma
+ input_servers.chop(1);
+ return input_servers.toStdString();
+}
diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h
new file mode 100644
index 000000000..15d61e8ba
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.h
@@ -0,0 +1,95 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+#include "common/param_package.h"
+
+class QLabel;
+class QPushButton;
+class QStringListModel;
+class QVBoxLayout;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace InputCommon::CemuhookUDP {
+class CalibrationConfigurationJob;
+}
+
+namespace Ui {
+class ConfigureMotionTouch;
+}
+
+/// A dialog for touchpad calibration configuration.
+class CalibrationConfigurationDialog : public QDialog {
+ Q_OBJECT
+public:
+ explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
+ u8 pad_index, u16 client_id);
+ ~CalibrationConfigurationDialog() override;
+
+private:
+ Q_INVOKABLE void UpdateLabelText(const QString& text);
+ Q_INVOKABLE void UpdateButtonText(const QString& text);
+
+ QVBoxLayout* layout;
+ QLabel* status_label;
+ QPushButton* cancel_button;
+ std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job;
+
+ // Configuration results
+ bool completed{};
+ u16 min_x{};
+ u16 min_y{};
+ u16 max_x{};
+ u16 max_y{};
+
+ friend class ConfigureMotionTouch;
+};
+
+class ConfigureMotionTouch : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureMotionTouch(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
+ ~ConfigureMotionTouch() override;
+
+public slots:
+ void ApplyConfiguration();
+
+private slots:
+ void OnUDPAddServer();
+ void OnUDPDeleteServer();
+ void OnCemuhookUDPTest();
+ void OnConfigureTouchCalibration();
+ void OnConfigureTouchFromButton();
+
+private:
+ void closeEvent(QCloseEvent* event) override;
+ Q_INVOKABLE void ShowUDPTestResult(bool result);
+ void SetConfiguration();
+ void UpdateUiDisplay();
+ void ConnectEvents();
+ bool CanCloseDialog();
+ std::string GetUDPServerString() const;
+
+ InputCommon::InputSubsystem* input_subsystem;
+
+ std::unique_ptr<Ui::ConfigureMotionTouch> ui;
+ QStringListModel* udp_server_list_model;
+
+ // Coordinate system of the CemuhookUDP touch provider
+ int min_x{};
+ int min_y{};
+ int max_x{};
+ int max_y{};
+
+ bool udp_test_in_progress{};
+
+ std::vector<Settings::TouchFromButtonMap> touch_from_button_maps;
+};
diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui
new file mode 100644
index 000000000..1e35ea946
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.ui
@@ -0,0 +1,370 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureMotionTouch</class>
+ <widget class="QDialog" name="ConfigureMotionTouch">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>482</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Motion / Touch</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox" name="motion_group_box">
+ <property name="title">
+ <string>Mouse Motion</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="motion_sensitivity_label">
+ <property name="text">
+ <string>Sensitivity:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="motion_sensitivity">
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="decimals">
+ <number>4</number>
+ </property>
+ <property name="minimum">
+ <double>0.010000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.001000000000000</double>
+ </property>
+ <property name="value">
+ <double>0.010000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="touch_group_box">
+ <property name="title">
+ <string>Touch</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel" name="touch_calibration_label">
+ <property name="text">
+ <string>UDP Calibration:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="touch_calibration">
+ <property name="text">
+ <string>(100, 50) - (1800, 850)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="touch_calibration_config">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QCheckBox" name="touch_from_button_checkbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Use button mapping:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="touch_from_button_map"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="touch_from_button_config_btn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="udp_config_group_box">
+ <property name="title">
+ <string>CemuhookUDP Config</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QLabel" name="udp_help">
+ <property name="text">
+ <string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QListView" name="udp_server_list"/>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="udp_server_label">
+ <property name="text">
+ <string>Server:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="udp_server">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="udp_port_label">
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="udp_port">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="udp_learn_more">
+ <property name="text">
+ <string>Learn More</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="udp_test">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Test</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="udp_add">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Add Server</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="udp_remove">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Remove Server</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>167</width>
+ <height>55</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureMotionTouch</receiver>
+ <slot>ApplyConfiguration()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui
index 74552fdbd..5b99e1c37 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.ui
+++ b/src/yuzu/configuration/configure_mouse_advanced.ui
@@ -15,7 +15,7 @@
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
- min-width: 55px;
+ min-width: 60px;
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -42,13 +42,13 @@
<widget class="QPushButton" name="forward_button">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>16777215</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -82,7 +82,7 @@
<widget class="QPushButton" name="back_button">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
@@ -110,7 +110,7 @@
<widget class="QPushButton" name="left_button">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
@@ -138,13 +138,13 @@
<widget class="QPushButton" name="middle_button">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>16777215</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -204,13 +204,13 @@
<widget class="QPushButton" name="right_button">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>16777215</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -256,13 +256,13 @@
<widget class="QPushButton" name="buttonClearAll">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>16777215</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -275,13 +275,13 @@
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="minimumSize">
<size>
- <width>57</width>
+ <width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
- <width>16777215</width>
+ <width>68</width>
<height>16777215</height>
</size>
</property>
@@ -324,32 +324,12 @@
<signal>accepted()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>124</x>
- <y>266</y>
- </hint>
- <hint type="destinationlabel">
- <x>124</x>
- <y>143</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>124</x>
- <y>266</y>
- </hint>
- <hint type="destinationlabel">
- <x>124</x>
- <y>143</y>
- </hint>
- </hints>
</connection>
</connections>
</ui>
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index 1e49f0787..f598513df 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -16,6 +16,7 @@
#include "common/common_paths.h"
#include "common/file_util.h"
+#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h"
@@ -29,9 +30,10 @@
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) {
- game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false);
+ game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id),
+ Config::ConfigType::PerGameConfig);
- Settings::configuring_global = false;
+ Settings::SetConfiguringGlobal(false);
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
@@ -55,7 +57,7 @@ void ConfigurePerGame::ApplyConfiguration() {
ui->graphicsAdvancedTab->ApplyConfiguration();
ui->audioTab->ApplyConfiguration();
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
Settings::LogSettings();
game_config->Save();
@@ -88,9 +90,11 @@ void ConfigurePerGame::LoadConfiguration() {
ui->display_title_id->setText(
QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
- FileSys::PatchManager pm{title_id};
+ auto& system = Core::System::GetInstance();
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
- const auto loader = Loader::GetLoader(file);
+ const auto loader = Loader::GetLoader(system, file);
if (control.first != nullptr) {
ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui
index d2057c4ab..25975b3b9 100644
--- a/src/yuzu/configuration/configure_per_game.ui
+++ b/src/yuzu/configuration/configure_per_game.ui
@@ -319,32 +319,12 @@
<signal>accepted()</signal>
<receiver>ConfigurePerGame</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>248</x>
- <y>254</y>
- </hint>
- <hint type="destinationlabel">
- <x>157</x>
- <y>274</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigurePerGame</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>316</x>
- <y>260</y>
- </hint>
- <hint type="destinationlabel">
- <x>286</x>
- <y>274</y>
- </hint>
- </hints>
</connection>
</connections>
</ui>
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 793fd8975..cdeeec01c 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -112,8 +112,10 @@ void ConfigurePerGameAddons::LoadConfiguration() {
return;
}
- FileSys::PatchManager pm{title_id};
- const auto loader = Loader::GetLoader(file);
+ auto& system = Core::System::GetInstance();
+ const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto loader = Loader::GetLoader(system, file);
FileSys::VirtualFile update_raw;
loader->ReadUpdateRaw(update_raw);
diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp
index 6334c4c50..51647a028 100644
--- a/src/yuzu/configuration/configure_profile_manager.cpp
+++ b/src/yuzu/configuration/configure_profile_manager.cpp
@@ -40,7 +40,7 @@ QString GetImagePath(Common::UUID uuid) {
}
QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) {
- Service::Account::ProfileBase profile;
+ Service::Account::ProfileBase profile{};
if (!manager.GetProfileBase(uuid, profile)) {
return {};
}
@@ -116,8 +116,8 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
scene = new QGraphicsScene;
ui->current_user_icon->setScene(scene);
- SetConfiguration();
RetranslateUI();
+ SetConfiguration();
}
ConfigureProfileManager::~ConfigureProfileManager() = default;
@@ -147,7 +147,7 @@ void ConfigureProfileManager::SetConfiguration() {
void ConfigureProfileManager::PopulateUserList() {
const auto& profiles = profile_manager->GetAllUsers();
for (const auto& user : profiles) {
- Service::Account::ProfileBase profile;
+ Service::Account::ProfileBase profile{};
if (!profile_manager->GetProfileBase(user, profile))
continue;
@@ -180,7 +180,7 @@ void ConfigureProfileManager::ApplyConfiguration() {
return;
}
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
}
void ConfigureProfileManager::SelectUser(const QModelIndex& index) {
@@ -212,7 +212,7 @@ void ConfigureProfileManager::RenameUser() {
const auto uuid = profile_manager->GetUser(user);
ASSERT(uuid);
- Service::Account::ProfileBase profile;
+ Service::Account::ProfileBase profile{};
if (!profile_manager->GetProfileBase(*uuid, profile))
return;
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_service.cpp
index 0de7a4f0b..b580cfff2 100644
--- a/src/yuzu/configuration/configure_service.cpp
+++ b/src/yuzu/configuration/configure_service.cpp
@@ -9,6 +9,7 @@
#include "ui_configure_service.h"
#include "yuzu/configuration/configure_service.h"
+#ifdef YUZU_ENABLE_BOXCAT
namespace {
QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
QString out;
@@ -32,6 +33,7 @@ QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
return out;
}
} // Anonymous namespace
+#endif
ConfigureService::ConfigureService(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) {
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 9ad43ed8f..6cf2032da 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -12,6 +12,7 @@
#include "common/assert.h"
#include "common/file_util.h"
#include "core/core.h"
+#include "core/hle/service/time/time.h"
#include "core/settings.h"
#include "ui_configure_system.h"
#include "yuzu/configuration/configuration_shared.h"
@@ -36,8 +37,8 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::
}
});
- ui->label_console_id->setVisible(Settings::configuring_global);
- ui->button_regenerate_console_id->setVisible(Settings::configuring_global);
+ ui->label_console_id->setVisible(Settings::IsConfiguringGlobal());
+ ui->button_regenerate_console_id->setVisible(Settings::IsConfiguringGlobal());
SetupPerGameUI();
@@ -77,7 +78,7 @@ void ConfigureSystem::SetConfiguration() {
Settings::values.rng_seed.UsingGlobal());
ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count()));
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue());
ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue());
@@ -104,11 +105,29 @@ void ConfigureSystem::SetConfiguration() {
void ConfigureSystem::ReadSystemSettings() {}
void ConfigureSystem::ApplyConfiguration() {
+ auto& system = Core::System::GetInstance();
+
+ // Allow setting custom RTC even if system is powered on,
+ // to allow in-game time to be fast forwarded
+ if (Settings::values.custom_rtc.UsingGlobal()) {
+ if (ui->custom_rtc_checkbox->isChecked()) {
+ Settings::values.custom_rtc.SetValue(
+ std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()));
+ if (system.IsPoweredOn()) {
+ const s64 posix_time{Settings::values.custom_rtc.GetValue()->count() +
+ Service::Time::TimeManager::GetExternalTimeZoneOffset()};
+ system.GetTimeManager().UpdateLocalSystemClockTime(posix_time);
+ }
+ } else {
+ Settings::values.custom_rtc.SetValue(std::nullopt);
+ }
+ }
+
if (!enabled) {
return;
}
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
// Guard if during game and set to game-specific value
if (Settings::values.language_index.UsingGlobal()) {
Settings::values.language_index.SetValue(ui->combo_language->currentIndex());
@@ -131,15 +150,6 @@ void ConfigureSystem::ApplyConfiguration() {
Settings::values.rng_seed.SetValue(std::nullopt);
}
}
-
- if (Settings::values.custom_rtc.UsingGlobal()) {
- if (ui->custom_rtc_checkbox->isChecked()) {
- Settings::values.custom_rtc.SetValue(
- std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()));
- } else {
- Settings::values.custom_rtc.SetValue(std::nullopt);
- }
- }
} else {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index,
ui->combo_language);
@@ -189,7 +199,7 @@ void ConfigureSystem::ApplyConfiguration() {
}
}
- Settings::Apply();
+ Settings::Apply(system);
}
void ConfigureSystem::RefreshConsoleID() {
@@ -210,7 +220,7 @@ void ConfigureSystem::RefreshConsoleID() {
}
void ConfigureSystem::SetupPerGameUI() {
- if (Settings::configuring_global) {
+ if (Settings::IsConfiguringGlobal()) {
ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal());
ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal());
ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal());
diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp
new file mode 100644
index 000000000..15557e4b8
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -0,0 +1,623 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QInputDialog>
+#include <QKeyEvent>
+#include <QMessageBox>
+#include <QMouseEvent>
+#include <QResizeEvent>
+#include <QStandardItemModel>
+#include <QTimer>
+#include "common/param_package.h"
+#include "core/frontend/framebuffer_layout.h"
+#include "core/settings.h"
+#include "input_common/main.h"
+#include "ui_configure_touch_from_button.h"
+#include "yuzu/configuration/configure_touch_from_button.h"
+#include "yuzu/configuration/configure_touch_widget.h"
+
+static QString GetKeyName(int key_code) {
+ switch (key_code) {
+ case Qt::Key_Shift:
+ return QObject::tr("Shift");
+ case Qt::Key_Control:
+ return QObject::tr("Ctrl");
+ case Qt::Key_Alt:
+ return QObject::tr("Alt");
+ case Qt::Key_Meta:
+ return QString{};
+ default:
+ return QKeySequence(key_code).toString();
+ }
+}
+
+static QString ButtonToText(const Common::ParamPackage& param) {
+ if (!param.Has("engine")) {
+ return QObject::tr("[not set]");
+ }
+
+ if (param.Get("engine", "") == "keyboard") {
+ return GetKeyName(param.Get("code", 0));
+ }
+
+ if (param.Get("engine", "") == "sdl") {
+ if (param.Has("hat")) {
+ const QString hat_str = QString::fromStdString(param.Get("hat", ""));
+ const QString direction_str = QString::fromStdString(param.Get("direction", ""));
+
+ return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
+ }
+
+ if (param.Has("axis")) {
+ const QString axis_str = QString::fromStdString(param.Get("axis", ""));
+ const QString direction_str = QString::fromStdString(param.Get("direction", ""));
+
+ return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
+ }
+
+ if (param.Has("button")) {
+ const QString button_str = QString::fromStdString(param.Get("button", ""));
+
+ return QObject::tr("Button %1").arg(button_str);
+ }
+
+ return {};
+ }
+
+ return QObject::tr("[unknown]");
+}
+
+ConfigureTouchFromButton::ConfigureTouchFromButton(
+ QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps,
+ InputCommon::InputSubsystem* input_subsystem_, const int default_index)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()),
+ touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index),
+ timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
+ ui->setupUi(this);
+ binding_list_model = new QStandardItemModel(0, 3, this);
+ binding_list_model->setHorizontalHeaderLabels(
+ {tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")});
+ ui->binding_list->setModel(binding_list_model);
+ ui->bottom_screen->SetCoordLabel(ui->coord_label);
+
+ SetConfiguration();
+ UpdateUiDisplay();
+ ConnectEvents();
+}
+
+ConfigureTouchFromButton::~ConfigureTouchFromButton() = default;
+
+void ConfigureTouchFromButton::showEvent(QShowEvent* ev) {
+ QWidget::showEvent(ev);
+
+ // width values are not valid in the constructor
+ const int w =
+ ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount();
+ if (w <= 0) {
+ return;
+ }
+ ui->binding_list->setColumnWidth(0, w);
+ ui->binding_list->setColumnWidth(1, w);
+ ui->binding_list->setColumnWidth(2, w);
+}
+
+void ConfigureTouchFromButton::SetConfiguration() {
+ for (const auto& touch_map : touch_maps) {
+ ui->mapping->addItem(QString::fromStdString(touch_map.name));
+ }
+
+ ui->mapping->setCurrentIndex(selected_index);
+}
+
+void ConfigureTouchFromButton::UpdateUiDisplay() {
+ ui->button_delete->setEnabled(touch_maps.size() > 1);
+ ui->button_delete_bind->setEnabled(false);
+
+ binding_list_model->removeRows(0, binding_list_model->rowCount());
+
+ for (const auto& button_str : touch_maps[selected_index].buttons) {
+ Common::ParamPackage package{button_str};
+ QStandardItem* button = new QStandardItem(ButtonToText(package));
+ button->setData(QString::fromStdString(button_str));
+ button->setEditable(false);
+ QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0)));
+ QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0)));
+ binding_list_model->appendRow({button, xcoord, ycoord});
+
+ const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
+ button->setData(dot, DataRoleDot);
+ }
+}
+
+void ConfigureTouchFromButton::ConnectEvents() {
+ connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
+ SaveCurrentMapping();
+ selected_index = index;
+ UpdateUiDisplay();
+ });
+ connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping);
+ connect(ui->button_delete, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::DeleteMapping);
+ connect(ui->button_rename, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::RenameMapping);
+ connect(ui->button_delete_bind, &QPushButton::clicked, this,
+ &ConfigureTouchFromButton::DeleteBinding);
+ connect(ui->binding_list, &QTreeView::doubleClicked, this,
+ &ConfigureTouchFromButton::EditBinding);
+ connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
+ &ConfigureTouchFromButton::OnBindingSelection);
+ connect(binding_list_model, &QStandardItemModel::itemChanged, this,
+ &ConfigureTouchFromButton::OnBindingChanged);
+ connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this,
+ &ConfigureTouchFromButton::OnBindingDeleted);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this,
+ &ConfigureTouchFromButton::NewBinding);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this,
+ &ConfigureTouchFromButton::SetActiveBinding);
+ connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this,
+ &ConfigureTouchFromButton::SetCoordinates);
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
+ &ConfigureTouchFromButton::ApplyConfiguration);
+
+ connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
+
+ connect(poll_timer.get(), &QTimer::timeout, [this]() {
+ Common::ParamPackage params;
+ for (auto& poller : device_pollers) {
+ params = poller->GetNextInput();
+ if (params.Has("engine")) {
+ SetPollingResult(params, false);
+ return;
+ }
+ }
+ });
+}
+
+void ConfigureTouchFromButton::SaveCurrentMapping() {
+ auto& map = touch_maps[selected_index];
+ map.buttons.clear();
+ for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) {
+ const auto bind_str = binding_list_model->index(i, 0)
+ .data(Qt::ItemDataRole::UserRole + 1)
+ .toString()
+ .toStdString();
+ if (bind_str.empty()) {
+ continue;
+ }
+ Common::ParamPackage params{bind_str};
+ if (!params.Has("engine")) {
+ continue;
+ }
+ params.Set("x", binding_list_model->index(i, 1).data().toInt());
+ params.Set("y", binding_list_model->index(i, 2).data().toInt());
+ map.buttons.emplace_back(params.Serialize());
+ }
+}
+
+void ConfigureTouchFromButton::NewMapping() {
+ const QString name =
+ QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile."));
+ if (name.isEmpty()) {
+ return;
+ }
+ touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}});
+ ui->mapping->addItem(name);
+ ui->mapping->setCurrentIndex(ui->mapping->count() - 1);
+}
+
+void ConfigureTouchFromButton::DeleteMapping() {
+ const auto answer = QMessageBox::question(
+ this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText()));
+ if (answer != QMessageBox::Yes) {
+ return;
+ }
+ const bool blocked = ui->mapping->blockSignals(true);
+ ui->mapping->removeItem(selected_index);
+ ui->mapping->blockSignals(blocked);
+ touch_maps.erase(touch_maps.begin() + selected_index);
+ selected_index = ui->mapping->currentIndex();
+ UpdateUiDisplay();
+}
+
+void ConfigureTouchFromButton::RenameMapping() {
+ const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:"));
+ if (new_name.isEmpty()) {
+ return;
+ }
+ ui->mapping->setItemText(selected_index, new_name);
+ touch_maps[selected_index].name = new_name.toStdString();
+}
+
+void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) {
+ binding_list_model->item(row_index, 0)->setText(tr("[press key]"));
+
+ input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
+ const bool cancel) {
+ auto* cell = binding_list_model->item(row_index, 0);
+ if (cancel) {
+ if (is_new) {
+ binding_list_model->removeRow(row_index);
+ } else {
+ cell->setText(
+ ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()}));
+ }
+ } else {
+ cell->setText(ButtonToText(params));
+ cell->setData(QString::fromStdString(params.Serialize()));
+ }
+ };
+
+ device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button);
+
+ for (auto& poller : device_pollers) {
+ poller->Start();
+ }
+
+ grabKeyboard();
+ grabMouse();
+ qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor));
+ timeout_timer->start(5000); // Cancel after 5 seconds
+ poll_timer->start(200); // Check for new inputs every 200ms
+}
+
+void ConfigureTouchFromButton::NewBinding(const QPoint& pos) {
+ auto* button = new QStandardItem();
+ button->setEditable(false);
+ auto* x_coord = new QStandardItem(QString::number(pos.x()));
+ auto* y_coord = new QStandardItem(QString::number(pos.y()));
+
+ const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y());
+ button->setData(dot_id, DataRoleDot);
+
+ binding_list_model->appendRow({button, x_coord, y_coord});
+ ui->binding_list->setFocus();
+ ui->binding_list->setCurrentIndex(button->index());
+
+ GetButtonInput(binding_list_model->rowCount() - 1, true);
+}
+
+void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) {
+ if (qi.row() >= 0 && qi.column() == 0) {
+ GetButtonInput(qi.row(), false);
+ }
+}
+
+void ConfigureTouchFromButton::DeleteBinding() {
+ const int row_index = ui->binding_list->currentIndex().row();
+ if (row_index < 0) {
+ return;
+ }
+ ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
+ binding_list_model->removeRow(row_index);
+}
+
+void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected,
+ const QItemSelection& deselected) {
+ ui->button_delete_bind->setEnabled(!selected.isEmpty());
+ if (!selected.isEmpty()) {
+ const auto dot_data = selected.indexes().first().data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->HighlightDot(dot_data.toInt());
+ }
+ }
+ if (!deselected.isEmpty()) {
+ const auto dot_data = deselected.indexes().first().data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->HighlightDot(dot_data.toInt(), false);
+ }
+ }
+}
+
+void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) {
+ if (item->column() == 0) {
+ return;
+ }
+
+ const bool blocked = binding_list_model->blockSignals(true);
+ item->setText(QString::number(
+ std::clamp(item->text().toInt(), 0,
+ static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width
+ : Layout::ScreenUndocked::Height) -
+ 1))));
+ binding_list_model->blockSignals(blocked);
+
+ const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->MoveDot(dot_data.toInt(),
+ binding_list_model->item(item->row(), 1)->text().toInt(),
+ binding_list_model->item(item->row(), 2)->text().toInt());
+ }
+}
+
+void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) {
+ for (int i = first; i <= last; ++i) {
+ const auto ix = binding_list_model->index(i, 0);
+ if (!ix.isValid()) {
+ return;
+ }
+ const auto dot_data = ix.data(DataRoleDot);
+ if (dot_data.isValid()) {
+ ui->bottom_screen->RemoveDot(dot_data.toInt());
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) {
+ for (int i = 0; i < binding_list_model->rowCount(); ++i) {
+ if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) {
+ ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0));
+ ui->binding_list->setFocus();
+ return;
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) {
+ for (int i = 0; i < binding_list_model->rowCount(); ++i) {
+ if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) {
+ binding_list_model->item(i, 1)->setText(QString::number(pos.x()));
+ binding_list_model->item(i, 2)->setText(QString::number(pos.y()));
+ return;
+ }
+ }
+}
+
+void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params,
+ const bool cancel) {
+ releaseKeyboard();
+ releaseMouse();
+ qApp->restoreOverrideCursor();
+ timeout_timer->stop();
+ poll_timer->stop();
+ for (auto& poller : device_pollers) {
+ poller->Stop();
+ }
+ if (input_setter) {
+ (*input_setter)(params, cancel);
+ input_setter.reset();
+ }
+}
+
+void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) {
+ if (!input_setter && event->key() == Qt::Key_Delete) {
+ DeleteBinding();
+ return;
+ }
+
+ if (!input_setter) {
+ return QDialog::keyPressEvent(event);
+ }
+
+ if (event->key() != Qt::Key_Escape) {
+ SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
+ false);
+ } else {
+ SetPollingResult({}, true);
+ }
+}
+
+void ConfigureTouchFromButton::ApplyConfiguration() {
+ SaveCurrentMapping();
+ accept();
+}
+
+int ConfigureTouchFromButton::GetSelectedIndex() const {
+ return selected_index;
+}
+
+std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const {
+ return touch_maps;
+}
+
+TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) {
+ setBackgroundRole(QPalette::ColorRole::Base);
+}
+
+TouchScreenPreview::~TouchScreenPreview() = default;
+
+void TouchScreenPreview::SetCoordLabel(QLabel* const label) {
+ coord_label = label;
+}
+
+int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
+ QFont dot_font{QStringLiteral("monospace")};
+ dot_font.setStyleHint(QFont::Monospace);
+ dot_font.setPointSize(20);
+
+ auto* dot = new QLabel(this);
+ dot->setAttribute(Qt::WA_TranslucentBackground);
+ dot->setFont(dot_font);
+ dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
+ dot->setAlignment(Qt::AlignmentFlag::AlignCenter);
+ dot->setProperty(PropId, ++max_dot_id);
+ dot->setProperty(PropX, device_x);
+ dot->setProperty(PropY, device_y);
+ dot->setCursor(Qt::CursorShape::PointingHandCursor);
+ dot->setMouseTracking(true);
+ dot->installEventFilter(this);
+ dot->show();
+ PositionDot(dot, device_x, device_y);
+ dots.emplace_back(max_dot_id, dot);
+ return max_dot_id;
+}
+
+void TouchScreenPreview::RemoveDot(const int id) {
+ const auto iter = std::find_if(dots.begin(), dots.end(),
+ [id](const auto& entry) { return entry.first == id; });
+ if (iter == dots.cend()) {
+ return;
+ }
+
+ iter->second->deleteLater();
+ dots.erase(iter);
+}
+
+void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
+ for (const auto& dot : dots) {
+ if (dot.first == id) {
+ // use color property from the stylesheet, or fall back to the default palette
+ if (dot_highlight_color.isValid()) {
+ dot.second->setStyleSheet(
+ active ? QStringLiteral("color: %1").arg(dot_highlight_color.name())
+ : QString{});
+ } else {
+ dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited
+ : QPalette::ColorRole::NoRole);
+ }
+ if (active) {
+ dot.second->raise();
+ }
+ return;
+ }
+ }
+}
+
+void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const {
+ const auto iter = std::find_if(dots.begin(), dots.end(),
+ [id](const auto& entry) { return entry.first == id; });
+ if (iter == dots.cend()) {
+ return;
+ }
+
+ iter->second->setProperty(PropX, device_x);
+ iter->second->setProperty(PropY, device_y);
+ PositionDot(iter->second, device_x, device_y);
+}
+
+void TouchScreenPreview::resizeEvent(QResizeEvent* event) {
+ if (ignore_resize) {
+ return;
+ }
+
+ const int target_width = std::min(width(), height() * 4 / 3);
+ const int target_height = std::min(height(), width() * 3 / 4);
+ if (target_width == width() && target_height == height()) {
+ return;
+ }
+ ignore_resize = true;
+ setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width,
+ target_height);
+ ignore_resize = false;
+
+ if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) {
+ for (const auto& dot : dots) {
+ PositionDot(dot.second);
+ }
+ }
+}
+
+void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
+ if (!coord_label) {
+ return;
+ }
+ const auto pos = MapToDeviceCoords(event->x(), event->y());
+ if (pos) {
+ coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
+ } else {
+ coord_label->clear();
+ }
+}
+
+void TouchScreenPreview::leaveEvent(QEvent* event) {
+ if (coord_label) {
+ coord_label->clear();
+ }
+}
+
+void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
+ if (event->button() != Qt::MouseButton::LeftButton) {
+ return;
+ }
+ const auto pos = MapToDeviceCoords(event->x(), event->y());
+ if (pos) {
+ emit DotAdded(*pos);
+ }
+}
+
+bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
+ switch (event->type()) {
+ case QEvent::Type::MouseButtonPress: {
+ const auto mouse_event = static_cast<QMouseEvent*>(event);
+ if (mouse_event->button() != Qt::MouseButton::LeftButton) {
+ break;
+ }
+ emit DotSelected(obj->property(PropId).toInt());
+
+ drag_state.dot = qobject_cast<QLabel*>(obj);
+ drag_state.start_pos = mouse_event->globalPos();
+ return true;
+ }
+ case QEvent::Type::MouseMove: {
+ if (!drag_state.dot) {
+ break;
+ }
+ const auto mouse_event = static_cast<QMouseEvent*>(event);
+ if (!drag_state.active) {
+ drag_state.active =
+ (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
+ QApplication::startDragDistance();
+ if (!drag_state.active) {
+ break;
+ }
+ }
+ auto current_pos = mapFromGlobal(mouse_event->globalPos());
+ current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
+ contentsMargins().left() + contentsRect().width() - 1));
+ current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
+ contentsMargins().top() + contentsRect().height() - 1));
+ const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y());
+ if (device_coord) {
+ drag_state.dot->setProperty(PropX, device_coord->x());
+ drag_state.dot->setProperty(PropY, device_coord->y());
+ PositionDot(drag_state.dot, device_coord->x(), device_coord->y());
+ emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
+ if (coord_label) {
+ coord_label->setText(
+ QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
+ }
+ }
+ return true;
+ }
+ case QEvent::Type::MouseButtonRelease: {
+ drag_state.dot.clear();
+ drag_state.active = false;
+ return true;
+ }
+ default:
+ break;
+ }
+ return obj->eventFilter(obj, event);
+}
+
+std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x,
+ const int screen_y) const {
+ const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) *
+ (Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1);
+ const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) *
+ (Layout::ScreenUndocked::Height - 1) /
+ (contentsRect().height() - 1);
+ if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f &&
+ t_y < Layout::ScreenUndocked::Height) {
+
+ return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)};
+ }
+ return std::nullopt;
+}
+
+void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x,
+ const int device_y) const {
+ const float device_coord_x =
+ static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt());
+ int x_coord = static_cast<int>(
+ device_coord_x * (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) +
+ contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f);
+
+ const float device_coord_y =
+ static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt());
+ const int y_coord = static_cast<int>(
+ device_coord_y * (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) +
+ contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f);
+
+ dot->move(x_coord, y_coord);
+}
diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h
new file mode 100644
index 000000000..d9513e3bc
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -0,0 +1,92 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <optional>
+#include <vector>
+#include <QDialog>
+
+class QItemSelection;
+class QModelIndex;
+class QStandardItemModel;
+class QStandardItem;
+class QTimer;
+
+namespace Common {
+class ParamPackage;
+}
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace InputCommon::Polling {
+class DevicePoller;
+}
+
+namespace Settings {
+struct TouchFromButtonMap;
+}
+
+namespace Ui {
+class ConfigureTouchFromButton;
+}
+
+class ConfigureTouchFromButton : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureTouchFromButton(QWidget* parent,
+ const std::vector<Settings::TouchFromButtonMap>& touch_maps,
+ InputCommon::InputSubsystem* input_subsystem_,
+ int default_index = 0);
+ ~ConfigureTouchFromButton() override;
+
+ int GetSelectedIndex() const;
+ std::vector<Settings::TouchFromButtonMap> GetMaps() const;
+
+public slots:
+ void ApplyConfiguration();
+ void NewBinding(const QPoint& pos);
+ void SetActiveBinding(int dot_id);
+ void SetCoordinates(int dot_id, const QPoint& pos);
+
+protected:
+ void showEvent(QShowEvent* ev) override;
+ void keyPressEvent(QKeyEvent* event) override;
+
+private slots:
+ void NewMapping();
+ void DeleteMapping();
+ void RenameMapping();
+ void EditBinding(const QModelIndex& qi);
+ void DeleteBinding();
+ void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected);
+ void OnBindingChanged(QStandardItem* item);
+ void OnBindingDeleted(const QModelIndex& parent, int first, int last);
+
+private:
+ void SetConfiguration();
+ void UpdateUiDisplay();
+ void ConnectEvents();
+ void GetButtonInput(int row_index, bool is_new);
+ void SetPollingResult(const Common::ParamPackage& params, bool cancel);
+ void SaveCurrentMapping();
+
+ std::unique_ptr<Ui::ConfigureTouchFromButton> ui;
+ std::vector<Settings::TouchFromButtonMap> touch_maps;
+ QStandardItemModel* binding_list_model;
+ InputCommon::InputSubsystem* input_subsystem;
+ int selected_index;
+
+ std::unique_ptr<QTimer> timeout_timer;
+ std::unique_ptr<QTimer> poll_timer;
+ std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
+ std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;
+
+ static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;
+};
diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui
new file mode 100644
index 000000000..757219d54
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.ui
@@ -0,0 +1,221 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureTouchFromButton</class>
+ <widget class="QDialog" name="ConfigureTouchFromButton">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Touchscreen Mappings</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Mapping:</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="mapping">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_new">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>New</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_delete">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Delete</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_rename">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="button_delete_bind">
+ <property name="text">
+ <string>Delete Point</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTreeView" name="binding_list">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="TouchScreenPreview" name="bottom_screen">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>120</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>320</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="cursor">
+ <cursorShape>CrossCursor</cursorShape>
+ </property>
+ <property name="mouseTracking">
+ <bool>true</bool>
+ </property>
+ <property name="autoFillBackground">
+ <bool>true</bool>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="coord_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>TouchScreenPreview</class>
+ <extends>QFrame</extends>
+ <header>yuzu/configuration/configure_touch_widget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureTouchFromButton</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h
new file mode 100644
index 000000000..347b46583
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_widget.h
@@ -0,0 +1,62 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <utility>
+#include <vector>
+#include <QFrame>
+#include <QPointer>
+
+class QLabel;
+
+// Widget for representing touchscreen coordinates
+class TouchScreenPreview : public QFrame {
+ Q_OBJECT
+ Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color)
+
+public:
+ explicit TouchScreenPreview(QWidget* parent);
+ ~TouchScreenPreview() override;
+
+ void SetCoordLabel(QLabel*);
+ int AddDot(int device_x, int device_y);
+ void RemoveDot(int id);
+ void HighlightDot(int id, bool active = true) const;
+ void MoveDot(int id, int device_x, int device_y) const;
+
+signals:
+ void DotAdded(const QPoint& pos);
+ void DotSelected(int dot_id);
+ void DotMoved(int dot_id, const QPoint& pos);
+
+protected:
+ void resizeEvent(QResizeEvent*) override;
+ void mouseMoveEvent(QMouseEvent*) override;
+ void leaveEvent(QEvent*) override;
+ void mousePressEvent(QMouseEvent*) override;
+ bool eventFilter(QObject*, QEvent*) override;
+
+private:
+ std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const;
+ void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const;
+
+ bool ignore_resize = false;
+ QPointer<QLabel> coord_label;
+
+ std::vector<std::pair<int, QLabel*>> dots;
+ int max_dot_id = 0;
+ QColor dot_highlight_color;
+ static constexpr char PropId[] = "dot_id";
+ static constexpr char PropX[] = "device_x";
+ static constexpr char PropY[] = "device_y";
+
+ struct DragState {
+ bool active = false;
+ QPointer<QLabel> dot;
+ QPoint start_pos;
+ };
+ DragState drag_state;
+};
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
index 7d7cc00b7..29c86c7bc 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
@@ -33,21 +33,18 @@ void ConfigureTouchscreenAdvanced::RetranslateUI() {
}
void ConfigureTouchscreenAdvanced::ApplyConfiguration() {
- Settings::values.touchscreen.finger = ui->finger_box->value();
Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value();
Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value();
Settings::values.touchscreen.rotation_angle = ui->angle_box->value();
}
void ConfigureTouchscreenAdvanced::LoadConfiguration() {
- ui->finger_box->setValue(Settings::values.touchscreen.finger);
ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x);
ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y);
ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle);
}
void ConfigureTouchscreenAdvanced::RestoreDefaults() {
- ui->finger_box->setValue(0);
ui->diameter_x_box->setValue(15);
ui->diameter_y_box->setValue(15);
ui->angle_box->setValue(0);
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui
index 1171c2dd1..88e7cf050 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.ui
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.ui
@@ -65,20 +65,13 @@
</property>
</spacer>
</item>
- <item row="2" column="1">
+ <item row="1" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Touch Diameter Y</string>
</property>
</widget>
</item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Finger</string>
- </property>
- </widget>
- </item>
<item row="0" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
@@ -92,37 +85,27 @@
</property>
</spacer>
</item>
- <item row="1" column="1">
+ <item row="0" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Touch Diameter X</string>
</property>
</widget>
</item>
- <item row="0" column="2">
- <widget class="QSpinBox" name="finger_box">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
+ <item row="2" column="1">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Rotational Angle</string>
</property>
</widget>
</item>
- <item row="1" column="2">
+ <item row="0" column="2">
<widget class="QSpinBox" name="diameter_x_box"/>
</item>
- <item row="2" column="2">
+ <item row="1" column="2">
<widget class="QSpinBox" name="diameter_y_box"/>
</item>
- <item row="3" column="2">
+ <item row="2" column="2">
<widget class="QSpinBox" name="angle_box"/>
</item>
</layout>
@@ -168,32 +151,12 @@
<signal>accepted()</signal>
<receiver>ConfigureTouchscreenAdvanced</receiver>
<slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>140</x>
- <y>318</y>
- </hint>
- <hint type="destinationlabel">
- <x>140</x>
- <y>169</y>
- </hint>
- </hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureTouchscreenAdvanced</receiver>
<slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>140</x>
- <y>318</y>
- </hint>
- <hint type="destinationlabel">
- <x>140</x>
- <y>169</y>
- </hint>
- </hints>
- </connection>
+ </connection>
</connections>
</ui>
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index dbe3f78c8..aed876008 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -9,6 +9,7 @@
#include <QDirIterator>
#include "common/common_types.h"
#include "common/file_util.h"
+#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_ui.h"
#include "yuzu/configuration/configure_ui.h"
@@ -84,7 +85,7 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked();
Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir,
ui->screenshot_path_edit->text().toStdString());
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
}
void ConfigureUi::RequestGameListUpdate() {
diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp
new file mode 100644
index 000000000..7dcb2c5b9
--- /dev/null
+++ b/src/yuzu/configuration/configure_vibration.cpp
@@ -0,0 +1,146 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <unordered_map>
+
+#include <fmt/format.h>
+
+#include "common/param_package.h"
+#include "core/settings.h"
+#include "ui_configure_vibration.h"
+#include "yuzu/configuration/configure_vibration.h"
+
+ConfigureVibration::ConfigureVibration(QWidget* parent)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureVibration>()) {
+ ui->setupUi(this);
+
+ vibration_groupboxes = {
+ ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3,
+ ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6,
+ ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8,
+ };
+
+ vibration_spinboxes = {
+ ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3,
+ ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6,
+ ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8,
+ };
+
+ const auto& players = Settings::values.players.GetValue();
+
+ for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+ vibration_groupboxes[i]->setChecked(players[i].vibration_enabled);
+ vibration_spinboxes[i]->setValue(players[i].vibration_strength);
+ }
+
+ ui->checkBoxAccurateVibration->setChecked(
+ Settings::values.enable_accurate_vibrations.GetValue());
+
+ if (!Settings::IsConfiguringGlobal()) {
+ ui->checkBoxAccurateVibration->setDisabled(true);
+ }
+
+ RetranslateUI();
+}
+
+ConfigureVibration::~ConfigureVibration() = default;
+
+void ConfigureVibration::ApplyConfiguration() {
+ auto& players = Settings::values.players.GetValue();
+
+ for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+ players[i].vibration_enabled = vibration_groupboxes[i]->isChecked();
+ players[i].vibration_strength = vibration_spinboxes[i]->value();
+ }
+
+ Settings::values.enable_accurate_vibrations.SetValue(
+ ui->checkBoxAccurateVibration->isChecked());
+}
+
+void ConfigureVibration::SetVibrationDevices(std::size_t player_index) {
+ using namespace Settings::NativeButton;
+ static constexpr std::array<std::array<Settings::NativeButton::Values, 6>, 2> buttons{{
+ {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons
+ {A, B, X, Y, R, ZR}, // Right Buttons
+ }};
+
+ auto& player = Settings::values.players.GetValue()[player_index];
+
+ for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) {
+ std::unordered_map<std::string, int> params_count;
+
+ for (const auto button_index : buttons[device_idx]) {
+ const auto& player_button = player.buttons[button_index];
+
+ if (params_count.find(player_button) != params_count.end()) {
+ ++params_count[player_button];
+ continue;
+ }
+
+ params_count.insert_or_assign(player_button, 1);
+ }
+
+ const auto it = std::max_element(
+ params_count.begin(), params_count.end(),
+ [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
+
+ auto& vibration_param_str = player.vibrations[device_idx];
+ vibration_param_str.clear();
+
+ if (it->first.empty()) {
+ continue;
+ }
+
+ const auto param = Common::ParamPackage(it->first);
+
+ const auto engine = param.Get("engine", "");
+ const auto guid = param.Get("guid", "");
+ const auto port = param.Get("port", "");
+
+ if (engine.empty() || engine == "keyboard" || engine == "mouse") {
+ continue;
+ }
+
+ vibration_param_str += fmt::format("engine:{}", engine);
+
+ if (!port.empty()) {
+ vibration_param_str += fmt::format(",port:{}", port);
+ }
+ if (!guid.empty()) {
+ vibration_param_str += fmt::format(",guid:{}", guid);
+ }
+ }
+
+ if (player.vibrations[0] != player.vibrations[1]) {
+ return;
+ }
+
+ if (!player.vibrations[0].empty() &&
+ player.controller_type != Settings::ControllerType::RightJoycon) {
+ player.vibrations[1].clear();
+ } else if (!player.vibrations[1].empty() &&
+ player.controller_type == Settings::ControllerType::RightJoycon) {
+ player.vibrations[0].clear();
+ }
+}
+
+void ConfigureVibration::SetAllVibrationDevices() {
+ // Set vibration devices for all player indices including handheld
+ for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) {
+ SetVibrationDevices(player_idx);
+ }
+}
+
+void ConfigureVibration::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QDialog::changeEvent(event);
+}
+
+void ConfigureVibration::RetranslateUI() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h
new file mode 100644
index 000000000..07411a86f
--- /dev/null
+++ b/src/yuzu/configuration/configure_vibration.h
@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+
+class QGroupBox;
+class QSpinBox;
+
+namespace Ui {
+class ConfigureVibration;
+}
+
+class ConfigureVibration : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigureVibration(QWidget* parent);
+ ~ConfigureVibration() override;
+
+ void ApplyConfiguration();
+
+ static void SetVibrationDevices(std::size_t player_index);
+ static void SetAllVibrationDevices();
+
+private:
+ void changeEvent(QEvent* event) override;
+ void RetranslateUI();
+
+ std::unique_ptr<Ui::ConfigureVibration> ui;
+
+ static constexpr std::size_t NUM_PLAYERS = 8;
+
+ // Groupboxes encapsulating the vibration strength spinbox.
+ std::array<QGroupBox*, NUM_PLAYERS> vibration_groupboxes;
+
+ // Spinboxes representing the vibration strength percentage.
+ std::array<QSpinBox*, NUM_PLAYERS> vibration_spinboxes;
+};
diff --git a/src/yuzu/configuration/configure_vibration.ui b/src/yuzu/configuration/configure_vibration.ui
new file mode 100644
index 000000000..efdf317a9
--- /dev/null
+++ b/src/yuzu/configuration/configure_vibration.ui
@@ -0,0 +1,546 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureVibration</class>
+ <widget class="QDialog" name="ConfigureVibration">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>364</width>
+ <height>242</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Configure Vibration</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QGroupBox" name="vibrationStrengthGroup">
+ <property name="title">
+ <string>Vibration</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0">
+ <property name="leftMargin">
+ <number>9</number>
+ </property>
+ <property name="topMargin">
+ <number>9</number>
+ </property>
+ <property name="rightMargin">
+ <number>9</number>
+ </property>
+ <property name="bottomMargin">
+ <number>9</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="player14Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer1">
+ <property name="title">
+ <string>Player 1</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer1">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer2">
+ <property name="title">
+ <string>Player 2</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer2">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer3">
+ <property name="title">
+ <string>Player 3</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer3">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer4">
+ <property name="title">
+ <string>Player 4</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer4">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="player58Widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer7">
+ <property name="title">
+ <string>Player 5</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer7">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer8">
+ <property name="title">
+ <string>Player 6</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer8">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer5">
+ <property name="title">
+ <string>Player 7</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer5">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationGroupPlayer6">
+ <property name="title">
+ <string>Player 8</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="vibrationSpinPlayer6">
+ <property name="minimumSize">
+ <size>
+ <width>68</width>
+ <height>21</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>150</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="vibrationSettingsGroup">
+ <property name="title">
+ <string>Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="checkBoxAccurateVibration">
+ <property name="text">
+ <string>Enable Accurate Vibration</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="spacerVibration">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>167</width>
+ <height>55</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBoxVibration">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBoxVibration</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureVibration</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBoxVibration</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureVibration</receiver>
+ <slot>reject()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
new file mode 100644
index 000000000..e87aededb
--- /dev/null
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -0,0 +1,131 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/format.h>
+
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/input_profiles.h"
+
+namespace FS = Common::FS;
+
+namespace {
+
+bool ProfileExistsInFilesystem(std::string_view profile_name) {
+ return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini",
+ FS::GetUserPath(FS::UserPath::ConfigDir), profile_name));
+}
+
+bool IsINI(std::string_view filename) {
+ const std::size_t index = filename.rfind('.');
+
+ if (index == std::string::npos) {
+ return false;
+ }
+
+ return filename.substr(index) == ".ini";
+}
+
+std::string GetNameWithoutExtension(const std::string& filename) {
+ const std::size_t index = filename.rfind('.');
+
+ if (index == std::string::npos) {
+ return filename;
+ }
+
+ return filename.substr(0, index);
+}
+
+} // namespace
+
+InputProfiles::InputProfiles() {
+ const std::string input_profile_loc =
+ fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir));
+
+ FS::ForeachDirectoryEntry(
+ nullptr, input_profile_loc,
+ [this](u64* entries_out, const std::string& directory, const std::string& filename) {
+ if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) {
+ map_profiles.insert_or_assign(
+ GetNameWithoutExtension(filename),
+ std::make_unique<Config>(GetNameWithoutExtension(filename),
+ Config::ConfigType::InputProfile));
+ }
+ return true;
+ });
+}
+
+InputProfiles::~InputProfiles() = default;
+
+std::vector<std::string> InputProfiles::GetInputProfileNames() {
+ std::vector<std::string> profile_names;
+ profile_names.reserve(map_profiles.size());
+
+ for (const auto& [profile_name, config] : map_profiles) {
+ if (!ProfileExistsInFilesystem(profile_name)) {
+ DeleteProfile(profile_name);
+ continue;
+ }
+
+ profile_names.push_back(profile_name);
+ }
+
+ return profile_names;
+}
+
+bool InputProfiles::IsProfileNameValid(std::string_view profile_name) {
+ return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos;
+}
+
+bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) {
+ if (ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ map_profiles.insert_or_assign(
+ profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
+
+ return SaveProfile(profile_name, player_index);
+}
+
+bool InputProfiles::DeleteProfile(const std::string& profile_name) {
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ if (!ProfileExistsInFilesystem(profile_name) ||
+ FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) {
+ map_profiles.erase(profile_name);
+ }
+
+ return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name);
+}
+
+bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) {
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ if (!ProfileExistsInFilesystem(profile_name)) {
+ map_profiles.erase(profile_name);
+ return false;
+ }
+
+ map_profiles[profile_name]->ReadControlPlayerValue(player_index);
+ return true;
+}
+
+bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) {
+ if (!ProfileExistsInMap(profile_name)) {
+ return false;
+ }
+
+ map_profiles[profile_name]->SaveControlPlayerValue(player_index);
+ return true;
+}
+
+bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const {
+ return map_profiles.find(profile_name) != map_profiles.end();
+}
diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h
new file mode 100644
index 000000000..cb41fd9be
--- /dev/null
+++ b/src/yuzu/configuration/input_profiles.h
@@ -0,0 +1,32 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+class Config;
+
+class InputProfiles {
+
+public:
+ explicit InputProfiles();
+ virtual ~InputProfiles();
+
+ std::vector<std::string> GetInputProfileNames();
+
+ static bool IsProfileNameValid(std::string_view profile_name);
+
+ bool CreateProfile(const std::string& profile_name, std::size_t player_index);
+ bool DeleteProfile(const std::string& profile_name);
+ bool LoadProfile(const std::string& profile_name, std::size_t player_index);
+ bool SaveProfile(const std::string& profile_name, std::size_t player_index);
+
+private:
+ bool ProfileExistsInMap(const std::string& profile_name) const;
+
+ std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
+};
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp
new file mode 100644
index 000000000..2731d948d
--- /dev/null
+++ b/src/yuzu/debugger/controller.cpp
@@ -0,0 +1,66 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QAction>
+#include <QLayout>
+#include <QString>
+#include "core/settings.h"
+#include "yuzu/configuration/configure_input_player_widget.h"
+#include "yuzu/debugger/controller.h"
+
+ControllerDialog::ControllerDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) {
+ setObjectName(QStringLiteral("Controller"));
+ setWindowTitle(tr("Controller P1"));
+ resize(500, 350);
+ setMinimumSize(500, 350);
+ // Remove the "?" button from the titlebar and enable the maximize button
+ setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) |
+ Qt::WindowMaximizeButtonHint);
+
+ widget = new PlayerControlPreview(this);
+ refreshConfiguration();
+ QLayout* layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->addWidget(widget);
+ setLayout(layout);
+
+ // Configure focus so that widget is focusable and the dialog automatically forwards focus to
+ // it.
+ setFocusProxy(widget);
+ widget->setFocusPolicy(Qt::StrongFocus);
+ widget->setFocus();
+}
+
+void ControllerDialog::refreshConfiguration() {
+ const auto& players = Settings::values.players.GetValue();
+ constexpr std::size_t player = 0;
+ widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs);
+ widget->SetConnectedStatus(players[player].connected);
+ widget->SetControllerType(players[player].controller_type);
+}
+
+QAction* ControllerDialog::toggleViewAction() {
+ if (toggle_view_action == nullptr) {
+ toggle_view_action = new QAction(tr("&Controller P1"), this);
+ toggle_view_action->setCheckable(true);
+ toggle_view_action->setChecked(isVisible());
+ connect(toggle_view_action, &QAction::toggled, this, &ControllerDialog::setVisible);
+ }
+
+ return toggle_view_action;
+}
+
+void ControllerDialog::showEvent(QShowEvent* ev) {
+ if (toggle_view_action) {
+ toggle_view_action->setChecked(isVisible());
+ }
+ QWidget::showEvent(ev);
+}
+
+void ControllerDialog::hideEvent(QHideEvent* ev) {
+ if (toggle_view_action) {
+ toggle_view_action->setChecked(isVisible());
+ }
+ QWidget::hideEvent(ev);
+}
diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h
new file mode 100644
index 000000000..c54750070
--- /dev/null
+++ b/src/yuzu/debugger/controller.h
@@ -0,0 +1,31 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QWidget>
+
+class QAction;
+class QHideEvent;
+class QShowEvent;
+class PlayerControlPreview;
+
+class ControllerDialog : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ControllerDialog(QWidget* parent = nullptr);
+
+ /// Returns a QAction that can be used to toggle visibility of this dialog.
+ QAction* toggleViewAction();
+ void refreshConfiguration();
+
+protected:
+ void showEvent(QShowEvent* ev) override;
+ void hideEvent(QHideEvent* ev) override;
+
+private:
+ QAction* toggle_view_action = nullptr;
+ PlayerControlPreview* widget;
+};
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp
index 0e26f765b..efdc6aa50 100644
--- a/src/yuzu/debugger/profiler.cpp
+++ b/src/yuzu/debugger/profiler.cpp
@@ -48,7 +48,7 @@ private:
MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) {
setObjectName(QStringLiteral("MicroProfile"));
- setWindowTitle(tr("MicroProfile"));
+ setWindowTitle(tr("&MicroProfile"));
resize(1000, 600);
// Remove the "?" button from the titlebar and enable the maximize button
setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) |
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 3439cb333..3bca6277b 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -13,19 +13,19 @@
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/mutex.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/process.h"
-#include "core/hle/kernel/readable_event.h"
-#include "core/hle/kernel/scheduler.h"
-#include "core/hle/kernel/synchronization_object.h"
-#include "core/hle/kernel/thread.h"
+#include "core/hle/kernel/svc_common.h"
+#include "core/hle/kernel/svc_types.h"
#include "core/memory.h"
namespace {
constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{
{Qt::GlobalColor::darkGreen, Qt::GlobalColor::green},
- {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green},
{Qt::GlobalColor::darkBlue, Qt::GlobalColor::cyan},
{Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray},
{Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray},
@@ -91,9 +91,9 @@ std::size_t WaitTreeItem::Row() const {
std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() {
std::vector<std::unique_ptr<WaitTreeThread>> item_list;
std::size_t row = 0;
- auto add_threads = [&](const std::vector<std::shared_ptr<Kernel::Thread>>& threads) {
+ auto add_threads = [&](const std::vector<std::shared_ptr<Kernel::KThread>>& threads) {
for (std::size_t i = 0; i < threads.size(); ++i) {
- if (!threads[i]->IsHLEThread()) {
+ if (threads[i]->GetThreadTypeForDebugging() == Kernel::ThreadType::User) {
item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i]));
item_list.back()->row = row;
}
@@ -102,7 +102,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
};
const auto& system = Core::System::GetInstance();
- add_threads(system.GlobalScheduler().GetThreadList());
+ add_threads(system.GlobalSchedulerContext().GetThreadList());
return item_list;
}
@@ -117,8 +117,8 @@ QString WaitTreeText::GetText() const {
WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address, const Kernel::HandleTable& handle_table)
: mutex_address(mutex_address) {
mutex_value = Core::System::GetInstance().Memory().Read32(mutex_address);
- owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask);
- owner = handle_table.Get<Kernel::Thread>(owner_handle);
+ owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Svc::HandleWaitMask);
+ owner = handle_table.Get<Kernel::KThread>(owner_handle);
}
WaitTreeMutexInfo::~WaitTreeMutexInfo() = default;
@@ -128,7 +128,7 @@ QString WaitTreeMutexInfo::GetText() const {
}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const {
- const bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0;
+ const bool has_waiters = (mutex_value & Kernel::Svc::HandleWaitMask) != 0;
std::vector<std::unique_ptr<WaitTreeItem>> list;
list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters)));
@@ -140,7 +140,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() cons
return list;
}
-WaitTreeCallstack::WaitTreeCallstack(const Kernel::Thread& thread) : thread(thread) {}
+WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread) : thread(thread) {}
WaitTreeCallstack::~WaitTreeCallstack() = default;
QString WaitTreeCallstack::GetText() const {
@@ -150,7 +150,7 @@ QString WaitTreeCallstack::GetText() const {
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list;
- if (thread.IsHLEThread()) {
+ if (thread.GetThreadTypeForDebugging() != Kernel::ThreadType::User) {
return list;
}
@@ -170,7 +170,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
return list;
}
-WaitTreeSynchronizationObject::WaitTreeSynchronizationObject(const Kernel::SynchronizationObject& o)
+WaitTreeSynchronizationObject::WaitTreeSynchronizationObject(
+ const Kernel::KSynchronizationObject& o)
: object(o) {}
WaitTreeSynchronizationObject::~WaitTreeSynchronizationObject() = default;
@@ -189,12 +190,12 @@ QString WaitTreeSynchronizationObject::GetText() const {
}
std::unique_ptr<WaitTreeSynchronizationObject> WaitTreeSynchronizationObject::make(
- const Kernel::SynchronizationObject& object) {
+ const Kernel::KSynchronizationObject& object) {
switch (object.GetHandleType()) {
case Kernel::HandleType::ReadableEvent:
- return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::ReadableEvent&>(object));
+ return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::KReadableEvent&>(object));
case Kernel::HandleType::Thread:
- return std::make_unique<WaitTreeThread>(static_cast<const Kernel::Thread&>(object));
+ return std::make_unique<WaitTreeThread>(static_cast<const Kernel::KThread&>(object));
default:
return std::make_unique<WaitTreeSynchronizationObject>(object);
}
@@ -203,7 +204,7 @@ std::unique_ptr<WaitTreeSynchronizationObject> WaitTreeSynchronizationObject::ma
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSynchronizationObject::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list;
- const auto& threads = object.GetWaitingThreads();
+ const auto& threads = object.GetWaitingThreadsForDebugging();
if (threads.empty()) {
list.push_back(std::make_unique<WaitTreeText>(tr("waited by no thread")));
} else {
@@ -212,8 +213,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSynchronizationObject::GetChi
return list;
}
-WaitTreeObjectList::WaitTreeObjectList(
- const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& list, bool w_all)
+WaitTreeObjectList::WaitTreeObjectList(const std::vector<Kernel::KSynchronizationObject*>& list,
+ bool w_all)
: object_list(list), wait_all(w_all) {}
WaitTreeObjectList::~WaitTreeObjectList() = default;
@@ -231,57 +232,54 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeObjectList::GetChildren() con
return list;
}
-WaitTreeThread::WaitTreeThread(const Kernel::Thread& thread)
+WaitTreeThread::WaitTreeThread(const Kernel::KThread& thread)
: WaitTreeSynchronizationObject(thread) {}
WaitTreeThread::~WaitTreeThread() = default;
QString WaitTreeThread::GetText() const {
- const auto& thread = static_cast<const Kernel::Thread&>(object);
+ const auto& thread = static_cast<const Kernel::KThread&>(object);
QString status;
- switch (thread.GetStatus()) {
- case Kernel::ThreadStatus::Running:
- status = tr("running");
- break;
- case Kernel::ThreadStatus::Ready:
- if (!thread.IsPaused()) {
- if (thread.WasRunning()) {
- status = tr("running");
- } else {
- status = tr("ready");
- }
+ switch (thread.GetState()) {
+ case Kernel::ThreadState::Runnable:
+ if (!thread.IsSuspended()) {
+ status = tr("runnable");
} else {
status = tr("paused");
}
break;
- case Kernel::ThreadStatus::Paused:
- status = tr("paused");
- break;
- case Kernel::ThreadStatus::WaitHLEEvent:
- status = tr("waiting for HLE return");
- break;
- case Kernel::ThreadStatus::WaitSleep:
- status = tr("sleeping");
- break;
- case Kernel::ThreadStatus::WaitIPC:
- status = tr("waiting for IPC reply");
- break;
- case Kernel::ThreadStatus::WaitSynch:
- status = tr("waiting for objects");
- break;
- case Kernel::ThreadStatus::WaitMutex:
- status = tr("waiting for mutex");
- break;
- case Kernel::ThreadStatus::WaitCondVar:
- status = tr("waiting for condition variable");
+ case Kernel::ThreadState::Waiting:
+ switch (thread.GetWaitReasonForDebugging()) {
+ case Kernel::ThreadWaitReasonForDebugging::Sleep:
+ status = tr("sleeping");
+ break;
+ case Kernel::ThreadWaitReasonForDebugging::IPC:
+ status = tr("waiting for IPC reply");
+ break;
+ case Kernel::ThreadWaitReasonForDebugging::Synchronization:
+ status = tr("waiting for objects");
+ break;
+ case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
+ status = tr("waiting for condition variable");
+ break;
+ case Kernel::ThreadWaitReasonForDebugging::Arbitration:
+ status = tr("waiting for address arbiter");
+ break;
+ case Kernel::ThreadWaitReasonForDebugging::Suspended:
+ status = tr("waiting for suspend resume");
+ break;
+ default:
+ status = tr("waiting");
+ break;
+ }
break;
- case Kernel::ThreadStatus::WaitArb:
- status = tr("waiting for address arbiter");
+ case Kernel::ThreadState::Initialized:
+ status = tr("initialized");
break;
- case Kernel::ThreadStatus::Dormant:
- status = tr("dormant");
+ case Kernel::ThreadState::Terminated:
+ status = tr("terminated");
break;
- case Kernel::ThreadStatus::Dead:
- status = tr("dead");
+ default:
+ status = tr("unknown");
break;
}
@@ -296,36 +294,33 @@ QString WaitTreeThread::GetText() const {
QColor WaitTreeThread::GetColor() const {
const std::size_t color_index = IsDarkTheme() ? 1 : 0;
- const auto& thread = static_cast<const Kernel::Thread&>(object);
- switch (thread.GetStatus()) {
- case Kernel::ThreadStatus::Running:
- return QColor(WaitTreeColors[0][color_index]);
- case Kernel::ThreadStatus::Ready:
- if (!thread.IsPaused()) {
- if (thread.WasRunning()) {
- return QColor(WaitTreeColors[1][color_index]);
- } else {
- return QColor(WaitTreeColors[2][color_index]);
- }
+ const auto& thread = static_cast<const Kernel::KThread&>(object);
+ switch (thread.GetState()) {
+ case Kernel::ThreadState::Runnable:
+ if (!thread.IsSuspended()) {
+ return QColor(WaitTreeColors[0][color_index]);
} else {
+ return QColor(WaitTreeColors[2][color_index]);
+ }
+ case Kernel::ThreadState::Waiting:
+ switch (thread.GetWaitReasonForDebugging()) {
+ case Kernel::ThreadWaitReasonForDebugging::IPC:
+ return QColor(WaitTreeColors[4][color_index]);
+ case Kernel::ThreadWaitReasonForDebugging::Sleep:
+ return QColor(WaitTreeColors[5][color_index]);
+ case Kernel::ThreadWaitReasonForDebugging::Synchronization:
+ case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
+ case Kernel::ThreadWaitReasonForDebugging::Arbitration:
+ case Kernel::ThreadWaitReasonForDebugging::Suspended:
+ return QColor(WaitTreeColors[6][color_index]);
+ break;
+ default:
return QColor(WaitTreeColors[3][color_index]);
}
- case Kernel::ThreadStatus::Paused:
- return QColor(WaitTreeColors[4][color_index]);
- case Kernel::ThreadStatus::WaitHLEEvent:
- case Kernel::ThreadStatus::WaitIPC:
- return QColor(WaitTreeColors[5][color_index]);
- case Kernel::ThreadStatus::WaitSleep:
- return QColor(WaitTreeColors[6][color_index]);
- case Kernel::ThreadStatus::WaitSynch:
- case Kernel::ThreadStatus::WaitMutex:
- case Kernel::ThreadStatus::WaitCondVar:
- case Kernel::ThreadStatus::WaitArb:
+ case Kernel::ThreadState::Initialized:
return QColor(WaitTreeColors[7][color_index]);
- case Kernel::ThreadStatus::Dormant:
+ case Kernel::ThreadState::Terminated:
return QColor(WaitTreeColors[8][color_index]);
- case Kernel::ThreadStatus::Dead:
- return QColor(WaitTreeColors[9][color_index]);
default:
return WaitTreeItem::GetColor();
}
@@ -334,37 +329,31 @@ QColor WaitTreeThread::GetColor() const {
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeSynchronizationObject::GetChildren());
- const auto& thread = static_cast<const Kernel::Thread&>(object);
+ const auto& thread = static_cast<const Kernel::KThread&>(object);
QString processor;
- switch (thread.GetProcessorID()) {
- case Kernel::ThreadProcessorId::THREADPROCESSORID_IDEAL:
+ switch (thread.GetActiveCore()) {
+ case Kernel::Svc::IdealCoreUseProcessValue:
processor = tr("ideal");
break;
- case Kernel::ThreadProcessorId::THREADPROCESSORID_0:
- case Kernel::ThreadProcessorId::THREADPROCESSORID_1:
- case Kernel::ThreadProcessorId::THREADPROCESSORID_2:
- case Kernel::ThreadProcessorId::THREADPROCESSORID_3:
- processor = tr("core %1").arg(thread.GetProcessorID());
- break;
default:
- processor = tr("Unknown processor %1").arg(thread.GetProcessorID());
+ processor = tr("core %1").arg(thread.GetActiveCore());
break;
}
list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
- list.push_back(
- std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.GetIdealCore())));
- list.push_back(
- std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.GetAffinityMask())));
+ list.push_back(std::make_unique<WaitTreeText>(
+ tr("ideal core = %1").arg(thread.GetIdealCoreForDebugging())));
+ list.push_back(std::make_unique<WaitTreeText>(
+ tr("affinity mask = %1").arg(thread.GetAffinityMask().GetAffinityMask())));
list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID())));
list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
.arg(thread.GetPriority())
- .arg(thread.GetNominalPriority())));
+ .arg(thread.GetBasePriority())));
list.push_back(std::make_unique<WaitTreeText>(
- tr("last running ticks = %1").arg(thread.GetLastRunningTicks())));
+ tr("last running ticks = %1").arg(thread.GetLastScheduledTick())));
- const VAddr mutex_wait_address = thread.GetMutexWaitAddress();
+ const VAddr mutex_wait_address = thread.GetMutexWaitAddressForDebugging();
if (mutex_wait_address != 0) {
const auto& handle_table = thread.GetOwnerProcess()->GetHandleTable();
list.push_back(std::make_unique<WaitTreeMutexInfo>(mutex_wait_address, handle_table));
@@ -372,9 +361,11 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
}
- if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynch) {
- list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetSynchronizationObjects(),
- thread.IsWaitingSync()));
+ if (thread.GetState() == Kernel::ThreadState::Waiting &&
+ thread.GetWaitReasonForDebugging() ==
+ Kernel::ThreadWaitReasonForDebugging::Synchronization) {
+ list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjectsForDebugging(),
+ thread.IsCancellable()));
}
list.push_back(std::make_unique<WaitTreeCallstack>(thread));
@@ -382,11 +373,11 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
return list;
}
-WaitTreeEvent::WaitTreeEvent(const Kernel::ReadableEvent& object)
+WaitTreeEvent::WaitTreeEvent(const Kernel::KReadableEvent& object)
: WaitTreeSynchronizationObject(object) {}
WaitTreeEvent::~WaitTreeEvent() = default;
-WaitTreeThreadList::WaitTreeThreadList(const std::vector<std::shared_ptr<Kernel::Thread>>& list)
+WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::KThread*>& list)
: thread_list(list) {}
WaitTreeThreadList::~WaitTreeThreadList() = default;
@@ -463,7 +454,7 @@ void WaitTreeModel::InitItems() {
thread_items = WaitTreeItem::MakeThreadItemList();
}
-WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) {
+WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("&Wait Tree"), parent) {
setObjectName(QStringLiteral("WaitTreeWidget"));
view = new QTreeView(this);
view->setHeaderHidden(true);
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index 8e3bc4b24..3da2fdfd2 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -18,9 +18,9 @@ class EmuThread;
namespace Kernel {
class HandleTable;
-class ReadableEvent;
-class SynchronizationObject;
-class Thread;
+class KReadableEvent;
+class KSynchronizationObject;
+class KThread;
} // namespace Kernel
class WaitTreeThread;
@@ -83,56 +83,55 @@ private:
VAddr mutex_address;
u32 mutex_value;
Kernel::Handle owner_handle;
- std::shared_ptr<Kernel::Thread> owner;
+ std::shared_ptr<Kernel::KThread> owner;
};
class WaitTreeCallstack : public WaitTreeExpandableItem {
Q_OBJECT
public:
- explicit WaitTreeCallstack(const Kernel::Thread& thread);
+ explicit WaitTreeCallstack(const Kernel::KThread& thread);
~WaitTreeCallstack() override;
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
- const Kernel::Thread& thread;
+ const Kernel::KThread& thread;
};
class WaitTreeSynchronizationObject : public WaitTreeExpandableItem {
Q_OBJECT
public:
- explicit WaitTreeSynchronizationObject(const Kernel::SynchronizationObject& object);
+ explicit WaitTreeSynchronizationObject(const Kernel::KSynchronizationObject& object);
~WaitTreeSynchronizationObject() override;
static std::unique_ptr<WaitTreeSynchronizationObject> make(
- const Kernel::SynchronizationObject& object);
+ const Kernel::KSynchronizationObject& object);
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
protected:
- const Kernel::SynchronizationObject& object;
+ const Kernel::KSynchronizationObject& object;
};
class WaitTreeObjectList : public WaitTreeExpandableItem {
Q_OBJECT
public:
- WaitTreeObjectList(const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& list,
- bool wait_all);
+ WaitTreeObjectList(const std::vector<Kernel::KSynchronizationObject*>& list, bool wait_all);
~WaitTreeObjectList() override;
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
- const std::vector<std::shared_ptr<Kernel::SynchronizationObject>>& object_list;
+ const std::vector<Kernel::KSynchronizationObject*>& object_list;
bool wait_all;
};
class WaitTreeThread : public WaitTreeSynchronizationObject {
Q_OBJECT
public:
- explicit WaitTreeThread(const Kernel::Thread& thread);
+ explicit WaitTreeThread(const Kernel::KThread& thread);
~WaitTreeThread() override;
QString GetText() const override;
@@ -143,21 +142,21 @@ public:
class WaitTreeEvent : public WaitTreeSynchronizationObject {
Q_OBJECT
public:
- explicit WaitTreeEvent(const Kernel::ReadableEvent& object);
+ explicit WaitTreeEvent(const Kernel::KReadableEvent& object);
~WaitTreeEvent() override;
};
class WaitTreeThreadList : public WaitTreeExpandableItem {
Q_OBJECT
public:
- explicit WaitTreeThreadList(const std::vector<std::shared_ptr<Kernel::Thread>>& list);
+ explicit WaitTreeThreadList(const std::vector<Kernel::KThread*>& list);
~WaitTreeThreadList() override;
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
- const std::vector<std::shared_ptr<Kernel::Thread>>& thread_list;
+ const std::vector<Kernel::KThread*>& thread_list;
};
class WaitTreeModel : public QAbstractItemModel {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 6a71d9644..9afd5b45f 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -25,7 +25,8 @@
#include "yuzu/main.h"
#include "yuzu/uisettings.h"
-GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {}
+GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
+ : QObject(parent), gamelist{gamelist} {}
// EventFilter in order to process systemkeys while editing the searchfield
bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) {
@@ -56,7 +57,7 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
case Qt::Key_Return:
case Qt::Key_Enter: {
if (gamelist->search_field->visible == 1) {
- QString file_path = gamelist->getLastFilterResultItem();
+ const QString file_path = gamelist->GetLastFilterResultItem();
// To avoid loading error dialog loops while confirming them using enter
// Also users usually want to run a different game after closing one
@@ -83,22 +84,25 @@ void GameListSearchField::setFilterResult(int visible, int total) {
label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
}
-QString GameList::getLastFilterResultItem() const {
- QStandardItem* folder;
- QStandardItem* child;
+QString GameList::GetLastFilterResultItem() const {
QString file_path;
const int folder_count = item_model->rowCount();
+
for (int i = 0; i < folder_count; ++i) {
- folder = item_model->item(i, 0);
+ const QStandardItem* folder = item_model->item(i, 0);
const QModelIndex folder_index = folder->index();
const int children_count = folder->rowCount();
+
for (int j = 0; j < children_count; ++j) {
- if (!tree_view->isRowHidden(j, folder_index)) {
- child = folder->child(j, 0);
- file_path = child->data(GameListItemPath::FullPathRole).toString();
+ if (tree_view->isRowHidden(j, folder_index)) {
+ continue;
}
+
+ const QStandardItem* child = folder->child(j, 0);
+ file_path = child->data(GameListItemPath::FullPathRole).toString();
}
}
+
return file_path;
}
@@ -113,9 +117,9 @@ void GameListSearchField::setFocus() {
}
GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
- auto* const key_release_eater = new KeyReleaseEater(parent);
+ auto* const key_release_eater = new KeyReleaseEater(parent, this);
layout_filter = new QHBoxLayout;
- layout_filter->setMargin(8);
+ layout_filter->setContentsMargins(8, 8, 8, 8);
label_filter = new QLabel;
label_filter->setText(tr("Filter:"));
edit_filter = new QLineEdit;
@@ -123,7 +127,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
edit_filter->installEventFilter(key_release_eater);
edit_filter->setClearButtonEnabled(true);
- connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged);
+ connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged);
label_filter_result = new QLabel;
button_filter_close = new QToolButton(this);
button_filter_close->setText(QStringLiteral("X"));
@@ -133,7 +137,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
"#000000; font-weight: bold; background: #F0F0F0; }"
"QToolButton:hover{ border: none; padding: 0px; color: "
"#EEEEEE; font-weight: bold; background: #E81123}"));
- connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked);
+ connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked);
layout_filter->setSpacing(10);
layout_filter->addWidget(label_filter);
layout_filter->addWidget(edit_filter);
@@ -159,16 +163,22 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput)
}
// Syncs the expanded state of Game Directories with settings to persist across sessions
-void GameList::onItemExpanded(const QModelIndex& item) {
+void GameList::OnItemExpanded(const QModelIndex& item) {
const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>();
- if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
- type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir)
- item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded =
- tree_view->isExpanded(item);
+ const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
+ type == GameListItemType::UserNandDir ||
+ type == GameListItemType::SysNandDir;
+
+ if (!is_dir) {
+ return;
+ }
+
+ UISettings::values.game_dirs[item.data(GameListDir::GameDirRole).toInt()].expanded =
+ tree_view->isExpanded(item);
}
// Event in order to filter the gamelist after editing the searchfield
-void GameList::onTextChanged(const QString& new_text) {
+void GameList::OnTextChanged(const QString& new_text) {
const int folder_count = tree_view->model()->rowCount();
QString edit_filter_text = new_text.toLower();
QStandardItem* folder;
@@ -224,7 +234,7 @@ void GameList::onTextChanged(const QString& new_text) {
}
}
-void GameList::onUpdateThemedIcons() {
+void GameList::OnUpdateThemedIcons() {
for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
QStandardItem* child = item_model->invisibleRootItem()->child(i);
@@ -252,9 +262,9 @@ void GameList::onUpdateThemedIcons() {
Qt::DecorationRole);
break;
case GameListItemType::CustomDir: {
- const UISettings::GameDir* game_dir =
- child->data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
- const QString icon_name = QFileInfo::exists(game_dir->path)
+ const UISettings::GameDir& game_dir =
+ UISettings::values.game_dirs[child->data(GameListDir::GameDirRole).toInt()];
+ const QString icon_name = QFileInfo::exists(game_dir.path)
? QStringLiteral("folder")
: QStringLiteral("bad_folder");
child->setData(
@@ -276,7 +286,7 @@ void GameList::onUpdateThemedIcons() {
}
}
-void GameList::onFilterCloseClicked() {
+void GameList::OnFilterCloseClicked() {
main_window->filterBarSetChecked(false);
}
@@ -317,11 +327,11 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
}
item_model->setSortRole(GameListItemPath::SortRole);
- connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons);
+ connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
- connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded);
- connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded);
+ connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
+ connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
// We must register all custom types with the Qt Automoc system so that we are able to use
// it with signals/slots. In this case, QList falls under the umbrells of custom types.
@@ -338,17 +348,17 @@ GameList::~GameList() {
emit ShouldCancelWorker();
}
-void GameList::setFilterFocus() {
+void GameList::SetFilterFocus() {
if (tree_view->model()->rowCount() > 0) {
search_field->setFocus();
}
}
-void GameList::setFilterVisible(bool visibility) {
+void GameList::SetFilterVisible(bool visibility) {
search_field->setVisible(visibility);
}
-void GameList::clearFilter() {
+void GameList::ClearFilter() {
search_field->clear();
}
@@ -356,7 +366,7 @@ void GameList::AddDirEntry(GameListDir* entry_items) {
item_model->invisibleRootItem()->appendRow(entry_items);
tree_view->setExpanded(
entry_items->index(),
- entry_items->data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded);
+ UISettings::values.game_dirs[entry_items->data(GameListDir::GameDirRole).toInt()].expanded);
}
void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent) {
@@ -397,10 +407,11 @@ void GameList::ValidateEntry(const QModelIndex& item) {
}
}
-bool GameList::isEmpty() const {
+bool GameList::IsEmpty() const {
for (int i = 0; i < item_model->rowCount(); i++) {
const QStandardItem* child = item_model->invisibleRootItem()->child(i);
const auto type = static_cast<GameListItemType>(child->type());
+
if (!child->hasChildren() &&
(type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir ||
type == GameListItemType::SysNandDir)) {
@@ -408,11 +419,12 @@ bool GameList::isEmpty() const {
i--;
}
}
+
return !item_model->invisibleRootItem()->hasChildren();
}
-void GameList::DonePopulating(QStringList watch_list) {
- emit ShowList(!isEmpty());
+void GameList::DonePopulating(const QStringList& watch_list) {
+ emit ShowList(!IsEmpty());
item_model->invisibleRootItem()->appendRow(new GameListAddDir());
@@ -472,7 +484,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}
-void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) {
+void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) {
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location"));
QAction* open_transferable_shader_cache =
@@ -537,7 +549,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string pat
void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
UISettings::GameDir& game_dir =
- *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
+ UISettings::values.game_dirs[selected.data(GameListDir::GameDirRole).toInt()];
QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders"));
QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory"));
@@ -556,8 +568,7 @@ void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
}
void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
- UISettings::GameDir& game_dir =
- *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
+ const int game_dir_index = selected.data(GameListDir::GameDirRole).toInt();
QAction* move_up = context_menu.addAction(tr("\u25B2 Move Up"));
QAction* move_down = context_menu.addAction(tr("\u25bc Move Down"));
@@ -568,34 +579,39 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
move_up->setEnabled(row > 0);
move_down->setEnabled(row < item_model->rowCount() - 2);
- connect(move_up, &QAction::triggered, [this, selected, row, &game_dir] {
- // find the indices of the items in settings and swap them
- std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)],
- UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(
- *selected.sibling(row - 1, 0)
- .data(GameListDir::GameDirRole)
- .value<UISettings::GameDir*>())]);
+ connect(move_up, &QAction::triggered, [this, selected, row, game_dir_index] {
+ const int other_index = selected.sibling(row - 1, 0).data(GameListDir::GameDirRole).toInt();
+ // swap the items in the settings
+ std::swap(UISettings::values.game_dirs[game_dir_index],
+ UISettings::values.game_dirs[other_index]);
+ // swap the indexes held by the QVariants
+ item_model->setData(selected, QVariant(other_index), GameListDir::GameDirRole);
+ item_model->setData(selected.sibling(row - 1, 0), QVariant(game_dir_index),
+ GameListDir::GameDirRole);
// move the treeview items
QList<QStandardItem*> item = item_model->takeRow(row);
item_model->invisibleRootItem()->insertRow(row - 1, item);
- tree_view->setExpanded(selected, game_dir.expanded);
+ tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded);
});
- connect(move_down, &QAction::triggered, [this, selected, row, &game_dir] {
- // find the indices of the items in settings and swap them
- std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)],
- UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(
- *selected.sibling(row + 1, 0)
- .data(GameListDir::GameDirRole)
- .value<UISettings::GameDir*>())]);
+ connect(move_down, &QAction::triggered, [this, selected, row, game_dir_index] {
+ const int other_index = selected.sibling(row + 1, 0).data(GameListDir::GameDirRole).toInt();
+ // swap the items in the settings
+ std::swap(UISettings::values.game_dirs[game_dir_index],
+ UISettings::values.game_dirs[other_index]);
+ // swap the indexes held by the QVariants
+ item_model->setData(selected, QVariant(other_index), GameListDir::GameDirRole);
+ item_model->setData(selected.sibling(row + 1, 0), QVariant(game_dir_index),
+ GameListDir::GameDirRole);
// move the treeview items
const QList<QStandardItem*> item = item_model->takeRow(row);
item_model->invisibleRootItem()->insertRow(row + 1, item);
- tree_view->setExpanded(selected, game_dir.expanded);
+ tree_view->setExpanded(selected, UISettings::values.game_dirs[game_dir_index].expanded);
});
- connect(open_directory_location, &QAction::triggered,
- [this, game_dir] { emit OpenDirectory(game_dir.path); });
+ connect(open_directory_location, &QAction::triggered, [this, game_dir_index] {
+ emit OpenDirectory(UISettings::values.game_dirs[game_dir_index].path);
+ });
}
void GameList::LoadCompatibilityList() {
@@ -690,12 +706,15 @@ void GameList::SaveInterfaceLayout() {
}
void GameList::LoadInterfaceLayout() {
- auto header = tree_view->header();
- if (!header->restoreState(UISettings::values.gamelist_header_state)) {
- // We are using the name column to display icons and titles
- // so make it as large as possible as default.
- header->resizeSection(COLUMN_NAME, header->width());
+ auto* header = tree_view->header();
+
+ if (header->restoreState(UISettings::values.gamelist_header_state)) {
+ return;
}
+
+ // We are using the name column to display icons and titles
+ // so make it as large as possible as default.
+ header->resizeSection(COLUMN_NAME, header->width());
}
const QStringList GameList::supported_file_extensions = {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 78e2ba169..58059a3c4 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -67,11 +67,11 @@ public:
FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
~GameList() override;
- QString getLastFilterResultItem() const;
- void clearFilter();
- void setFilterFocus();
- void setFilterVisible(bool visibility);
- bool isEmpty() const;
+ QString GetLastFilterResultItem() const;
+ void ClearFilter();
+ void SetFilterFocus();
+ void SetFilterVisible(bool visibility);
+ bool IsEmpty() const;
void LoadCompatibilityList();
void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
@@ -82,7 +82,7 @@ public:
static const QStringList supported_file_extensions;
signals:
- void GameChosen(QString game_path);
+ void GameChosen(const QString& game_path);
void ShouldCancelWorker();
void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
const std::string& game_path);
@@ -99,21 +99,21 @@ signals:
void ShowList(bool show);
private slots:
- void onItemExpanded(const QModelIndex& item);
- void onTextChanged(const QString& new_text);
- void onFilterCloseClicked();
- void onUpdateThemedIcons();
+ void OnItemExpanded(const QModelIndex& item);
+ void OnTextChanged(const QString& new_text);
+ void OnFilterCloseClicked();
+ void OnUpdateThemedIcons();
private:
void AddDirEntry(GameListDir* entry_items);
void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
void ValidateEntry(const QModelIndex& item);
- void DonePopulating(QStringList watch_list);
+ void DonePopulating(const QStringList& watch_list);
void RefreshGameDirectory();
void PopupContextMenu(const QPoint& menu_location);
- void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path);
+ void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path);
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 0cd0054c8..f25445f18 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -49,10 +49,10 @@ class GameListItem : public QStandardItem {
public:
// used to access type from item index
- static const int TypeRole = Qt::UserRole + 1;
- static const int SortRole = Qt::UserRole + 2;
+ static constexpr int TypeRole = Qt::UserRole + 1;
+ static constexpr int SortRole = Qt::UserRole + 2;
GameListItem() = default;
- GameListItem(const QString& string) : QStandardItem(string) {
+ explicit GameListItem(const QString& string) : QStandardItem(string) {
setData(string, SortRole);
}
};
@@ -65,10 +65,10 @@ public:
*/
class GameListItemPath : public GameListItem {
public:
- static const int TitleRole = SortRole + 1;
- static const int FullPathRole = SortRole + 2;
- static const int ProgramIdRole = SortRole + 3;
- static const int FileTypeRole = SortRole + 4;
+ static constexpr int TitleRole = SortRole + 1;
+ static constexpr int FullPathRole = SortRole + 2;
+ static constexpr int ProgramIdRole = SortRole + 3;
+ static constexpr int FileTypeRole = SortRole + 4;
GameListItemPath() = default;
GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
@@ -110,18 +110,22 @@ public:
const auto& row1 = row_data.at(UISettings::values.row_1_text_id);
const int row2_id = UISettings::values.row_2_text_id;
- if (role == SortRole)
+ if (role == SortRole) {
return row1.toLower();
+ }
- if (row2_id == 4) // None
+ // None
+ if (row2_id == 4) {
return row1;
+ }
const auto& row2 = row_data.at(row2_id);
- if (row1 == row2)
+ if (row1 == row2) {
return row1;
+ }
- return QString(row1 + QStringLiteral("\n ") + row2);
+ return QStringLiteral("%1\n %2").arg(row1, row2);
}
return GameListItem::data(role);
@@ -131,7 +135,7 @@ public:
class GameListItemCompat : public GameListItem {
Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
public:
- static const int CompatNumberRole = SortRole;
+ static constexpr int CompatNumberRole = SortRole;
GameListItemCompat() = default;
explicit GameListItemCompat(const QString& compatibility) {
setData(type(), TypeRole);
@@ -170,7 +174,8 @@ public:
}
bool operator<(const QStandardItem& other) const override {
- return data(CompatNumberRole) < other.data(CompatNumberRole);
+ return data(CompatNumberRole).value<QString>() <
+ other.data(CompatNumberRole).value<QString>();
}
};
@@ -181,7 +186,7 @@ public:
*/
class GameListItemSize : public GameListItem {
public:
- static const int SizeRole = SortRole;
+ static constexpr int SizeRole = SortRole;
GameListItemSize() = default;
explicit GameListItemSize(const qulonglong size_bytes) {
@@ -217,7 +222,7 @@ public:
class GameListDir : public GameListItem {
public:
- static const int GameDirRole = Qt::UserRole + 2;
+ static constexpr int GameDirRole = Qt::UserRole + 2;
explicit GameListDir(UISettings::GameDir& directory,
GameListItemType dir_type = GameListItemType::CustomDir)
@@ -225,7 +230,7 @@ public:
setData(type(), TypeRole);
UISettings::GameDir* game_dir = &directory;
- setData(QVariant::fromValue(game_dir), GameDirRole);
+ setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole);
const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
switch (dir_type) {
@@ -326,7 +331,7 @@ public:
private:
class KeyReleaseEater : public QObject {
public:
- explicit KeyReleaseEater(GameList* gamelist);
+ explicit KeyReleaseEater(GameList* gamelist, QObject* parent = nullptr);
private:
GameList* gamelist = nullptr;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index e0ce45fd9..23643aea2 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -235,12 +235,11 @@ GameListWorker::~GameListWorker() = default;
void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
using namespace FileSys;
- const auto& cache =
- dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider());
+ auto& system = Core::System::GetInstance();
+ const auto& cache = dynamic_cast<ContentProviderUnion&>(system.GetContentProvider());
- std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games;
- installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application,
- ContentRecordType::Program);
+ auto installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application,
+ ContentRecordType::Program);
if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) {
installed_games = cache.ListEntriesFilterOrigin(
@@ -254,23 +253,27 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
}
for (const auto& [slot, game] : installed_games) {
- if (slot == ContentProviderUnionSlot::FrontendManual)
+ if (slot == ContentProviderUnionSlot::FrontendManual) {
continue;
+ }
const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
- std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
- if (!loader)
+ std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(system, file);
+ if (!loader) {
continue;
+ }
std::vector<u8> icon;
std::string name;
u64 program_id = 0;
loader->ReadProgramId(program_id);
- const PatchManager patch{program_id};
+ const PatchManager patch{program_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control);
- if (control != nullptr)
+ if (control != nullptr) {
GetMetadataFromControlNCA(patch, *control, icon, name);
+ }
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
compatibility_list, patch),
@@ -280,9 +283,11 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
unsigned int recursion, GameListDir* parent_dir) {
- const auto callback = [this, target, recursion,
- parent_dir](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
+ auto& system = Core::System::GetInstance();
+
+ const auto callback = [this, target, recursion, parent_dir,
+ &system](u64* num_entries_out, const std::string& directory,
+ const std::string& virtual_name) -> bool {
if (stop_processing) {
// Breaks the callback loop.
return false;
@@ -293,7 +298,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
- auto loader = Loader::GetLoader(file);
+ auto loader = Loader::GetLoader(system, file);
if (!loader) {
return true;
}
@@ -331,7 +336,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
std::string name = " ";
[[maybe_unused]] const auto res3 = loader->ReadTitle(name);
- const FileSys::PatchManager patch{program_id};
+ const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
compatibility_list, patch),
diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h
index e4aba1b06..68e03fe4e 100644
--- a/src/yuzu/install_dialog.h
+++ b/src/yuzu/install_dialog.h
@@ -20,9 +20,8 @@ public:
explicit InstallDialog(QWidget* parent, const QStringList& files);
~InstallDialog() override;
- QStringList GetFiles() const;
- bool ShouldOverwriteFiles() const;
- int GetMinimumWidth() const;
+ [[nodiscard]] QStringList GetFiles() const;
+ [[nodiscard]] int GetMinimumWidth() const;
private:
QListWidget* file_list;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index a1b61d119..0ba7c07cc 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -11,21 +11,24 @@
#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
+#include "applets/controller.h"
#include "applets/error.h"
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
#include "applets/web_browser.h"
+#include "common/nvidia_flags.h"
#include "configuration/configure_input.h"
#include "configuration/configure_per_game.h"
+#include "configuration/configure_vibration.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
+#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/general_frontend.h"
+#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
-#include "core/hle/service/hid/controllers/npad.h"
-#include "core/hle/service/hid/hid.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
@@ -47,12 +50,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDialogButtonBox>
+#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QProgressBar>
#include <QProgressDialog>
+#include <QPushButton>
#include <QShortcut>
#include <QStatusBar>
#include <QSysInfo>
@@ -77,6 +82,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/core.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -84,7 +90,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/file_sys/romfs.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/submission_package.h"
-#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -104,6 +109,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
#include "yuzu/debugger/console.h"
+#include "yuzu/debugger/controller.h"
#include "yuzu/debugger/profiler.h"
#include "yuzu/debugger/wait_tree.h"
#include "yuzu/discord.h"
@@ -119,14 +125,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/discord_impl.h"
#endif
-#ifdef YUZU_USE_QT_WEB_ENGINE
-#include <QWebEngineProfile>
-#include <QWebEngineScript>
-#include <QWebEngineScriptCollection>
-#include <QWebEngineSettings>
-#include <QWebEngineView>
-#endif
-
#ifdef QT_STATICPLUGIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
@@ -143,12 +141,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
constexpr int default_mouse_timeout = 2500;
-constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
-
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
- * user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones.
+ * user. This is 32-bits - if we have more than 32 callouts, we should retire and recycle old ones.
*/
enum class CalloutFlag : uint32_t {
Telemetry = 0x1,
@@ -167,7 +163,7 @@ void GMainWindow::ShowTelemetryCallout() {
"<br/><br/>Would you like to share your usage data with us?");
if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) {
Settings::values.enable_telemetry = false;
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
}
}
@@ -186,6 +182,30 @@ static void InitializeLogging() {
#endif
}
+static void RemoveCachedContents() {
+ const auto offline_fonts = Common::FS::SanitizePath(
+ fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
+ Common::FS::DirectorySeparator::PlatformDefault);
+
+ const auto offline_manual = Common::FS::SanitizePath(
+ fmt::format("{}/offline_web_applet_manual",
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
+ Common::FS::DirectorySeparator::PlatformDefault);
+ const auto offline_legal_information = Common::FS::SanitizePath(
+ fmt::format("{}/offline_web_applet_legal_information",
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
+ Common::FS::DirectorySeparator::PlatformDefault);
+ const auto offline_system_data = Common::FS::SanitizePath(
+ fmt::format("{}/offline_web_applet_system_data",
+ Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
+ Common::FS::DirectorySeparator::PlatformDefault);
+
+ Common::FS::DeleteDirRecursively(offline_fonts);
+ Common::FS::DeleteDirRecursively(offline_manual);
+ Common::FS::DeleteDirRecursively(offline_legal_information);
+ Common::FS::DeleteDirRecursively(offline_system_data);
+}
+
GMainWindow::GMainWindow()
: input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
@@ -254,6 +274,9 @@ GMainWindow::GMainWindow()
FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
+ // Remove cached contents generated during the previous session
+ RemoveCachedContents();
+
// Gen keys if necessary
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
@@ -271,9 +294,47 @@ GMainWindow::GMainWindow()
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
connect(ui.menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
+ MigrateConfigFiles();
+
+ ui.action_Fullscreen->setChecked(false);
+
QStringList args = QApplication::arguments();
- if (args.length() >= 2) {
- BootGame(args[1]);
+
+ if (args.size() < 2) {
+ return;
+ }
+
+ QString game_path;
+
+ for (int i = 1; i < args.size(); ++i) {
+ // Preserves drag/drop functionality
+ if (args.size() == 2 && !args[1].startsWith(QChar::fromLatin1('-'))) {
+ game_path = args[1];
+ break;
+ }
+
+ // Launch game in fullscreen mode
+ if (args[i] == QStringLiteral("-f")) {
+ ui.action_Fullscreen->setChecked(true);
+ continue;
+ }
+
+ // Launch game at path
+ if (args[i] == QStringLiteral("-g")) {
+ if (i >= args.size() - 1) {
+ continue;
+ }
+
+ if (args[i + 1].startsWith(QChar::fromLatin1('-'))) {
+ continue;
+ }
+
+ game_path = args[++i];
+ }
+ }
+
+ if (!game_path.isEmpty()) {
+ BootGame(game_path);
}
}
@@ -283,22 +344,37 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
+void GMainWindow::ControllerSelectorReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters) {
+ QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get());
+
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
+ Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ dialog.exec();
+
+ emit ControllerSelectorReconfigureFinished();
+
+ // Don't forget to apply settings.
+ Settings::Apply(Core::System::GetInstance());
+ config->Save();
+
+ UpdateStatusButtons();
+}
+
void GMainWindow::ProfileSelectorSelectProfile() {
- const Service::Account::ProfileManager manager;
- int index = 0;
- if (manager.GetUserCount() != 1) {
- QtProfileSelectionDialog dialog(this);
- dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
- Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
- dialog.setWindowModality(Qt::WindowModal);
- if (dialog.exec() == QDialog::Rejected) {
- emit ProfileSelectorFinishedSelection(std::nullopt);
- return;
- }
- index = dialog.GetIndex();
+ QtProfileSelectionDialog dialog(this);
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
+ Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
+ Qt::WindowCloseButtonHint);
+ dialog.setWindowModality(Qt::WindowModal);
+ if (dialog.exec() == QDialog::Rejected) {
+ emit ProfileSelectorFinishedSelection(std::nullopt);
+ return;
}
- const auto uuid = manager.GetUser(static_cast<std::size_t>(index));
+ const Service::Account::ProfileManager manager;
+ const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex()));
if (!uuid.has_value()) {
emit ProfileSelectorFinishedSelection(std::nullopt);
return;
@@ -310,8 +386,9 @@ void GMainWindow::ProfileSelectorSelectProfile() {
void GMainWindow::SoftwareKeyboardGetText(
const Core::Frontend::SoftwareKeyboardParameters& parameters) {
QtSoftwareKeyboardDialog dialog(this, parameters);
- dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
- Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+ dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
+ Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
+ Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
@@ -327,148 +404,141 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message
emit SoftwareKeyboardFinishedCheckDialog();
}
+void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args,
+ bool is_local) {
#ifdef YUZU_USE_QT_WEB_ENGINE
-void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
- NXInputWebEngineView web_browser_view(this);
+ if (disable_web_applet) {
+ emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed,
+ "http://localhost/");
+ return;
+ }
- // Scope to contain the QProgressDialog for initialization
- {
- QProgressDialog progress(this);
- progress.setMinimumDuration(200);
- progress.setLabelText(tr("Loading Web Applet..."));
- progress.setRange(0, 4);
- progress.setValue(0);
- progress.show();
+ QtNXWebEngineView web_browser_view(this, Core::System::GetInstance(), input_subsystem.get());
+
+ ui.action_Pause->setEnabled(false);
+ ui.action_Restart->setEnabled(false);
+ ui.action_Stop->setEnabled(false);
- auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); });
+ {
+ QProgressDialog loading_progress(this);
+ loading_progress.setLabelText(tr("Loading Web Applet..."));
+ loading_progress.setRange(0, 3);
+ loading_progress.setValue(0);
- while (!future.isFinished())
- QApplication::processEvents();
+ if (is_local && !Common::FS::Exists(std::string(main_url))) {
+ loading_progress.show();
- progress.setValue(1);
+ auto future = QtConcurrent::run([this] { emit WebBrowserExtractOfflineRomFS(); });
- // Load the special shim script to handle input and exit.
- QWebEngineScript nx_shim;
- nx_shim.setSourceCode(GetNXShimInjectionScript());
- nx_shim.setWorldId(QWebEngineScript::MainWorld);
- nx_shim.setName(QStringLiteral("nx_inject.js"));
- nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation);
- nx_shim.setRunsOnSubFrames(true);
- web_browser_view.page()->profile()->scripts()->insert(nx_shim);
+ while (!future.isFinished()) {
+ QCoreApplication::processEvents();
+ }
+ }
- web_browser_view.load(
- QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() +
- QString::fromStdString(std::string(additional_args))));
+ loading_progress.setValue(1);
- progress.setValue(2);
+ if (is_local) {
+ web_browser_view.LoadLocalWebPage(main_url, additional_args);
+ } else {
+ web_browser_view.LoadExternalWebPage(main_url, additional_args);
+ }
- render_window->hide();
- web_browser_view.setFocus();
+ if (render_window->IsLoadingComplete()) {
+ render_window->hide();
+ }
const auto& layout = render_window->GetFramebufferLayout();
web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight());
web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height());
web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) /
- Layout::ScreenUndocked::Width);
- web_browser_view.settings()->setAttribute(
- QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
+ static_cast<qreal>(Layout::ScreenUndocked::Width));
+ web_browser_view.setFocus();
web_browser_view.show();
- progress.setValue(3);
+ loading_progress.setValue(2);
- QApplication::processEvents();
+ QCoreApplication::processEvents();
- progress.setValue(4);
+ loading_progress.setValue(3);
}
- bool finished = false;
- QAction* exit_action = new QAction(tr("Exit Web Applet"), this);
- connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; });
- ui.menubar->addAction(exit_action);
+ bool exit_check = false;
- auto& npad =
- Core::System::GetInstance()
- .ServiceManager()
- .GetService<Service::HID::Hid>("hid")
- ->GetAppletResource()
- ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
-
- const auto fire_js_keypress = [&web_browser_view](u32 key_code) {
- web_browser_view.page()->runJavaScript(
- QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));")
- .arg(key_code));
- };
+ // TODO (Morph): Remove this
+ QAction* exit_action = new QAction(tr("Disable Web Applet"), this);
+ connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] {
+ const auto result = QMessageBox::warning(
+ this, tr("Disable Web Applet"),
+ tr("Disabling the web applet will cause it to not be shown again for the rest of the "
+ "emulated session. This can lead to undefined behavior and should only be used with "
+ "Super Mario 3D All-Stars. Are you sure you want to disable the web applet?"),
+ QMessageBox::Yes | QMessageBox::No);
+ if (result == QMessageBox::Yes) {
+ disable_web_applet = true;
+ web_browser_view.SetFinished(true);
+ }
+ });
+ ui.menubar->addAction(exit_action);
- QMessageBox::information(
- this, tr("Exit"),
- tr("To exit the web application, use the game provided controls to select exit, select the "
- "'Exit Web Applet' option in the menu bar, or press the 'Enter' key."));
-
- bool running_exit_check = false;
- while (!finished) {
- QApplication::processEvents();
-
- if (!running_exit_check) {
- web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"),
- [&](const QVariant& res) {
- running_exit_check = false;
- if (res.toBool())
- finished = true;
- });
- running_exit_check = true;
+ while (!web_browser_view.IsFinished()) {
+ QCoreApplication::processEvents();
+
+ if (!exit_check) {
+ web_browser_view.page()->runJavaScript(
+ QStringLiteral("end_applet;"), [&](const QVariant& variant) {
+ exit_check = false;
+ if (variant.toBool()) {
+ web_browser_view.SetFinished(true);
+ web_browser_view.SetExitReason(
+ Service::AM::Applets::WebExitReason::EndButtonPressed);
+ }
+ });
+
+ exit_check = true;
}
- const auto input = npad.GetAndResetPressState();
- for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- if ((input & (1 << i)) != 0) {
- LOG_DEBUG(Frontend, "firing input for button id={:02X}", i);
- web_browser_view.page()->runJavaScript(
- QStringLiteral("yuzu_key_callbacks[%1]();").arg(i));
+ if (web_browser_view.GetCurrentURL().contains(QStringLiteral("localhost"))) {
+ if (!web_browser_view.IsFinished()) {
+ web_browser_view.SetFinished(true);
+ web_browser_view.SetExitReason(Service::AM::Applets::WebExitReason::CallbackURL);
}
+
+ web_browser_view.SetLastURL(web_browser_view.GetCurrentURL().toStdString());
}
- if (input & 0x00888000) // RStick Down | LStick Down | DPad Down
- fire_js_keypress(40); // Down Arrow Key
- else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right
- fire_js_keypress(39); // Right Arrow Key
- else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up
- fire_js_keypress(38); // Up Arrow Key
- else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left
- fire_js_keypress(37); // Left Arrow Key
- else if (input & 0x00000001) // A Button
- fire_js_keypress(13); // Enter Key
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
+ const auto exit_reason = web_browser_view.GetExitReason();
+ const auto last_url = web_browser_view.GetLastURL();
+
web_browser_view.hide();
- render_window->show();
+
render_window->setFocus();
- ui.menubar->removeAction(exit_action);
- // Needed to update render window focus/show and remove menubar action
- QApplication::processEvents();
- emit WebBrowserFinishedBrowsing();
-}
+ if (render_window->IsLoadingComplete()) {
+ render_window->show();
+ }
-#else
+ ui.action_Pause->setEnabled(true);
+ ui.action_Restart->setEnabled(true);
+ ui.action_Stop->setEnabled(true);
-void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) {
- QMessageBox::warning(
- this, tr("Web Applet"),
- tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot "
- "properly display the game manual or web page requested."),
- QMessageBox::Ok, QMessageBox::Ok);
+ ui.menubar->removeAction(exit_action);
- LOG_INFO(Frontend,
- "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at "
- "'{}' with arguments '{}'!",
- filename, additional_args);
+ QCoreApplication::processEvents();
- emit WebBrowserFinishedBrowsing();
-}
+ emit WebBrowserClosed(exit_reason, last_url);
+
+#else
+
+ // Utilize the same fallback as the default web browser applet.
+ emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
#endif
+}
void GMainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
@@ -530,13 +600,14 @@ void GMainWindow::InitializeWidgets() {
dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
dock_status_button->setFocusPolicy(Qt::NoFocus);
connect(dock_status_button, &QPushButton::clicked, [&] {
- Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
- dock_status_button->setChecked(Settings::values.use_docked_mode);
- OnDockedModeChanged(!Settings::values.use_docked_mode, Settings::values.use_docked_mode);
+ Settings::values.use_docked_mode.SetValue(!Settings::values.use_docked_mode.GetValue());
+ dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
+ OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(),
+ Settings::values.use_docked_mode.GetValue());
});
dock_status_button->setText(tr("DOCK"));
dock_status_button->setCheckable(true);
- dock_status_button->setChecked(Settings::values.use_docked_mode);
+ dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
statusBar()->insertPermanentWidget(0, dock_status_button);
// Setup ASync button
@@ -547,11 +618,10 @@ void GMainWindow::InitializeWidgets() {
if (emulation_running) {
return;
}
- bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
- Settings::values.use_multi_core.GetValue();
- Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async);
+ Settings::values.use_asynchronous_gpu_emulation.SetValue(
+ !Settings::values.use_asynchronous_gpu_emulation.GetValue());
async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
});
async_status_button->setText(tr("ASYNC"));
async_status_button->setCheckable(true);
@@ -566,16 +636,13 @@ void GMainWindow::InitializeWidgets() {
return;
}
Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue());
- bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
- Settings::values.use_multi_core.GetValue();
- Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async);
- async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
});
multicore_status_button->setText(tr("MULTICORE"));
multicore_status_button->setCheckable(true);
multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
+
statusBar()->insertPermanentWidget(0, multicore_status_button);
statusBar()->insertPermanentWidget(0, async_status_button);
@@ -589,11 +656,6 @@ void GMainWindow::InitializeWidgets() {
});
renderer_status_button->toggle();
-#ifndef HAS_VULKAN
- renderer_status_button->setChecked(false);
- renderer_status_button->setCheckable(false);
- renderer_status_button->setDisabled(true);
-#else
renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
Settings::RendererBackend::Vulkan);
connect(renderer_status_button, &QPushButton::clicked, [this] {
@@ -606,9 +668,8 @@ void GMainWindow::InitializeWidgets() {
Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL);
}
- Settings::Apply();
+ Settings::Apply(Core::System::GetInstance());
});
-#endif // HAS_VULKAN
statusBar()->insertPermanentWidget(0, renderer_status_button);
statusBar()->setVisible(true);
@@ -628,6 +689,11 @@ void GMainWindow::InitializeDebugWidgets() {
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
waitTreeWidget->hide();
debug_menu->addAction(waitTreeWidget->toggleViewAction());
+
+ controller_dialog = new ControllerDialog(this);
+ controller_dialog->hide();
+ debug_menu->addAction(controller_dialog->toggleViewAction());
+
connect(this, &GMainWindow::EmulationStarting, waitTreeWidget,
&WaitTreeWidget::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, waitTreeWidget,
@@ -644,7 +710,7 @@ void GMainWindow::InitializeRecentFileMenuActions() {
}
ui.menu_recent_files->addSeparator();
QAction* action_clear_recent_files = new QAction(this);
- action_clear_recent_files->setText(tr("Clear Recent Files"));
+ action_clear_recent_files->setText(tr("&Clear Recent Files"));
connect(action_clear_recent_files, &QAction::triggered, this, [this] {
UISettings::values.recent_files.clear();
UpdateRecentFiles();
@@ -775,14 +841,25 @@ void GMainWindow::InitializeHotkeys() {
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Change Docked Mode"), this),
&QShortcut::activated, this, [&] {
- Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
- OnDockedModeChanged(!Settings::values.use_docked_mode,
- Settings::values.use_docked_mode);
- dock_status_button->setChecked(Settings::values.use_docked_mode);
+ Settings::values.use_docked_mode.SetValue(
+ !Settings::values.use_docked_mode.GetValue());
+ OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(),
+ Settings::values.use_docked_mode.GetValue());
+ dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this),
&QShortcut::activated, this,
[] { Settings::values.audio_muted = !Settings::values.audio_muted; });
+
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Mouse Panning"), this),
+ &QShortcut::activated, this, [&] {
+ Settings::values.mouse_panning = !Settings::values.mouse_panning;
+ if (UISettings::values.hide_mouse || Settings::values.mouse_panning) {
+ mouse_hide_timer.start();
+ render_window->installEventFilter(render_window);
+ render_window->setAttribute(Qt::WA_Hover, true);
+ }
+ });
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -817,7 +894,7 @@ void GMainWindow::RestoreUIState() {
OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked());
ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar);
- game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked());
+ game_list->SetFilterVisible(ui.action_Show_Filter_Bar->isChecked());
ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar);
statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked());
@@ -905,7 +982,10 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::OnDisplayTitleBars);
connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
- connect(ui.action_Reset_Window_Size, &QAction::triggered, this, &GMainWindow::ResetWindowSize);
+ connect(ui.action_Reset_Window_Size_720, &QAction::triggered, this,
+ &GMainWindow::ResetWindowSize720);
+ connect(ui.action_Reset_Window_Size_1080, &QAction::triggered, this,
+ &GMainWindow::ResetWindowSize1080);
// Fullscreen
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
@@ -953,7 +1033,7 @@ void GMainWindow::AllowOSSleep() {
#endif
}
-bool GMainWindow::LoadROM(const QString& filename) {
+bool GMainWindow::LoadROM(const QString& filename, std::size_t program_index) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr)
ShutdownGame();
@@ -966,18 +1046,17 @@ bool GMainWindow::LoadROM(const QString& filename) {
system.SetFilesystem(vfs);
system.SetAppletFrontendSet({
- nullptr, // Parental Controls
- std::make_unique<QtErrorDisplay>(*this), //
- nullptr, // Photo Viewer
- std::make_unique<QtProfileSelector>(*this), //
- std::make_unique<QtSoftwareKeyboard>(*this), //
- std::make_unique<QtWebBrowser>(*this), //
- nullptr, // E-Commerce
+ std::make_unique<QtControllerSelector>(*this), // Controller Selector
+ std::make_unique<QtErrorDisplay>(*this), // Error Display
+ nullptr, // Parental Controls
+ nullptr, // Photo Viewer
+ std::make_unique<QtProfileSelector>(*this), // Profile Selector
+ std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard
+ std::make_unique<QtWebBrowser>(*this), // Web Browser
});
- system.RegisterHostThread();
-
- const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
+ const Core::System::ResultStatus result{
+ system.Load(*render_window, filename.toStdString(), program_index)};
const auto drd_callout =
(UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0;
@@ -1017,20 +1096,24 @@ bool GMainWindow::LoadROM(const QString& filename) {
break;
default:
- if (static_cast<u32>(result) >
- static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) {
+ if (result > Core::System::ResultStatus::ErrorLoader) {
const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader);
const u16 error_id = static_cast<u16>(result) - loader_id;
const std::string error_code = fmt::format("({:04X}-{:04X})", loader_id, error_id);
LOG_CRITICAL(Frontend, "Failed to load ROM! {}", error_code);
- QMessageBox::critical(
- this,
- tr("Error while loading ROM! ").append(QString::fromStdString(error_code)),
- QString::fromStdString(fmt::format(
- "{}<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the "
- "yuzu quickstart guide</a> to redump your files.<br>You can refer "
- "to the yuzu wiki</a> or the yuzu Discord</a> for help.",
- static_cast<Loader::ResultStatus>(error_id))));
+
+ const auto title =
+ tr("Error while loading ROM! %1", "%1 signifies a numeric error code.")
+ .arg(QString::fromStdString(error_code));
+ const auto description =
+ tr("%1<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the "
+ "yuzu quickstart guide</a> to redump your files.<br>You can refer "
+ "to the yuzu wiki</a> or the yuzu Discord</a> for help.",
+ "%1 signifies an error string.")
+ .arg(QString::fromStdString(
+ GetResultStatusString(static_cast<Loader::ResultStatus>(error_id))));
+
+ QMessageBox::critical(this, title, description);
} else {
QMessageBox::critical(
this, tr("Error while loading ROM!"),
@@ -1059,26 +1142,37 @@ void GMainWindow::SelectAndSetCurrentUser() {
Settings::values.current_user = dialog.GetIndex();
}
-void GMainWindow::BootGame(const QString& filename) {
+void GMainWindow::BootGame(const QString& filename, std::size_t program_index) {
LOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list
u64 title_id{0};
+ last_filename_booted = filename;
+
+ auto& system = Core::System::GetInstance();
const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData());
- const auto loader = Loader::GetLoader(v_file);
+ const auto loader = Loader::GetLoader(system, v_file, program_index);
+
if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) {
// Load per game settings
- Config per_game_config(fmt::format("{:016X}.ini", title_id), false);
+ Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig);
}
+ ConfigureVibration::SetAllVibrationDevices();
+
+ // Save configurations
+ UpdateUISettings();
+ game_list->SaveInterfaceLayout();
+ config->Save();
+
Settings::LogSettings();
if (UISettings::values.select_user_on_boot) {
SelectAndSetCurrentUser();
}
- if (!LoadROM(filename))
+ if (!LoadROM(filename, program_index))
return;
// Create and start the emulation thread
@@ -1086,7 +1180,12 @@ void GMainWindow::BootGame(const QString& filename) {
emit EmulationStarting(emu_thread.get());
emu_thread->start();
+ // Register an ExecuteProgram callback such that Core can execute a sub-program
+ system.RegisterExecuteProgramCallback(
+ [this](std::size_t program_index) { render_window->ExecuteProgram(program_index); });
+
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
+ connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
// before the CPU continues
connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget,
@@ -1108,17 +1207,21 @@ void GMainWindow::BootGame(const QString& filename) {
multicore_status_button->setDisabled(true);
renderer_status_button->setDisabled(true);
- if (UISettings::values.hide_mouse) {
+ if (UISettings::values.hide_mouse || Settings::values.mouse_panning) {
mouse_hide_timer.start();
- setMouseTracking(true);
- ui.centralwidget->setMouseTracking(true);
+ render_window->installEventFilter(render_window);
+ render_window->setAttribute(Qt::WA_Hover, true);
}
std::string title_name;
std::string title_version;
- const auto res = Core::System::GetInstance().GetGameName(title_name);
+ const auto res = system.GetGameName(title_name);
- const auto metadata = FileSys::PatchManager(title_id).GetControlMetadata();
+ const auto metadata = [&system, title_id] {
+ const FileSys::PatchManager pm(title_id, system.GetFileSystemController(),
+ system.GetContentProvider());
+ return pm.GetControlMetadata();
+ }();
if (metadata.first != nullptr) {
title_version = metadata.first->GetVersionString();
title_name = metadata.first->GetApplicationName();
@@ -1129,7 +1232,7 @@ void GMainWindow::BootGame(const QString& filename) {
LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version);
UpdateWindowTitle(title_name, title_version);
- loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
+ loading_screen->Prepare(system.GetAppLoader());
loading_screen->show();
emulation_running = true;
@@ -1177,14 +1280,15 @@ void GMainWindow::ShutdownGame() {
render_window->hide();
loading_screen->hide();
loading_screen->Clear();
- if (game_list->isEmpty())
+ if (game_list->IsEmpty()) {
game_list_placeholder->show();
- else
+ } else {
game_list->show();
- game_list->setFilterFocus();
+ }
+ game_list->SetFilterFocus();
- setMouseTracking(false);
- ui.centralwidget->setMouseTracking(false);
+ render_window->removeEventFilter(render_window);
+ render_window->setAttribute(Qt::WA_Hover, false);
UpdateWindowTitle();
@@ -1196,9 +1300,7 @@ void GMainWindow::ShutdownGame() {
emu_frametime_label->setVisible(false);
async_status_button->setEnabled(true);
multicore_status_button->setEnabled(true);
-#ifdef HAS_VULKAN
renderer_status_button->setEnabled(true);
-#endif
emulation_running = false;
@@ -1247,16 +1349,18 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
const std::string& game_path) {
std::string path;
QString open_target;
+ auto& system = Core::System::GetInstance();
- const auto [user_save_size, device_save_size] = [this, &program_id, &game_path] {
- FileSys::PatchManager pm{program_id};
+ const auto [user_save_size, device_save_size] = [this, &game_path, &program_id, &system] {
+ const FileSys::PatchManager pm{program_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
const auto control = pm.GetControlMetadata().first;
if (control != nullptr) {
return std::make_pair(control->GetDefaultNormalSaveSize(),
control->GetDeviceSaveDataSize());
} else {
const auto file = Core::GetGameFileFromPath(vfs, game_path);
- const auto loader = Loader::GetLoader(file);
+ const auto loader = Loader::GetLoader(system, file);
FileSys::NACP nacp{};
loader->ReadControlData(nacp);
@@ -1298,12 +1402,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
const auto user_id = manager.GetUser(static_cast<std::size_t>(index));
ASSERT(user_id);
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(
- FileSys::SaveDataSpaceId::NandUser,
+ system, FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData, program_id, user_id->uuid, 0);
} else {
// Device save data
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(
- FileSys::SaveDataSpaceId::NandUser,
+ system, FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData, program_id, {}, 0);
}
@@ -1483,7 +1587,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)
FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
for (const auto& entry : dlc_entries) {
- if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) {
+ if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
const auto res =
fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) ||
fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id);
@@ -1558,7 +1662,8 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id) {
const QString config_dir =
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir));
const QString custom_config_file_path =
- config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id));
+ config_dir + QStringLiteral("custom") + QDir::separator() +
+ QString::fromStdString(fmt::format("{:016X}.ini", program_id));
if (!QFile::exists(custom_config_file_path)) {
QMessageBox::warning(this, tr("Error Removing Custom Configuration"),
@@ -1582,7 +1687,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
"cancelled the operation."));
};
- const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
+ auto& system = Core::System::GetInstance();
+ const auto loader = Loader::GetLoader(system, vfs->OpenFile(game_path, FileSys::Mode::Read));
if (loader == nullptr) {
failed();
return;
@@ -1594,7 +1700,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- const auto& installed = Core::System::GetInstance().GetContentProvider();
+ const auto& installed = system.GetContentProvider();
const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
if (!romfs_title_id) {
@@ -1609,7 +1715,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
if (*romfs_title_id == program_id) {
const u64 ivfc_offset = loader->ReadRomFSIVFCOffset();
- FileSys::PatchManager pm{program_id};
+ const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed};
romfs = pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program);
} else {
romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
@@ -1726,7 +1832,8 @@ void GMainWindow::OnGameListShowList(bool show) {
void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
u64 title_id{};
const auto v_file = Core::GetGameFileFromPath(vfs, file);
- const auto loader = Loader::GetLoader(v_file);
+ const auto loader = Loader::GetLoader(Core::System::GetInstance(), v_file);
+
if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
QMessageBox::information(this, tr("Properties"),
tr("The game properties could not be loaded."));
@@ -2047,17 +2154,19 @@ void GMainWindow::OnStartGame() {
emu_thread->SetRunning(true);
+ qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters");
qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>(
"Core::Frontend::SoftwareKeyboardParameters");
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>");
qRegisterMetaType<std::string_view>("std::string_view");
+ qRegisterMetaType<Service::AM::Applets::WebExitReason>("Service::AM::Applets::WebExitReason");
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
ui.action_Start->setEnabled(false);
- ui.action_Start->setText(tr("Continue"));
+ ui.action_Start->setText(tr("&Continue"));
ui.action_Pause->setEnabled(true);
ui.action_Stop->setEnabled(true);
@@ -2082,14 +2191,14 @@ void GMainWindow::OnPauseGame() {
}
void GMainWindow::OnStopGame() {
- Core::System& system{Core::System::GetInstance()};
+ auto& system{Core::System::GetInstance()};
if (system.GetExitLock() && !ConfirmForceLockedExit()) {
return;
}
ShutdownGame();
- Settings::RestoreGlobalState();
+ Settings::RestoreGlobalState(system.IsPoweredOn());
UpdateStatusButtons();
}
@@ -2097,6 +2206,11 @@ void GMainWindow::OnLoadComplete() {
loading_screen->OnLoadComplete();
}
+void GMainWindow::OnExecuteProgram(std::size_t program_index) {
+ ShutdownGame();
+ BootGame(last_filename_booted, program_index);
+}
+
void GMainWindow::ErrorDisplayDisplayError(QString body) {
QMessageBox::critical(this, tr("Error Display"), body);
emit ErrorDisplayFinished();
@@ -2196,7 +2310,7 @@ void GMainWindow::ToggleWindowMode() {
}
}
-void GMainWindow::ResetWindowSize() {
+void GMainWindow::ResetWindowSize720() {
const auto aspect_ratio = Layout::EmulationAspectRatio(
static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()),
static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width);
@@ -2210,6 +2324,20 @@ void GMainWindow::ResetWindowSize() {
}
}
+void GMainWindow::ResetWindowSize1080() {
+ const auto aspect_ratio = Layout::EmulationAspectRatio(
+ static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()),
+ static_cast<float>(Layout::ScreenDocked::Height) / Layout::ScreenDocked::Width);
+ if (!ui.action_Single_Window_Mode->isChecked()) {
+ render_window->resize(Layout::ScreenDocked::Height / aspect_ratio,
+ Layout::ScreenDocked::Height);
+ } else {
+ resize(Layout::ScreenDocked::Height / aspect_ratio,
+ Layout::ScreenDocked::Height + menuBar()->height() +
+ (ui.action_Show_Status_Bar->isChecked() ? statusBar()->height() : 0));
+ }
+}
+
void GMainWindow::OnConfigure() {
const auto old_theme = UISettings::values.theme;
const bool old_discord_presence = UISettings::values.enable_discord_presence;
@@ -2224,6 +2352,7 @@ void GMainWindow::OnConfigure() {
}
configure_dialog.ApplyConfiguration();
+ controller_dialog->refreshConfiguration();
InitializeHotkeys();
if (UISettings::values.theme != old_theme) {
UpdateUITheme();
@@ -2240,13 +2369,13 @@ void GMainWindow::OnConfigure() {
config->Save();
- if (UISettings::values.hide_mouse && emulation_running) {
- setMouseTracking(true);
- ui.centralwidget->setMouseTracking(true);
+ if ((UISettings::values.hide_mouse || Settings::values.mouse_panning) && emulation_running) {
+ render_window->installEventFilter(render_window);
+ render_window->setAttribute(Qt::WA_Hover, true);
mouse_hide_timer.start();
} else {
- setMouseTracking(false);
- ui.centralwidget->setMouseTracking(false);
+ render_window->removeEventFilter(render_window);
+ render_window->setAttribute(Qt::WA_Hover, false);
}
UpdateStatusButtons();
@@ -2259,10 +2388,11 @@ void GMainWindow::OnConfigurePerGame() {
void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) {
const auto v_file = Core::GetGameFileFromPath(vfs, file_name);
+ const auto& system = Core::System::GetInstance();
ConfigurePerGame dialog(this, title_id);
dialog.LoadFromFile(v_file);
- auto result = dialog.exec();
+ const auto result = dialog.exec();
if (result == QDialog::Accepted) {
dialog.ApplyConfiguration();
@@ -2272,13 +2402,14 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
}
// Do not cause the global config to write local settings into the config file
- Settings::RestoreGlobalState();
+ const bool is_powered_on = system.IsPoweredOn();
+ Settings::RestoreGlobalState(is_powered_on);
- if (!Core::System::GetInstance().IsPoweredOn()) {
+ if (!is_powered_on) {
config->Save();
}
} else {
- Settings::RestoreGlobalState();
+ Settings::RestoreGlobalState(system.IsPoweredOn());
}
}
@@ -2338,11 +2469,11 @@ void GMainWindow::OnAbout() {
}
void GMainWindow::OnToggleFilterBar() {
- game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked());
+ game_list->SetFilterVisible(ui.action_Show_Filter_Bar->isChecked());
if (ui.action_Show_Filter_Bar->isChecked()) {
- game_list->setFilterFocus();
+ game_list->SetFilterFocus();
} else {
- game_list->clearFilter();
+ game_list->ClearFilter();
}
}
@@ -2359,6 +2490,11 @@ void GMainWindow::OnCaptureScreenshot() {
.arg(title_id, 16, 16, QLatin1Char{'0'})
.arg(date);
+ if (!Common::FS::CreateDir(screenshot_path.toStdString())) {
+ OnStartGame();
+ return;
+ }
+
#ifdef _WIN32
if (UISettings::values.enable_screenshot_save_as) {
filename = QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), filename,
@@ -2373,6 +2509,29 @@ void GMainWindow::OnCaptureScreenshot() {
OnStartGame();
}
+// TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant
+void GMainWindow::MigrateConfigFiles() {
+ const std::string& config_dir_str = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir);
+ const QDir config_dir = QDir(QString::fromStdString(config_dir_str));
+ const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini")));
+
+ Common::FS::CreateFullPath(fmt::format("{}custom" DIR_SEP, config_dir_str));
+ for (QStringList::const_iterator it = config_dir_list.constBegin();
+ it != config_dir_list.constEnd(); ++it) {
+ const auto filename = it->toStdString();
+ if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) {
+ continue;
+ }
+ const auto origin = fmt::format("{}{}", config_dir_str, filename);
+ const auto destination = fmt::format("{}custom" DIR_SEP "{}", config_dir_str, filename);
+ LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);
+ if (!Common::FS::Rename(origin, destination)) {
+ // Delete the old config file if one already exists in the new location.
+ Common::FS::Delete(origin);
+ }
+ }
+}
+
void GMainWindow::UpdateWindowTitle(const std::string& title_name,
const std::string& title_version) {
const auto full_name = std::string(Common::g_build_fullname);
@@ -2430,40 +2589,53 @@ void GMainWindow::UpdateStatusBar() {
}
void GMainWindow::UpdateStatusButtons() {
- dock_status_button->setChecked(Settings::values.use_docked_mode);
+ dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
- Settings::values.use_asynchronous_gpu_emulation.SetValue(
- Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
- Settings::values.use_multi_core.GetValue());
async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
-#ifdef HAS_VULKAN
renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
Settings::RendererBackend::Vulkan);
+}
+
+void GMainWindow::UpdateUISettings() {
+ if (!ui.action_Fullscreen->isChecked()) {
+ UISettings::values.geometry = saveGeometry();
+ UISettings::values.renderwindow_geometry = render_window->saveGeometry();
+ }
+ UISettings::values.state = saveState();
+#if MICROPROFILE_ENABLED
+ UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry();
+ UISettings::values.microprofile_visible = microProfileDialog->isVisible();
#endif
+ UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked();
+ UISettings::values.fullscreen = ui.action_Fullscreen->isChecked();
+ UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked();
+ UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked();
+ UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked();
+ UISettings::values.first_start = false;
}
void GMainWindow::HideMouseCursor() {
- if (emu_thread == nullptr || UISettings::values.hide_mouse == false) {
+ if (emu_thread == nullptr ||
+ (!UISettings::values.hide_mouse && !Settings::values.mouse_panning)) {
mouse_hide_timer.stop();
ShowMouseCursor();
return;
}
- setCursor(QCursor(Qt::BlankCursor));
+ render_window->setCursor(QCursor(Qt::BlankCursor));
}
void GMainWindow::ShowMouseCursor() {
- unsetCursor();
- if (emu_thread != nullptr && UISettings::values.hide_mouse) {
+ render_window->unsetCursor();
+ if (emu_thread != nullptr &&
+ (UISettings::values.hide_mouse || Settings::values.mouse_panning)) {
mouse_hide_timer.start();
}
}
-void GMainWindow::mouseMoveEvent(QMouseEvent* event) {
- ShowMouseCursor();
-}
-
-void GMainWindow::mousePressEvent(QMouseEvent* event) {
- ShowMouseCursor();
+void GMainWindow::OnMouseActivity() {
+ if (!Settings::values.mouse_panning) {
+ ShowMouseCursor();
+ }
}
void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
@@ -2526,7 +2698,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
if (emu_thread) {
ShutdownGame();
- Settings::RestoreGlobalState();
+ Settings::RestoreGlobalState(Core::System::GetInstance().IsPoweredOn());
UpdateStatusButtons();
}
} else {
@@ -2569,8 +2741,10 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
const auto function = [this, &keys, &pdm] {
keys.PopulateFromPartitionData(pdm);
- Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
- keys.DeriveETicket(pdm);
+
+ auto& system = Core::System::GetInstance();
+ system.GetFileSystemController().CreateFactories(*vfs);
+ keys.DeriveETicket(pdm, system.GetContentProvider());
};
QString errors;
@@ -2596,7 +2770,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
.arg(errors));
}
- QProgressDialog prog;
+ QProgressDialog prog(this);
prog.setRange(0, 0);
prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your "
"system's performance."));
@@ -2627,7 +2801,7 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
[&program_id, &installed](const FileSys::ContentProviderEntry& entry) {
- return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
+ return FileSys::GetBaseTitleID(entry.title_id) == program_id &&
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
});
@@ -2673,22 +2847,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
return;
}
- if (!ui.action_Fullscreen->isChecked()) {
- UISettings::values.geometry = saveGeometry();
- UISettings::values.renderwindow_geometry = render_window->saveGeometry();
- }
- UISettings::values.state = saveState();
-#if MICROPROFILE_ENABLED
- UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry();
- UISettings::values.microprofile_visible = microProfileDialog->isVisible();
-#endif
- UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked();
- UISettings::values.fullscreen = ui.action_Fullscreen->isChecked();
- UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked();
- UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked();
- UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked();
- UISettings::values.first_start = false;
-
+ UpdateUISettings();
game_list->SaveInterfaceLayout();
hotkey_registry.SaveHotkeys();
@@ -2696,7 +2855,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
if (emu_thread != nullptr) {
ShutdownGame();
- Settings::RestoreGlobalState();
+ Settings::RestoreGlobalState(Core::System::GetInstance().IsPoweredOn());
UpdateStatusButtons();
}
@@ -2793,7 +2952,7 @@ void GMainWindow::filterBarSetChecked(bool state) {
}
void GMainWindow::UpdateUITheme() {
- const QString default_icons = QStringLiteral(":/icons/default");
+ const QString default_icons = QStringLiteral("default");
const QString& current_theme = UISettings::values.theme;
const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);
QStringList theme_paths(default_theme_paths);
@@ -2809,7 +2968,6 @@ void GMainWindow::UpdateUITheme() {
qApp->setStyleSheet({});
setStyleSheet({});
}
- theme_paths.append(default_icons);
QIcon::setThemeName(default_icons);
} else {
const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
@@ -2821,10 +2979,7 @@ void GMainWindow::UpdateUITheme() {
} else {
LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
}
-
- const QString theme_name = QStringLiteral(":/icons/") + current_theme;
- theme_paths.append({default_icons, theme_name});
- QIcon::setThemeName(theme_name);
+ QIcon::setThemeName(current_theme);
}
QIcon::setThemeSearchPaths(theme_paths);
@@ -2864,7 +3019,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) {
UpdateWindowTitle();
if (emulation_running)
- ui.action_Start->setText(tr("Continue"));
+ ui.action_Start->setText(tr("&Continue"));
}
void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
@@ -2889,6 +3044,8 @@ int main(int argc, char* argv[]) {
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
+ Common::ConfigureNvidiaEnvironmentFlags();
+
// Init settings params
QCoreApplication::setOrganizationName(QStringLiteral("yuzu team"));
QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 0ce66a1ca..04d37d4ae 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -27,6 +27,7 @@ class GRenderWindow;
class LoadingScreen;
class MicroProfileDialog;
class ProfilerWidget;
+class ControllerDialog;
class QLabel;
class QPushButton;
class QProgressDialog;
@@ -37,6 +38,7 @@ enum class InstalledEntryType;
class GameListPlaceholder;
namespace Core::Frontend {
+struct ControllerParameters;
struct SoftwareKeyboardParameters;
} // namespace Core::Frontend
@@ -54,6 +56,10 @@ namespace InputCommon {
class InputSubsystem;
}
+namespace Service::AM::Applets {
+enum class WebExitReason : u32;
+}
+
enum class EmulatedDirectoryTarget {
NAND,
SDMC,
@@ -116,22 +122,29 @@ signals:
void UpdateInstallProgress();
+ void ControllerSelectorReconfigureFinished();
+
void ErrorDisplayFinished();
void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
+
void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
void SoftwareKeyboardFinishedCheckDialog();
- void WebBrowserUnpackRomFS();
- void WebBrowserFinishedBrowsing();
+ void WebBrowserExtractOfflineRomFS();
+ void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url);
public slots:
void OnLoadComplete();
+ void OnExecuteProgram(std::size_t program_index);
+ void ControllerSelectorReconfigureControllers(
+ const Core::Frontend::ControllerParameters& parameters);
void ErrorDisplayDisplayError(QString body);
void ProfileSelectorSelectProfile();
void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message);
- void WebBrowserOpenPage(std::string_view filename, std::string_view arguments);
+ void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args,
+ bool is_local);
void OnAppFocusStateChanged(Qt::ApplicationState state);
private:
@@ -148,8 +161,8 @@ private:
void PreventOSSleep();
void AllowOSSleep();
- bool LoadROM(const QString& filename);
- void BootGame(const QString& filename);
+ bool LoadROM(const QString& filename, std::size_t program_index);
+ void BootGame(const QString& filename, std::size_t program_index = 0);
void ShutdownGame();
void ShowTelemetryCallout();
@@ -230,11 +243,13 @@ private slots:
void ShowFullscreen();
void HideFullscreen();
void ToggleWindowMode();
- void ResetWindowSize();
+ void ResetWindowSize720();
+ void ResetWindowSize1080();
void OnCaptureScreenshot();
void OnCoreError(Core::System::ResultStatus, std::string);
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
void OnLanguageChanged(const QString& locale);
+ void OnMouseActivity();
private:
void RemoveBaseContent(u64 program_id, const QString& entry_type);
@@ -245,10 +260,12 @@ private:
std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
InstallResult InstallNSPXCI(const QString& filename);
InstallResult InstallNCA(const QString& filename);
+ void MigrateConfigFiles();
void UpdateWindowTitle(const std::string& title_name = {},
const std::string& title_version = {});
void UpdateStatusBar();
void UpdateStatusButtons();
+ void UpdateUISettings();
void HideMouseCursor();
void ShowMouseCursor();
void OpenURL(const QUrl& url);
@@ -297,6 +314,7 @@ private:
ProfilerWidget* profilerWidget;
MicroProfileDialog* microProfileDialog;
WaitTreeWidget* waitTreeWidget;
+ ControllerDialog* controller_dialog;
QAction* actions_recent_files[max_recent_files_item];
@@ -310,10 +328,14 @@ private:
// Install progress dialog
QProgressDialog* install_progress;
+ // Last game booted, used for multi-process apps
+ QString last_filename_booted;
+
+ // Disables the web applet for the rest of the emulated session
+ bool disable_web_applet{};
+
protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
- void mouseMoveEvent(QMouseEvent* event) override;
- void mousePressEvent(QMouseEvent* event) override;
};
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 87ea985d8..048870687 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -14,8 +14,8 @@
<string>yuzu</string>
</property>
<property name="windowIcon">
- <iconset>
- <normaloff>../dist/yuzu.ico</normaloff>../dist/yuzu.ico</iconset>
+ <iconset resource="yuzu.qrc">
+ <normaloff>:/img/yuzu.ico</normaloff>:/img/yuzu.ico</iconset>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
@@ -25,16 +25,7 @@
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
+ <property name="margin">
<number>0</number>
</property>
</layout>
@@ -45,7 +36,7 @@
<x>0</x>
<y>0</y>
<width>1280</width>
- <height>21</height>
+ <height>26</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
@@ -54,7 +45,7 @@
</property>
<widget class="QMenu" name="menu_recent_files">
<property name="title">
- <string>Recent Files</string>
+ <string>&amp;Recent Files</string>
</property>
</widget>
<addaction name="action_Install_File_NAND"/>
@@ -89,7 +80,7 @@
</property>
<widget class="QMenu" name="menu_View_Debugging">
<property name="title">
- <string>Debugging</string>
+ <string>&amp;Debugging</string>
</property>
</widget>
<addaction name="action_Fullscreen"/>
@@ -97,13 +88,14 @@
<addaction name="action_Display_Dock_Widget_Headers"/>
<addaction name="action_Show_Filter_Bar"/>
<addaction name="action_Show_Status_Bar"/>
- <addaction name="action_Reset_Window_Size"/>
+ <addaction name="action_Reset_Window_Size_720"/>
+ <addaction name="action_Reset_Window_Size_1080"/>
<addaction name="separator"/>
<addaction name="menu_View_Debugging"/>
</widget>
<widget class="QMenu" name="menu_Tools">
<property name="title">
- <string>Tools</string>
+ <string>&amp;Tools</string>
</property>
<addaction name="action_Rederive"/>
<addaction name="separator"/>
@@ -131,17 +123,17 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Install Files to NAND...</string>
+ <string>&amp;Install Files to NAND...</string>
</property>
</action>
<action name="action_Load_File">
<property name="text">
- <string>Load File...</string>
+ <string>L&amp;oad File...</string>
</property>
</action>
<action name="action_Load_Folder">
<property name="text">
- <string>Load Folder...</string>
+ <string>Load &amp;Folder...</string>
</property>
</action>
<action name="action_Exit">
@@ -175,12 +167,12 @@
</action>
<action name="action_Rederive">
<property name="text">
- <string>Reinitialize keys...</string>
+ <string>&amp;Reinitialize keys...</string>
</property>
</action>
<action name="action_About">
<property name="text">
- <string>About yuzu</string>
+ <string>&amp;About yuzu</string>
</property>
</action>
<action name="action_Single_Window_Mode">
@@ -188,12 +180,12 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Single Window Mode</string>
+ <string>Single &amp;Window Mode</string>
</property>
</action>
<action name="action_Configure">
<property name="text">
- <string>Configure...</string>
+ <string>Con&amp;figure...</string>
</property>
</action>
<action name="action_Display_Dock_Widget_Headers">
@@ -201,7 +193,7 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Display Dock Widget Headers</string>
+ <string>Display D&amp;ock Widget Headers</string>
</property>
</action>
<action name="action_Show_Filter_Bar">
@@ -209,7 +201,7 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Show Filter Bar</string>
+ <string>Show &amp;Filter Bar</string>
</property>
</action>
<action name="action_Show_Status_Bar">
@@ -217,12 +209,26 @@
<bool>true</bool>
</property>
<property name="text">
+ <string>Show &amp;Status Bar</string>
+ </property>
+ <property name="iconText">
<string>Show Status Bar</string>
</property>
</action>
- <action name="action_Reset_Window_Size">
+ <action name="action_Reset_Window_Size_720">
+ <property name="text">
+ <string>Reset Window Size to &amp;720p</string>
+ </property>
+ <property name="iconText">
+ <string>Reset Window Size to 720p</string>
+ </property>
+ </action>
+ <action name="action_Reset_Window_Size_1080">
<property name="text">
- <string>Reset Window Size</string>
+ <string>Reset Window Size to &amp;1080p</string>
+ </property>
+ <property name="iconText">
+ <string>Reset Window Size to 1080p</string>
</property>
</action>
<action name="action_Fullscreen">
@@ -230,7 +236,7 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Fullscreen</string>
+ <string>F&amp;ullscreen</string>
</property>
</action>
<action name="action_Restart">
@@ -238,7 +244,7 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Restart</string>
+ <string>&amp;Restart</string>
</property>
</action>
<action name="action_Load_Amiibo">
@@ -246,7 +252,7 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Load Amiibo...</string>
+ <string>Load &amp;Amiibo...</string>
</property>
</action>
<action name="action_Report_Compatibility">
@@ -254,7 +260,7 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Report Compatibility</string>
+ <string>&amp;Report Compatibility</string>
</property>
<property name="visible">
<bool>false</bool>
@@ -262,22 +268,22 @@
</action>
<action name="action_Open_Mods_Page">
<property name="text">
- <string>Open Mods Page</string>
+ <string>Open &amp;Mods Page</string>
</property>
</action>
<action name="action_Open_Quickstart_Guide">
<property name="text">
- <string>Open Quickstart Guide</string>
+ <string>Open &amp;Quickstart Guide</string>
</property>
</action>
<action name="action_Open_FAQ">
<property name="text">
- <string>FAQ</string>
+ <string>&amp;FAQ</string>
</property>
</action>
<action name="action_Open_yuzu_Folder">
<property name="text">
- <string>Open yuzu Folder</string>
+ <string>Open &amp;yuzu Folder</string>
</property>
</action>
<action name="action_Capture_Screenshot">
@@ -285,7 +291,7 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Capture Screenshot</string>
+ <string>&amp;Capture Screenshot</string>
</property>
</action>
<action name="action_Configure_Current_Game">
@@ -293,10 +299,12 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Configure Current Game..</string>
+ <string>Configure C&amp;urrent Game...</string>
</property>
</action>
</widget>
- <resources/>
+ <resources>
+ <include location="yuzu.qrc"/>
+ </resources>
<connections/>
</ui>
diff --git a/src/yuzu/util/url_request_interceptor.cpp b/src/yuzu/util/url_request_interceptor.cpp
new file mode 100644
index 000000000..b637e771e
--- /dev/null
+++ b/src/yuzu/util/url_request_interceptor.cpp
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+#include "yuzu/util/url_request_interceptor.h"
+
+UrlRequestInterceptor::UrlRequestInterceptor(QObject* p) : QWebEngineUrlRequestInterceptor(p) {}
+
+UrlRequestInterceptor::~UrlRequestInterceptor() = default;
+
+void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {
+ const auto resource_type = info.resourceType();
+
+ switch (resource_type) {
+ case QWebEngineUrlRequestInfo::ResourceTypeMainFrame:
+ requested_url = info.requestUrl();
+ emit FrameChanged();
+ break;
+ case QWebEngineUrlRequestInfo::ResourceTypeSubFrame:
+ case QWebEngineUrlRequestInfo::ResourceTypeXhr:
+ emit FrameChanged();
+ break;
+ default:
+ break;
+ }
+}
+
+QUrl UrlRequestInterceptor::GetRequestedURL() const {
+ return requested_url;
+}
+
+#endif
diff --git a/src/yuzu/util/url_request_interceptor.h b/src/yuzu/util/url_request_interceptor.h
new file mode 100644
index 000000000..8a7f7499f
--- /dev/null
+++ b/src/yuzu/util/url_request_interceptor.h
@@ -0,0 +1,30 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#ifdef YUZU_USE_QT_WEB_ENGINE
+
+#include <QObject>
+#include <QWebEngineUrlRequestInterceptor>
+
+class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor {
+ Q_OBJECT
+
+public:
+ explicit UrlRequestInterceptor(QObject* p = nullptr);
+ ~UrlRequestInterceptor() override;
+
+ void interceptRequest(QWebEngineUrlRequestInfo& info) override;
+
+ QUrl GetRequestedURL() const;
+
+signals:
+ void FrameChanged();
+
+private:
+ QUrl requested_url;
+};
+
+#endif
diff --git a/src/yuzu/yuzu.qrc b/src/yuzu/yuzu.qrc
new file mode 100644
index 000000000..5733cac98
--- /dev/null
+++ b/src/yuzu/yuzu.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/img">
+ <file alias="yuzu.ico">../../dist/yuzu.ico</file>
+ </qresource>
+</RCC>
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index a15719a0f..8461f8896 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -1,29 +1,30 @@
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
+function(create_resource file output filename)
+ # Read hex data from file
+ file(READ ${file} filedata HEX)
+ # Convert hex data for C compatibility
+ string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata})
+ # Write data to output file
+ set(RESOURCES_DIR "${PROJECT_BINARY_DIR}/dist" PARENT_SCOPE)
+ file(WRITE "${PROJECT_BINARY_DIR}/dist/${output}" "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n")
+endfunction()
+
add_executable(yuzu-cmd
config.cpp
config.h
default_ini.h
- emu_window/emu_window_sdl2_gl.cpp
- emu_window/emu_window_sdl2_gl.h
emu_window/emu_window_sdl2.cpp
emu_window/emu_window_sdl2.h
emu_window/emu_window_sdl2_gl.cpp
emu_window/emu_window_sdl2_gl.h
+ emu_window/emu_window_sdl2_vk.cpp
+ emu_window/emu_window_sdl2_vk.h
resource.h
yuzu.cpp
yuzu.rc
)
-if (ENABLE_VULKAN)
- target_sources(yuzu-cmd PRIVATE
- emu_window/emu_window_sdl2_vk.cpp
- emu_window/emu_window_sdl2_vk.h)
-
- target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include)
- target_compile_definitions(yuzu-cmd PRIVATE HAS_VULKAN)
-endif()
-
create_target_directory_groups(yuzu-cmd)
target_link_libraries(yuzu-cmd PRIVATE common core input_common)
@@ -33,13 +34,16 @@ if (MSVC)
endif()
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads)
+create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
+target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR})
+
+target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include)
+
if(UNIX AND NOT APPLE)
install(TARGETS yuzu-cmd RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
if (MSVC)
include(CopyYuzuSDLDeps)
- include(CopyYuzuUnicornDeps)
copy_yuzu_SDL_deps(yuzu-cmd)
- copy_yuzu_unicorn_deps(yuzu-cmd)
endif()
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index e9f1c6500..aa0a9f288 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -228,24 +228,24 @@ static const std::array<int, 8> keyboard_mods{
void Config::ReadValues() {
// Controls
- for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
+ for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
const auto group = fmt::format("ControlsP{}", p);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- Settings::values.players[p].buttons[i] =
+ Settings::values.players.GetValue()[p].buttons[i] =
sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param);
- if (Settings::values.players[p].buttons[i].empty())
- Settings::values.players[p].buttons[i] = default_param;
+ if (Settings::values.players.GetValue()[p].buttons[i].empty())
+ Settings::values.players.GetValue()[p].buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
- Settings::values.players[p].analogs[i] =
+ Settings::values.players.GetValue()[p].analogs[i] =
sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
- if (Settings::values.players[p].analogs[i].empty())
- Settings::values.players[p].analogs[i] = default_param;
+ if (Settings::values.players.GetValue()[p].analogs[i].empty())
+ Settings::values.players.GetValue()[p].analogs[i] = default_param;
}
}
@@ -288,24 +288,22 @@ void Config::ReadValues() {
Settings::values.debug_pad_analogs[i] = default_param;
}
- Settings::values.vibration_enabled =
- sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true);
+ Settings::values.vibration_enabled.SetValue(
+ sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true));
+ Settings::values.enable_accurate_vibrations.SetValue(
+ sdl2_config->GetBoolean("ControlsGeneral", "enable_accurate_vibrations", false));
+ Settings::values.motion_enabled.SetValue(
+ sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true));
Settings::values.touchscreen.enabled =
sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
- Settings::values.touchscreen.device =
- sdl2_config->Get("ControlsGeneral", "touch_device", "engine:emu_window");
- Settings::values.touchscreen.finger =
- sdl2_config->GetInteger("ControlsGeneral", "touch_finger", 0);
Settings::values.touchscreen.rotation_angle =
sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0);
Settings::values.touchscreen.diameter_x =
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
Settings::values.touchscreen.diameter_y =
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
- Settings::values.udp_input_address =
- sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR);
- Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger(
- "Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT));
+ Settings::values.udp_input_servers =
+ sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_SRV);
std::transform(keyboard_keys.begin(), keyboard_keys.end(),
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
@@ -341,8 +339,8 @@ void Config::ReadValues() {
Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", "");
// System
- Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
- const auto size = sdl2_config->GetInteger("System", "users_size", 0);
+ Settings::values.use_docked_mode.SetValue(
+ sdl2_config->GetBoolean("System", "use_docked_mode", true));
Settings::values.current_user = std::clamp<int>(
sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);
@@ -369,7 +367,7 @@ void Config::ReadValues() {
// Core
Settings::values.use_multi_core.SetValue(
- sdl2_config->GetBoolean("Core", "use_multi_core", false));
+ sdl2_config->GetBoolean("Core", "use_multi_core", true));
// Renderer
const int renderer_backend = sdl2_config->GetInteger(
@@ -390,14 +388,14 @@ void Config::ReadValues() {
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)));
Settings::values.use_disk_shader_cache.SetValue(
sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false));
- const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0);
+ const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 1);
Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level));
Settings::values.use_asynchronous_gpu_emulation.SetValue(
- sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false));
+ sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", true));
Settings::values.use_vsync.SetValue(
static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1)));
Settings::values.use_assembly_shaders.SetValue(
- sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", false));
+ sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true));
Settings::values.use_asynchronous_shaders.SetValue(
sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false));
Settings::values.use_asynchronous_shaders.SetValue(
@@ -427,9 +425,6 @@ void Config::ReadValues() {
// Debugging
Settings::values.record_frame_times =
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
- Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
- Settings::values.gdbstub_port =
- static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", "");
Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);
Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index aa9e40380..3ee0e037d 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -65,6 +65,14 @@ button_screenshot=
lstick=
rstick=
+# Whether to enable or disable vibration
+# 0: Disabled, 1 (default): Enabled
+vibration_enabled=
+
+# Whether to enable or disable accurate vibrations
+# 0 (default): Disabled, 1: Enabled
+enable_accurate_vibrations=
+
# for motion input, the following devices are available:
# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
# - "update_period": update period in milliseconds (default to 100)
@@ -94,7 +102,7 @@ udp_pad_index=
[Core]
# Whether to use multi-core for CPU emulation
-# 0 (default): Disabled, 1: Enabled
+# 0: Disabled, 1 (default): Enabled
use_multi_core=
[Cpu]
@@ -163,7 +171,7 @@ max_anisotropy =
use_vsync =
# Whether to use OpenGL assembly shaders or not. NV_gpu_program5 is required.
-# 0 (default): Off, 1: On
+# 0: Off, 1 (default): On
use_assembly_shaders =
# Whether to allow asynchronous shader building.
@@ -266,7 +274,7 @@ gamecard_path =
[System]
# Whether the system is docked
-# 1: Yes, 0 (default): No
+# 1 (default): Yes, 0: No
use_docked_mode =
# Allow the use of NFC in games
@@ -310,9 +318,6 @@ log_filter = *:Trace
[Debugging]
# Record frame time data, can be found in the log directory. Boolean value
record_frame_times =
-# Port for listening to GDB connections.
-use_gdbstub=false
-gdbstub_port=24689
# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
dump_exefs=false
# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index a804d5185..7e391ab89 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -9,13 +9,13 @@
#include "core/perf_stats.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
-#include "input_common/motion_emu.h"
+#include "input_common/mouse/mouse_input.h"
#include "input_common/sdl/sdl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+#include "yuzu_cmd/yuzu_icon.h"
-EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen,
- InputCommon::InputSubsystem* input_subsystem_)
- : system{system}, input_subsystem{input_subsystem_} {
+EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_)
+ : input_subsystem{input_subsystem_} {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
exit(1);
@@ -30,22 +30,23 @@ EmuWindow_SDL2::~EmuWindow_SDL2() {
}
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
- TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
- input_subsystem->GetMotionEmu()->Tilt(x, y);
+ TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0), 0);
+
+ input_subsystem->GetMouse()->MouseMove(x, y, 0, 0);
}
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
if (button == SDL_BUTTON_LEFT) {
if (state == SDL_PRESSED) {
- TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
+ TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0), 0);
} else {
- TouchReleased();
+ TouchReleased(0);
}
} else if (button == SDL_BUTTON_RIGHT) {
if (state == SDL_PRESSED) {
- input_subsystem->GetMotionEmu()->BeginTilt(x, y);
+ input_subsystem->GetMouse()->PressButton(x, y, button);
} else {
- input_subsystem->GetMotionEmu()->EndTilt();
+ input_subsystem->GetMouse()->ReleaseButton(button);
}
}
}
@@ -67,16 +68,16 @@ void EmuWindow_SDL2::OnFingerDown(float x, float y) {
// 3DS does
const auto [px, py] = TouchToPixelPos(x, y);
- TouchPressed(px, py);
+ TouchPressed(px, py, 0);
}
void EmuWindow_SDL2::OnFingerMotion(float x, float y) {
const auto [px, py] = TouchToPixelPos(x, y);
- TouchMoved(px, py);
+ TouchMoved(px, py, 0);
}
void EmuWindow_SDL2::OnFingerUp() {
- TouchReleased();
+ TouchReleased(0);
}
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
@@ -122,62 +123,64 @@ void EmuWindow_SDL2::Fullscreen() {
SDL_MaximizeWindow(render_window);
}
-void EmuWindow_SDL2::PollEvents() {
+void EmuWindow_SDL2::WaitEvent() {
+ // Called on main thread
SDL_Event event;
- // SDL_PollEvent returns 0 when there are no more events in the event queue
- while (SDL_PollEvent(&event)) {
- switch (event.type) {
- case SDL_WINDOWEVENT:
- switch (event.window.event) {
- case SDL_WINDOWEVENT_SIZE_CHANGED:
- case SDL_WINDOWEVENT_RESIZED:
- case SDL_WINDOWEVENT_MAXIMIZED:
- case SDL_WINDOWEVENT_RESTORED:
- OnResize();
- break;
- case SDL_WINDOWEVENT_MINIMIZED:
- case SDL_WINDOWEVENT_EXPOSED:
- is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED;
- OnResize();
- break;
- case SDL_WINDOWEVENT_CLOSE:
- is_open = false;
- break;
- }
- break;
- case SDL_KEYDOWN:
- case SDL_KEYUP:
- OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
- break;
- case SDL_MOUSEMOTION:
- // ignore if it came from touch
- if (event.button.which != SDL_TOUCH_MOUSEID)
- OnMouseMotion(event.motion.x, event.motion.y);
- break;
- case SDL_MOUSEBUTTONDOWN:
- case SDL_MOUSEBUTTONUP:
- // ignore if it came from touch
- if (event.button.which != SDL_TOUCH_MOUSEID) {
- OnMouseButton(event.button.button, event.button.state, event.button.x,
- event.button.y);
- }
- break;
- case SDL_FINGERDOWN:
- OnFingerDown(event.tfinger.x, event.tfinger.y);
- break;
- case SDL_FINGERMOTION:
- OnFingerMotion(event.tfinger.x, event.tfinger.y);
+ if (!SDL_WaitEvent(&event)) {
+ LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", SDL_GetError());
+ exit(1);
+ }
+
+ switch (event.type) {
+ case SDL_WINDOWEVENT:
+ switch (event.window.event) {
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ case SDL_WINDOWEVENT_RESIZED:
+ case SDL_WINDOWEVENT_MAXIMIZED:
+ case SDL_WINDOWEVENT_RESTORED:
+ OnResize();
break;
- case SDL_FINGERUP:
- OnFingerUp();
+ case SDL_WINDOWEVENT_MINIMIZED:
+ case SDL_WINDOWEVENT_EXPOSED:
+ is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED;
+ OnResize();
break;
- case SDL_QUIT:
+ case SDL_WINDOWEVENT_CLOSE:
is_open = false;
break;
- default:
- break;
}
+ break;
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
+ break;
+ case SDL_MOUSEMOTION:
+ // ignore if it came from touch
+ if (event.button.which != SDL_TOUCH_MOUSEID)
+ OnMouseMotion(event.motion.x, event.motion.y);
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ // ignore if it came from touch
+ if (event.button.which != SDL_TOUCH_MOUSEID) {
+ OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y);
+ }
+ break;
+ case SDL_FINGERDOWN:
+ OnFingerDown(event.tfinger.x, event.tfinger.y);
+ break;
+ case SDL_FINGERMOTION:
+ OnFingerMotion(event.tfinger.x, event.tfinger.y);
+ break;
+ case SDL_FINGERUP:
+ OnFingerUp();
+ break;
+ case SDL_QUIT:
+ is_open = false;
+ break;
+ default:
+ break;
}
const u32 current_time = SDL_GetTicks();
@@ -192,6 +195,22 @@ void EmuWindow_SDL2::PollEvents() {
}
}
+void EmuWindow_SDL2::SetWindowIcon() {
+ SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size);
+ if (yuzu_icon_stream == nullptr) {
+ LOG_WARNING(Frontend, "Failed to create yuzu icon stream.");
+ return;
+ }
+ SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1);
+ if (window_icon == nullptr) {
+ LOG_WARNING(Frontend, "Failed to read BMP from stream.");
+ return;
+ }
+ // The icon is attached to the window pointer
+ SDL_SetWindowIcon(render_window, window_icon);
+ SDL_FreeSurface(window_icon);
+}
+
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index 82750ffec..51a12a6a9 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -20,45 +20,44 @@ class InputSubsystem;
class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
public:
- explicit EmuWindow_SDL2(Core::System& system, bool fullscreen,
- InputCommon::InputSubsystem* input_subsystem);
+ explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem);
~EmuWindow_SDL2();
- /// Polls window events
- void PollEvents() override;
-
/// Whether the window is still open, and a close request hasn't yet been sent
bool IsOpen() const;
/// Returns if window is shown (not minimized)
bool IsShown() const override;
- /// Presents the next frame
- virtual void Present() = 0;
+ /// Wait for the next event on the main thread.
+ void WaitEvent();
+
+ // Sets the window icon from yuzu.bmp
+ void SetWindowIcon();
protected:
- /// Called by PollEvents when a key is pressed or released.
+ /// Called by WaitEvent when a key is pressed or released.
void OnKeyEvent(int key, u8 state);
- /// Called by PollEvents when the mouse moves.
+ /// Called by WaitEvent when the mouse moves.
void OnMouseMotion(s32 x, s32 y);
- /// Called by PollEvents when a mouse button is pressed or released
+ /// Called by WaitEvent when a mouse button is pressed or released
void OnMouseButton(u32 button, u8 state, s32 x, s32 y);
/// Translates pixel position (0..1) to pixel positions
std::pair<unsigned, unsigned> TouchToPixelPos(float touch_x, float touch_y) const;
- /// Called by PollEvents when a finger starts touching the touchscreen
+ /// Called by WaitEvent when a finger starts touching the touchscreen
void OnFingerDown(float x, float y);
- /// Called by PollEvents when a finger moves while touching the touchscreen
+ /// Called by WaitEvent when a finger moves while touching the touchscreen
void OnFingerMotion(float x, float y);
- /// Called by PollEvents when a finger stops touching the touchscreen
+ /// Called by WaitEvent when a finger stops touching the touchscreen
void OnFingerUp();
- /// Called by PollEvents when any event that may cause the window to be resized occurs
+ /// Called by WaitEvent when any event that may cause the window to be resized occurs
void OnResize();
/// Called when user passes the fullscreen parameter flag
@@ -67,9 +66,6 @@ protected:
/// Called when a configuration change affects the minimal size of the window
void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override;
- /// Instance of the system, used to access renderer for the presentation thread
- Core::System& system;
-
/// Is the window still open?
bool is_open = true;
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 881b67a76..a02485c14 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -17,7 +17,6 @@
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
-#include "input_common/motion_emu.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
@@ -60,38 +59,25 @@ private:
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
std::vector<std::string_view> unsupported_ext;
- if (!GLAD_GL_ARB_buffer_storage)
- unsupported_ext.push_back("ARB_buffer_storage");
- if (!GLAD_GL_ARB_direct_state_access)
- unsupported_ext.push_back("ARB_direct_state_access");
- if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
- unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
- if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
- unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
- if (!GLAD_GL_ARB_multi_bind)
- unsupported_ext.push_back("ARB_multi_bind");
- if (!GLAD_GL_ARB_clip_control)
- unsupported_ext.push_back("ARB_clip_control");
-
// Extensions required to support some texture formats.
- if (!GLAD_GL_EXT_texture_compression_s3tc)
+ if (!GLAD_GL_EXT_texture_compression_s3tc) {
unsupported_ext.push_back("EXT_texture_compression_s3tc");
- if (!GLAD_GL_ARB_texture_compression_rgtc)
+ }
+ if (!GLAD_GL_ARB_texture_compression_rgtc) {
unsupported_ext.push_back("ARB_texture_compression_rgtc");
- if (!GLAD_GL_ARB_depth_buffer_float)
- unsupported_ext.push_back("ARB_depth_buffer_float");
+ }
- for (const auto& extension : unsupported_ext)
+ for (const auto& extension : unsupported_ext) {
LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", extension);
+ }
return unsupported_ext.empty();
}
-EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen,
- InputCommon::InputSubsystem* input_subsystem)
- : EmuWindow_SDL2{system, fullscreen, input_subsystem} {
+EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen)
+ : EmuWindow_SDL2{input_subsystem} {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
@@ -121,6 +107,8 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen,
dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
+ SetWindowIcon();
+
if (fullscreen) {
Fullscreen();
}
@@ -163,13 +151,3 @@ EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
return std::make_unique<SDLGLContext>();
}
-
-void EmuWindow_SDL2_GL::Present() {
- SDL_GL_MakeCurrent(render_window, window_context);
- SDL_GL_SetSwapInterval(Settings::values.use_vsync.GetValue() ? 1 : 0);
- while (IsOpen()) {
- system.Renderer().TryPresent(100);
- SDL_GL_SwapWindow(render_window);
- }
- SDL_GL_MakeCurrent(render_window, nullptr);
-}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index 732a64edd..dba5c293c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -14,12 +14,9 @@ class InputSubsystem;
class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
public:
- explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen,
- InputCommon::InputSubsystem* input_subsystem);
+ explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen);
~EmuWindow_SDL2_GL();
- void Present() override;
-
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
private:
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
index 53491f86e..6f9b00461 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -19,9 +19,8 @@
#include <SDL.h>
#include <SDL_syswm.h>
-EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen,
- InputCommon::InputSubsystem* input_subsystem)
- : EmuWindow_SDL2{system, fullscreen, input_subsystem} {
+EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem)
+ : EmuWindow_SDL2{input_subsystem} {
const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
Common::g_scm_branch, Common::g_scm_desc);
render_window =
@@ -36,6 +35,8 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen,
std::exit(EXIT_FAILURE);
}
+ SetWindowIcon();
+
switch (wm.subsystem) {
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS:
@@ -74,7 +75,3 @@ EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default;
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
return std::make_unique<DummyContext>();
}
-
-void EmuWindow_SDL2_VK::Present() {
- // TODO (bunnei): ImplementMe
-}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
index f99704d4c..bdfdc3c6f 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -19,11 +19,8 @@ class InputSubsystem;
class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
public:
- explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen,
- InputCommon::InputSubsystem* input_subsystem);
- ~EmuWindow_SDL2_VK();
-
- void Present() override;
+ explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem);
+ ~EmuWindow_SDL2_VK() override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 4f00c804d..982c41785 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -17,6 +17,7 @@
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
+#include "common/nvidia_flags.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
@@ -25,7 +26,7 @@
#include "core/crypto/key_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs_real.h"
-#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
@@ -35,9 +36,7 @@
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
-#ifdef HAS_VULKAN
#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
-#endif
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
@@ -64,7 +63,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
static void PrintHelp(const char* argv0) {
std::cout << "Usage: " << argv0
<< " [options] <filename>\n"
- "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
"-f, --fullscreen Start in fullscreen mode\n"
"-h, --help Display this help and exit\n"
"-v, --version Output version information and exit\n"
@@ -96,12 +94,8 @@ int main(int argc, char** argv) {
Config config;
int option_index = 0;
- bool use_gdbstub = Settings::values.use_gdbstub;
- u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
InitializeLogging();
-
- char* endarg;
#ifdef _WIN32
int argc_w;
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
@@ -116,26 +110,17 @@ int main(int argc, char** argv) {
bool fullscreen = false;
static struct option long_options[] = {
- {"gdbport", required_argument, 0, 'g'}, {"fullscreen", no_argument, 0, 'f'},
- {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'},
- {"program", optional_argument, 0, 'p'}, {0, 0, 0, 0},
+ {"fullscreen", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'v'},
+ {"program", optional_argument, 0, 'p'},
+ {0, 0, 0, 0},
};
while (optind < argc) {
int arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
if (arg != -1) {
switch (static_cast<char>(arg)) {
- case 'g':
- errno = 0;
- gdb_port = strtoul(optarg, &endarg, 0);
- use_gdbstub = true;
- if (endarg == optarg)
- errno = EINVAL;
- if (errno != 0) {
- perror("--gdbport");
- exit(1);
- }
- break;
case 'f':
fullscreen = true;
LOG_INFO(Frontend, "Starting in fullscreen mode...");
@@ -168,32 +153,27 @@ int main(int argc, char** argv) {
MicroProfileOnThreadCreate("EmuThread");
SCOPE_EXIT({ MicroProfileShutdown(); });
+ Common::ConfigureNvidiaEnvironmentFlags();
+
if (filepath.empty()) {
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
return -1;
}
- // Apply the command line arguments
- Settings::values.gdbstub_port = gdb_port;
- Settings::values.use_gdbstub = use_gdbstub;
- Settings::Apply();
-
- Core::System& system{Core::System::GetInstance()};
+ auto& system{Core::System::GetInstance()};
InputCommon::InputSubsystem input_subsystem;
+ // Apply the command line arguments
+ Settings::Apply(system);
+
std::unique_ptr<EmuWindow_SDL2> emu_window;
switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
- emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, &input_subsystem);
+ emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, fullscreen);
break;
case Settings::RendererBackend::Vulkan:
-#ifdef HAS_VULKAN
- emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, &input_subsystem);
+ emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem);
break;
-#else
- LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
- return 1;
-#endif
}
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
@@ -223,7 +203,7 @@ int main(int argc, char** argv) {
const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader);
const u16 error_id = static_cast<u16>(load_result) - loader_id;
LOG_CRITICAL(Frontend,
- "While attempting to load the ROM requested, an error occured. Please "
+ "While attempting to load the ROM requested, an error occurred. Please "
"refer to the yuzu wiki for more information or the yuzu discord for "
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
@@ -235,16 +215,15 @@ int main(int argc, char** argv) {
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
system.GPU().Start();
- system.Renderer().Rasterizer().LoadDiskResources();
+ system.Renderer().ReadRasterizer()->LoadDiskResources(
+ system.CurrentProcess()->GetTitleID(), false,
+ [](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
- std::thread render_thread([&emu_window] { emu_window->Present(); });
- system.Run();
+ void(system.Run());
while (emu_window->IsOpen()) {
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ emu_window->WaitEvent();
}
- system.Pause();
- render_thread.join();
-
+ void(system.Pause());
system.Shutdown();
detached_tasks.WaitForAllTasks();
diff --git a/src/yuzu_tester/CMakeLists.txt b/src/yuzu_tester/CMakeLists.txt
deleted file mode 100644
index 06c2ee011..000000000
--- a/src/yuzu_tester/CMakeLists.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
-
-add_executable(yuzu-tester
- config.cpp
- config.h
- default_ini.h
- emu_window/emu_window_sdl2_hide.cpp
- emu_window/emu_window_sdl2_hide.h
- resource.h
- service/yuzutest.cpp
- service/yuzutest.h
- yuzu.cpp
- yuzu.rc
-)
-
-create_target_directory_groups(yuzu-tester)
-
-target_link_libraries(yuzu-tester PRIVATE common core input_common)
-target_link_libraries(yuzu-tester PRIVATE inih glad)
-if (MSVC)
- target_link_libraries(yuzu-tester PRIVATE getopt)
-endif()
-target_link_libraries(yuzu-tester PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads)
-
-if(UNIX AND NOT APPLE)
- install(TARGETS yuzu-tester RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
-endif()
-
-if (MSVC)
- include(CopyYuzuSDLDeps)
- include(CopyYuzuUnicornDeps)
- copy_yuzu_SDL_deps(yuzu-tester)
- copy_yuzu_unicorn_deps(yuzu-tester)
-endif()
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
deleted file mode 100644
index aaf59129a..000000000
--- a/src/yuzu_tester/config.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <memory>
-#include <sstream>
-#include <SDL.h>
-#include <inih/cpp/INIReader.h>
-#include "common/file_util.h"
-#include "common/logging/log.h"
-#include "common/param_package.h"
-#include "core/hle/service/acc/profile_manager.h"
-#include "core/settings.h"
-#include "input_common/main.h"
-#include "yuzu_tester/config.h"
-#include "yuzu_tester/default_ini.h"
-
-namespace FS = Common::FS;
-
-Config::Config() {
- // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
- sdl2_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + "sdl2-tester-config.ini";
- sdl2_config = std::make_unique<INIReader>(sdl2_config_loc);
-
- Reload();
-}
-
-Config::~Config() = default;
-
-bool Config::LoadINI(const std::string& default_contents, bool retry) {
- const char* location = this->sdl2_config_loc.c_str();
- if (sdl2_config->ParseError() < 0) {
- if (retry) {
- LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location);
- FS::CreateFullPath(location);
- FS::WriteStringToFile(true, default_contents, location);
- sdl2_config = std::make_unique<INIReader>(location); // Reopen file
-
- return LoadINI(default_contents, false);
- }
- LOG_ERROR(Config, "Failed.");
- return false;
- }
- LOG_INFO(Config, "Successfully loaded {}", location);
- return true;
-}
-
-void Config::ReadValues() {
- // Controls
- for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- Settings::values.players[p].buttons[i] = "";
- }
-
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- Settings::values.players[p].analogs[i] = "";
- }
- }
-
- Settings::values.mouse_enabled = false;
- for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
- Settings::values.mouse_buttons[i] = "";
- }
-
- Settings::values.motion_device = "";
-
- Settings::values.keyboard_enabled = false;
-
- Settings::values.debug_pad_enabled = false;
- for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- Settings::values.debug_pad_buttons[i] = "";
- }
-
- for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- Settings::values.debug_pad_analogs[i] = "";
- }
-
- Settings::values.vibration_enabled = true;
- Settings::values.touchscreen.enabled = "";
- Settings::values.touchscreen.device = "";
- Settings::values.touchscreen.finger = 0;
- Settings::values.touchscreen.rotation_angle = 0;
- Settings::values.touchscreen.diameter_x = 15;
- Settings::values.touchscreen.diameter_y = 15;
-
- Settings::values.use_docked_mode =
- sdl2_config->GetBoolean("Controls", "use_docked_mode", false);
-
- // Data Storage
- Settings::values.use_virtual_sd =
- sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
- FS::GetUserPath(Common::FS::UserPath::NANDDir,
- sdl2_config->Get("Data Storage", "nand_directory",
- Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));
- FS::GetUserPath(Common::FS::UserPath::SDMCDir,
- sdl2_config->Get("Data Storage", "sdmc_directory",
- Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));
-
- // System
- Settings::values.current_user = std::clamp<int>(
- sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);
-
- const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
- if (rng_seed_enabled) {
- Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0));
- } else {
- Settings::values.rng_seed.SetValue(std::nullopt);
- }
-
- const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false);
- if (custom_rtc_enabled) {
- Settings::values.custom_rtc.SetValue(
- std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)));
- } else {
- Settings::values.custom_rtc.SetValue(std::nullopt);
- }
-
- // Core
- Settings::values.use_multi_core.SetValue(
- sdl2_config->GetBoolean("Core", "use_multi_core", false));
-
- // Renderer
- Settings::values.aspect_ratio.SetValue(
- static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)));
- Settings::values.max_anisotropy.SetValue(
- static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)));
- Settings::values.use_frame_limit.SetValue(false);
- Settings::values.frame_limit.SetValue(100);
- Settings::values.use_disk_shader_cache.SetValue(
- sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false));
- const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0);
- Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level));
- Settings::values.use_asynchronous_gpu_emulation.SetValue(
- sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false));
- Settings::values.use_fast_gpu_time.SetValue(
- sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true));
-
- Settings::values.bg_red.SetValue(
- static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)));
- Settings::values.bg_green.SetValue(
- static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)));
- Settings::values.bg_blue.SetValue(
- static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)));
-
- // Audio
- Settings::values.sink_id = "null";
- Settings::values.enable_audio_stretching.SetValue(false);
- Settings::values.audio_device_id = "auto";
- Settings::values.volume.SetValue(0);
-
- Settings::values.language_index.SetValue(
- sdl2_config->GetInteger("System", "language_index", 1));
-
- // Miscellaneous
- Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
- Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false);
-
- // Debugging
- Settings::values.use_gdbstub = false;
- Settings::values.program_args = "";
- Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);
- Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
-
- const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
- std::stringstream ss(title_list);
- std::string line;
- while (std::getline(ss, line, '|')) {
- const auto title_id = std::stoul(line, nullptr, 16);
- const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
-
- std::stringstream inner_ss(disabled_list);
- std::string inner_line;
- std::vector<std::string> out;
- while (std::getline(inner_ss, inner_line, '|')) {
- out.push_back(inner_line);
- }
-
- Settings::values.disabled_addons.insert_or_assign(title_id, out);
- }
-
- // Web Service
- Settings::values.enable_telemetry =
- sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
- Settings::values.web_api_url =
- sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org");
- Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", "");
- Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", "");
-}
-
-void Config::Reload() {
- LoadINI(DefaultINI::sdl2_config_file);
- ReadValues();
-}
diff --git a/src/yuzu_tester/config.h b/src/yuzu_tester/config.h
deleted file mode 100644
index 3b68e5bc9..000000000
--- a/src/yuzu_tester/config.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <string>
-
-class INIReader;
-
-class Config {
- std::unique_ptr<INIReader> sdl2_config;
- std::string sdl2_config_loc;
-
- bool LoadINI(const std::string& default_contents = "", bool retry = true);
- void ReadValues();
-
-public:
- Config();
- ~Config();
-
- void Reload();
-};
diff --git a/src/yuzu_tester/default_ini.h b/src/yuzu_tester/default_ini.h
deleted file mode 100644
index 3eb64e9d7..000000000
--- a/src/yuzu_tester/default_ini.h
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-namespace DefaultINI {
-
-const char* sdl2_config_file = R"(
-[Core]
-# Whether to use multi-core for CPU emulation
-# 0 (default): Disabled, 1: Enabled
-use_multi_core=
-
-[Cpu]
-# Enable inline page tables optimization (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_page_tables =
-
-# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_block_linking =
-
-# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_return_stack_buffer =
-
-# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fast_dispatcher =
-
-# Enable context elimination CPU Optimization (reduce host memory use for guest context)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_context_elimination =
-
-# Enable constant propagation CPU optimization (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_const_prop =
-
-# Enable miscellaneous CPU optimizations (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_misc_ir =
-
-# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_reduce_misalign_checks =
-
-[Renderer]
-# Whether to use software or hardware rendering.
-# 0: Software, 1 (default): Hardware
-use_hw_renderer =
-
-# Whether to use the Just-In-Time (JIT) compiler for shader emulation
-# 0: Interpreter (slow), 1 (default): JIT (fast)
-use_shader_jit =
-
-# Aspect ratio
-# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Stretch to Window
-aspect_ratio =
-
-# Anisotropic filtering
-# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
-max_anisotropy =
-
-# Whether to enable V-Sync (caps the framerate at 60FPS) or not.
-# 0 (default): Off, 1: On
-use_vsync =
-
-# Whether to use disk based shader cache
-# 0 (default): Off, 1 : On
-use_disk_shader_cache =
-
-# Whether to use accurate GPU emulation
-# 0 (default): Off (fast), 1 : On (slow)
-use_accurate_gpu_emulation =
-
-# Whether to use asynchronous GPU emulation
-# 0 : Off (slow), 1 (default): On (fast)
-use_asynchronous_gpu_emulation =
-
-# The clear color for the renderer. What shows up on the sides of the bottom screen.
-# Must be in range of 0.0-1.0. Defaults to 1.0 for all.
-bg_red =
-bg_blue =
-bg_green =
-
-[Layout]
-# Layout for the screen inside the render window.
-# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen
-layout_option =
-
-# Toggle custom layout (using the settings below) on or off.
-# 0 (default): Off, 1: On
-custom_layout =
-
-# Screen placement when using Custom layout option
-# 0x, 0y is the top left corner of the render window.
-custom_top_left =
-custom_top_top =
-custom_top_right =
-custom_top_bottom =
-custom_bottom_left =
-custom_bottom_top =
-custom_bottom_right =
-custom_bottom_bottom =
-
-# Swaps the prominent screen with the other screen.
-# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen.
-# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
-swap_screen =
-
-[Data Storage]
-# Whether to create a virtual SD card.
-# 1 (default): Yes, 0: No
-use_virtual_sd =
-
-[System]
-# Whether the system is docked
-# 1: Yes, 0 (default): No
-use_docked_mode =
-
-# Allow the use of NFC in games
-# 1 (default): Yes, 0 : No
-enable_nfc =
-
-# Sets the seed for the RNG generator built into the switch
-# rng_seed will be ignored and randomly generated if rng_seed_enabled is false
-rng_seed_enabled =
-rng_seed =
-
-# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
-# This will auto-increment, with the time set being the time the game is started
-# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
-custom_rtc_enabled =
-custom_rtc =
-
-# Sets the account username, max length is 32 characters
-# yuzu (default)
-username = yuzu
-
-# Sets the systems language index
-# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese,
-# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French,
-# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese
-language_index =
-
-# The system region that yuzu will use during emulation
-# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
-region_value =
-
-[Miscellaneous]
-# A filter which removes logs below a certain logging level.
-# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
-log_filter = *:Trace
-
-[Debugging]
-# Arguments to be passed to argv/argc in the emulated program. It is preferable to use the testing service datastring
-program_args=
-# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
-dump_exefs=false
-# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
-dump_nso=false
-
-[WebService]
-# Whether or not to enable telemetry
-# 0: No, 1 (default): Yes
-enable_telemetry =
-# URL for Web API
-web_api_url = https://api.yuzu-emu.org
-# Username and token for yuzu Web Service
-# See https://profile.yuzu-emu.org/ for more info
-yuzu_username =
-yuzu_token =
-
-[AddOns]
-# Used to disable add-ons
-# List of title IDs of games that will have add-ons disabled (separated by '|'):
-title_ids =
-# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
-# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
-)";
-}
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
deleted file mode 100644
index 78f75fb38..000000000
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <cstdlib>
-#include <string>
-
-#include <fmt/format.h>
-
-#define SDL_MAIN_HANDLED
-#include <SDL.h>
-
-#include <glad/glad.h>
-
-#include "common/logging/log.h"
-#include "common/scm_rev.h"
-#include "core/settings.h"
-#include "input_common/main.h"
-#include "yuzu_tester/emu_window/emu_window_sdl2_hide.h"
-
-bool EmuWindow_SDL2_Hide::SupportsRequiredGLExtensions() {
- std::vector<std::string> unsupported_ext;
-
- if (!GLAD_GL_ARB_direct_state_access)
- unsupported_ext.push_back("ARB_direct_state_access");
- if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
- unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
- if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
- unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
- if (!GLAD_GL_ARB_multi_bind)
- unsupported_ext.push_back("ARB_multi_bind");
-
- // Extensions required to support some texture formats.
- if (!GLAD_GL_EXT_texture_compression_s3tc)
- unsupported_ext.push_back("EXT_texture_compression_s3tc");
- if (!GLAD_GL_ARB_texture_compression_rgtc)
- unsupported_ext.push_back("ARB_texture_compression_rgtc");
- if (!GLAD_GL_ARB_depth_buffer_float)
- unsupported_ext.push_back("ARB_depth_buffer_float");
-
- for (const std::string& ext : unsupported_ext)
- LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext);
-
- return unsupported_ext.empty();
-}
-
-EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() {
- // Initialize the window
- if (SDL_Init(SDL_INIT_VIDEO) < 0) {
- LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
- exit(1);
- }
-
- input_subsystem->Initialize();
-
- SDL_SetMainReady();
-
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
- SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
- SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
- SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
-
- std::string window_title = fmt::format("yuzu-tester {} | {}-{}", Common::g_build_fullname,
- Common::g_scm_branch, Common::g_scm_desc);
- render_window = SDL_CreateWindow(window_title.c_str(),
- SDL_WINDOWPOS_UNDEFINED, // x position
- SDL_WINDOWPOS_UNDEFINED, // y position
- Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
- SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE |
- SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
-
- if (render_window == nullptr) {
- LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError());
- exit(1);
- }
-
- gl_context = SDL_GL_CreateContext(render_window);
-
- if (gl_context == nullptr) {
- LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError());
- exit(1);
- }
-
- if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
- LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError());
- exit(1);
- }
-
- if (!SupportsRequiredGLExtensions()) {
- LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting...");
- exit(1);
- }
-
- SDL_PumpEvents();
- SDL_GL_SetSwapInterval(false);
- LOG_INFO(Frontend, "yuzu-tester Version: {} | {}-{}", Common::g_build_fullname,
- Common::g_scm_branch, Common::g_scm_desc);
- Settings::LogSettings();
-}
-
-EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() {
- input_subsystem->Shutdown();
- SDL_GL_DeleteContext(gl_context);
- SDL_Quit();
-}
-
-void EmuWindow_SDL2_Hide::PollEvents() {}
-
-bool EmuWindow_SDL2_Hide::IsShown() const {
- return false;
-}
-
-class SDLGLContext : public Core::Frontend::GraphicsContext {
-public:
- explicit SDLGLContext() {
- // create a hidden window to make the shared context against
- window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
- SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
- context = SDL_GL_CreateContext(window);
- }
-
- ~SDLGLContext() {
- DoneCurrent();
- SDL_GL_DeleteContext(context);
- SDL_DestroyWindow(window);
- }
-
- void MakeCurrent() override {
- SDL_GL_MakeCurrent(window, context);
- }
-
- void DoneCurrent() override {
- SDL_GL_MakeCurrent(window, nullptr);
- }
-
-private:
- SDL_Window* window;
- SDL_GLContext context;
-};
-
-std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_Hide::CreateSharedContext() const {
- return std::make_unique<SDLGLContext>();
-}
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
deleted file mode 100644
index a553b4b95..000000000
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "core/frontend/emu_window.h"
-
-struct SDL_Window;
-
-namespace InputCommon {
-class InputSubsystem;
-}
-
-class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow {
-public:
- explicit EmuWindow_SDL2_Hide();
- ~EmuWindow_SDL2_Hide();
-
- /// Polls window events
- void PollEvents() override;
-
- /// Whether the screen is being shown or not.
- bool IsShown() const override;
-
- std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
-
-private:
- /// Whether the GPU and driver supports the OpenGL extension required
- bool SupportsRequiredGLExtensions();
-
- std::unique_ptr<InputCommon::InputSubsystem> input_subsystem;
-
- /// Internal SDL2 render window
- SDL_Window* render_window;
-
- using SDL_GLContext = void*;
- /// The OpenGL context associated with the window
- SDL_GLContext gl_context;
-};
diff --git a/src/yuzu_tester/resource.h b/src/yuzu_tester/resource.h
deleted file mode 100644
index df8e459e4..000000000
--- a/src/yuzu_tester/resource.h
+++ /dev/null
@@ -1,16 +0,0 @@
-//{{NO_DEPENDENCIES}}
-// Microsoft Visual C++ generated include file.
-// Used by pcafe.rc
-//
-#define IDI_ICON3 103
-
-// Next default values for new objects
-//
-#ifdef APSTUDIO_INVOKED
-#ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE 105
-#define _APS_NEXT_COMMAND_VALUE 40001
-#define _APS_NEXT_CONTROL_VALUE 1001
-#define _APS_NEXT_SYMED_VALUE 101
-#endif
-#endif
diff --git a/src/yuzu_tester/service/yuzutest.cpp b/src/yuzu_tester/service/yuzutest.cpp
deleted file mode 100644
index 2d3f6e3a7..000000000
--- a/src/yuzu_tester/service/yuzutest.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <memory>
-#include "common/string_util.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/service/service.h"
-#include "core/hle/service/sm/sm.h"
-#include "yuzu_tester/service/yuzutest.h"
-
-namespace Service::Yuzu {
-
-constexpr u64 SERVICE_VERSION = 0x00000002;
-
-class YuzuTest final : public ServiceFramework<YuzuTest> {
-public:
- explicit YuzuTest(std::string data,
- std::function<void(std::vector<TestResult>)> finish_callback)
- : ServiceFramework{"yuzutest"}, data(std::move(data)),
- finish_callback(std::move(finish_callback)) {
- static const FunctionInfo functions[] = {
- {0, &YuzuTest::Initialize, "Initialize"},
- {1, &YuzuTest::GetServiceVersion, "GetServiceVersion"},
- {2, &YuzuTest::GetData, "GetData"},
- {10, &YuzuTest::StartIndividual, "StartIndividual"},
- {20, &YuzuTest::FinishIndividual, "FinishIndividual"},
- {100, &YuzuTest::ExitProgram, "ExitProgram"},
- };
-
- RegisterHandlers(functions);
- }
-
-private:
- void Initialize(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Frontend, "called");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
-
- void GetServiceVersion(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Frontend, "called");
- IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(RESULT_SUCCESS);
- rb.Push(SERVICE_VERSION);
- }
-
- void GetData(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Frontend, "called");
- const auto size = ctx.GetWriteBufferSize();
- const auto write_size = std::min(size, data.size());
- ctx.WriteBuffer(data.data(), write_size);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(static_cast<u32>(write_size));
- }
-
- void StartIndividual(Kernel::HLERequestContext& ctx) {
- const auto name_raw = ctx.ReadBuffer();
-
- const auto name = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(name_raw.data()), name_raw.size());
-
- LOG_DEBUG(Frontend, "called, name={}", name);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
-
- void FinishIndividual(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
-
- const auto code = rp.PopRaw<u32>();
-
- const auto result_data_raw = ctx.ReadBuffer();
- const auto test_name_raw = ctx.ReadBuffer(1);
-
- const auto data = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(result_data_raw.data()), result_data_raw.size());
- const auto test_name = Common::StringFromFixedZeroTerminatedBuffer(
- reinterpret_cast<const char*>(test_name_raw.data()), test_name_raw.size());
-
- LOG_INFO(Frontend, "called, result_code={:08X}, data={}, name={}", code, data, test_name);
-
- results.push_back({code, data, test_name});
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- }
-
- void ExitProgram(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Frontend, "called");
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
-
- finish_callback(std::move(results));
- }
-
- std::string data;
-
- std::vector<TestResult> results;
- std::function<void(std::vector<TestResult>)> finish_callback;
-};
-
-void InstallInterfaces(SM::ServiceManager& sm, std::string data,
- std::function<void(std::vector<TestResult>)> finish_callback) {
- std::make_shared<YuzuTest>(data, finish_callback)->InstallAsService(sm);
-}
-
-} // namespace Service::Yuzu
diff --git a/src/yuzu_tester/service/yuzutest.h b/src/yuzu_tester/service/yuzutest.h
deleted file mode 100644
index eca129c8c..000000000
--- a/src/yuzu_tester/service/yuzutest.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <functional>
-#include <string>
-
-namespace Service::SM {
-class ServiceManager;
-}
-
-namespace Service::Yuzu {
-
-struct TestResult {
- u32 code;
- std::string data;
- std::string name;
-};
-
-void InstallInterfaces(SM::ServiceManager& sm, std::string data,
- std::function<void(std::vector<TestResult>)> finish_callback);
-
-} // namespace Service::Yuzu
diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp
deleted file mode 100644
index 7acf0caad..000000000
--- a/src/yuzu_tester/yuzu.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2019 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <chrono>
-#include <iostream>
-#include <memory>
-#include <string>
-#include <thread>
-
-#include <fmt/ostream.h>
-
-#include "common/common_paths.h"
-#include "common/detached_tasks.h"
-#include "common/file_util.h"
-#include "common/logging/backend.h"
-#include "common/logging/filter.h"
-#include "common/logging/log.h"
-#include "common/microprofile.h"
-#include "common/scm_rev.h"
-#include "common/scope_exit.h"
-#include "common/string_util.h"
-#include "common/telemetry.h"
-#include "core/core.h"
-#include "core/crypto/key_manager.h"
-#include "core/file_sys/registered_cache.h"
-#include "core/file_sys/vfs_real.h"
-#include "core/hle/service/filesystem/filesystem.h"
-#include "core/loader/loader.h"
-#include "core/settings.h"
-#include "core/telemetry_session.h"
-#include "video_core/renderer_base.h"
-#include "yuzu_tester/config.h"
-#include "yuzu_tester/emu_window/emu_window_sdl2_hide.h"
-#include "yuzu_tester/service/yuzutest.h"
-
-#ifdef _WIN32
-// windows.h needs to be included before shellapi.h
-#include <windows.h>
-
-#include <shellapi.h>
-#endif
-
-#undef _UNICODE
-#include <getopt.h>
-#ifndef _MSC_VER
-#include <unistd.h>
-#endif
-
-#ifdef _WIN32
-extern "C" {
-// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
-// graphics
-__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
-__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
-}
-#endif
-
-static void PrintHelp(const char* argv0) {
- std::cout << "Usage: " << argv0
- << " [options] <filename>\n"
- "-h, --help Display this help and exit\n"
- "-v, --version Output version information and exit\n"
- "-d, --datastring Pass following string as data to test service command #2\n"
- "-l, --log Log to console in addition to file (will log to file only "
- "by default)\n";
-}
-
-static void PrintVersion() {
- std::cout << "yuzu [Test Utility] " << Common::g_scm_branch << " " << Common::g_scm_desc
- << std::endl;
-}
-
-static void InitializeLogging(bool console) {
- Log::Filter log_filter(Log::Level::Debug);
- log_filter.ParseFilterString(Settings::values.log_filter);
- Log::SetGlobalFilter(log_filter);
-
- if (console)
- Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
-
- const std::string& log_dir = Common::FS::GetUserPath(Common::FS::UserPath::LogDir);
- Common::FS::CreateFullPath(log_dir);
- Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
-#ifdef _WIN32
- Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
-#endif
-}
-
-/// Application entry point
-int main(int argc, char** argv) {
- Common::DetachedTasks detached_tasks;
- Config config;
-
- int option_index = 0;
-
-#ifdef _WIN32
- int argc_w;
- auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
-
- if (argv_w == nullptr) {
- std::cout << "Failed to get command line arguments" << std::endl;
- return -1;
- }
-#endif
- std::string filepath;
-
- static struct option long_options[] = {
- {"help", no_argument, 0, 'h'},
- {"version", no_argument, 0, 'v'},
- {"datastring", optional_argument, 0, 'd'},
- {"log", no_argument, 0, 'l'},
- {0, 0, 0, 0},
- };
-
- bool console_log = false;
- std::string datastring;
-
- while (optind < argc) {
- int arg = getopt_long(argc, argv, "hvdl::", long_options, &option_index);
- if (arg != -1) {
- switch (static_cast<char>(arg)) {
- case 'h':
- PrintHelp(argv[0]);
- return 0;
- case 'v':
- PrintVersion();
- return 0;
- case 'd':
- datastring = argv[optind];
- ++optind;
- break;
- case 'l':
- console_log = true;
- break;
- }
- } else {
-#ifdef _WIN32
- filepath = Common::UTF16ToUTF8(argv_w[optind]);
-#else
- filepath = argv[optind];
-#endif
- optind++;
- }
- }
-
- InitializeLogging(console_log);
-
-#ifdef _WIN32
- LocalFree(argv_w);
-#endif
-
- MicroProfileOnThreadCreate("EmuThread");
- SCOPE_EXIT({ MicroProfileShutdown(); });
-
- if (filepath.empty()) {
- LOG_CRITICAL(Frontend, "Failed to load application: No application specified");
- std::cout << "Failed to load application: No application specified" << std::endl;
- PrintHelp(argv[0]);
- return -1;
- }
-
- Settings::values.use_gdbstub = false;
- Settings::Apply();
-
- std::unique_ptr<EmuWindow_SDL2_Hide> emu_window{std::make_unique<EmuWindow_SDL2_Hide>()};
-
- bool finished = false;
- int return_value = 0;
- const auto callback = [&finished,
- &return_value](std::vector<Service::Yuzu::TestResult> results) {
- finished = true;
- return_value = 0;
-
- // Find the minimum length needed to fully enclose all test names (and the header field) in
- // the fmt::format column by first finding the maximum size of any test name and comparing
- // that to 9, the string length of 'Test Name'
- const auto needed_length_name =
- std::max<u64>(std::max_element(results.begin(), results.end(),
- [](const auto& lhs, const auto& rhs) {
- return lhs.name.size() < rhs.name.size();
- })
- ->name.size(),
- 9ull);
-
- std::size_t passed = 0;
- std::size_t failed = 0;
-
- std::cout << fmt::format("Result [Res Code] | {:<{}} | Extra Data", "Test Name",
- needed_length_name)
- << std::endl;
-
- for (const auto& res : results) {
- const auto main_res = res.code == 0 ? "PASSED" : "FAILED";
- if (res.code == 0)
- ++passed;
- else
- ++failed;
- std::cout << fmt::format("{} [{:08X}] | {:<{}} | {}", main_res, res.code, res.name,
- needed_length_name, res.data)
- << std::endl;
- }
-
- std::cout << std::endl
- << fmt::format("{:4d} Passed | {:4d} Failed | {:4d} Total | {:2.2f} Passed Ratio",
- passed, failed, passed + failed,
- static_cast<float>(passed) / (passed + failed))
- << std::endl
- << (failed == 0 ? "PASSED" : "FAILED") << std::endl;
-
- if (failed > 0)
- return_value = -1;
- };
-
- Core::System& system{Core::System::GetInstance()};
- system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
- system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
- system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
-
- SCOPE_EXIT({ system.Shutdown(); });
-
- const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)};
-
- switch (load_result) {
- case Core::System::ResultStatus::ErrorGetLoader:
- LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
- return -1;
- case Core::System::ResultStatus::ErrorLoader:
- LOG_CRITICAL(Frontend, "Failed to load ROM!");
- return -1;
- case Core::System::ResultStatus::ErrorNotInitialized:
- LOG_CRITICAL(Frontend, "CPUCore not initialized");
- return -1;
- case Core::System::ResultStatus::ErrorVideoCore:
- LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
- return -1;
- case Core::System::ResultStatus::Success:
- break; // Expected case
- default:
- if (static_cast<u32>(load_result) >
- static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) {
- const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader);
- const u16 error_id = static_cast<u16>(load_result) - loader_id;
- LOG_CRITICAL(Frontend,
- "While attempting to load the ROM requested, an error occured. Please "
- "refer to the yuzu wiki for more information or the yuzu discord for "
- "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
- loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
- }
- }
-
- Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback);
-
- system.TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend",
- "SDLHideTester");
-
- system.GPU().Start();
- system.Renderer().Rasterizer().LoadDiskResources();
-
- system.Run();
- while (!finished) {
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
- }
- system.Pause();
-
- detached_tasks.WaitForAllTasks();
- return return_value;
-}
diff --git a/src/yuzu_tester/yuzu.rc b/src/yuzu_tester/yuzu.rc
deleted file mode 100644
index 0cde75e2f..000000000
--- a/src/yuzu_tester/yuzu.rc
+++ /dev/null
@@ -1,17 +0,0 @@
-#include "winresrc.h"
-/////////////////////////////////////////////////////////////////////////////
-//
-// Icon
-//
-
-// Icon with lowest ID value placed first to ensure application icon
-// remains consistent on all systems.
-YUZU_ICON ICON "../../dist/yuzu.ico"
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// RT_MANIFEST
-//
-
-0 RT_MANIFEST "../../dist/yuzu.manifest"